import { useRect } from '@reach/rect';
import {
  Tabs as ReachTabs,
  TabList as ReachTabList,
  Tab as ReachTab,
  useTabsContext,
} from '@reach/tabs';
import { useNavigate } from '@tanstack/react-router';
import clsx from 'clsx';
import React from 'react';

import Counter from './counter';

import type { LinkOptions } from '@tanstack/react-router';

import './tabs.css';

const HORIZONTAL_PADDING = 8;
const AnimatedContext = React.createContext(null);

interface IAnimatedInterface {
  /** Callback when a tab is chosen. */
  onChange(index: number): void;
  /** Default tab index. */
  defaultIndex: number;
  children: React.ReactNode;
}

/** Tabs with a sliding active state. */
const AnimatedTabs: React.FC<IAnimatedInterface> = ({ children, ...rest }) => {
  const [activeRect, setActiveRect] = React.useState(null);
  const ref = React.useRef<any>(null);
  const rect = useRect(ref);

  // @ts-ignore
  const top: number = (activeRect && activeRect.bottom) - (rect && rect.top);
  // @ts-ignore
  const width: number = activeRect && activeRect.width - HORIZONTAL_PADDING * 2;
  // @ts-ignore
  const left: number = (activeRect && activeRect.left) - (rect && rect.left) + HORIZONTAL_PADDING;

  return (
    // @ts-ignore
    <AnimatedContext.Provider value={setActiveRect}>
      <ReachTabs
        {...rest}
        // @ts-ignore
        ref={ref}
      >
        <div className='absolute h-[3px] w-full bg-primary-200 dark:bg-gray-800' style={{ top }} />
        <div
          className={clsx('absolute h-[3px] bg-primary-500 transition-all duration-200', {
            hidden: top <= 0,
          })}
          style={{ left, top, width }}
        />
        {children}
      </ReachTabs>
    </AnimatedContext.Provider>
  );
};

interface IAnimatedTab {
  /** ARIA role. */
  role: 'button';
  /** Element to represent the tab. */
  as: 'a' | 'button';
  /** Route to visit when the tab is chosen. */
  href?: string;
  /** Tab title text. */
  title: string;
  /** Index value of the tab. */
  index: number;
}

/** A single animated tab. */
const AnimatedTab: React.FC<IAnimatedTab> = ({ index, ...props }) => {
  // get the currently selected index from useTabsContext
  const { selectedIndex } = useTabsContext();
  const isSelected: boolean = selectedIndex === index;

  // measure the size of our element, only listen to rect if active
  const ref = React.useRef<any>(null);
  const rect = useRect(ref, { observe: isSelected });

  // get the style changing function from context
  const setActiveRect = React.useContext(AnimatedContext);

  // callup to set styles whenever we're active
  React.useLayoutEffect(() => {
    if (isSelected) {
      // @ts-ignore
      setActiveRect(rect);
    }
  }, [isSelected, rect, setActiveRect]);

  return (
    // @ts-ignore
    <ReachTab ref={ref} {...props} />
  );
};

/** Structure to represent a tab. */
type Item = {
  /** Tab text. */
  text: React.ReactNode;
  /** Tab tooltip text. */
  title?: string;
  /** URL to visit when the tab is selected. */
  href?: string;
  /** Callback when the tab is selected. */
  action?: () => void;
  /** Display a counter over the tab. */
  count?: number;
  /** Unique name for this tab. */
  name: string;
} & (LinkOptions | { to?: undefined });

interface ITabs {
  /** Array of structured tab items. */
  items: Item[];
  /** Name of the active tab item. */
  activeItem: string;
}

/** Animated tabs component. */
const Tabs = ({ items, activeItem }: ITabs) => {
  const navigate = useNavigate();

  const defaultIndex = items.findIndex(({ name }) => name === activeItem);

  const onChange = (selectedIndex: number) => {
    const item = items[selectedIndex];

    if (typeof item.action === 'function') {
      item.action();
    } else if (item.to) {
      navigate({ to: item.to, params: item.params, search: item.search });
    }
  };

  const renderItem = (item: Item, idx: number) => {
    const { name, text, title, count } = item;

    return (
      <AnimatedTab
        key={name}
        as='button'
        role='button'
        // @ts-ignore
        title={title}
        index={idx}
      >
        <div className='relative'>
          {count ? (
            <span className='absolute left-full ml-2'>
              <Counter count={count} />
            </span>
          ) : null}

          {text}
        </div>
      </AnimatedTab>
    );
  };

  return (
    <AnimatedTabs onChange={onChange} defaultIndex={defaultIndex}>
      <ReachTabList>{items.map((item, i) => renderItem(item, i))}</ReachTabList>
    </AnimatedTabs>
  );
};

export { type Item, Tabs as default };
