import {
  ParentComponent,
  createContext,
  createEffect,
  createMemo,
  createRenderEffect,
  createSignal,
  useContext,
  type Signal,
} from "solid-js";
import { css } from "solid-styled";

const TabListContext = /* @__PURE__ */ createContext<{
  focusOn: Signal<HTMLElement[]>;
}>();

export function useTabListContext() {
  const result = useContext(TabListContext);
  if (!result) {
    throw new TypeError("tab list context is not found");
  }
  return result;
}

const ANIM_SPEED = 160 / 110; // 160px/110ms

const TABLIST_FOCUS_CLASS = "tablist-focus";

const Tabs: ParentComponent<{
  offset?: number;
  onFocusChanged?: (element: HTMLElement[]) => void;
}> = (props) => {
  let self: HTMLDivElement;
  const [focusOn, setFocusOn] = createSignal<HTMLElement[]>([]);

  createRenderEffect<HTMLElement[] | undefined>((lastFocusElement) => {
    const current = focusOn();
    if (lastFocusElement) {
      for (const e of lastFocusElement) {
        e.classList.remove(TABLIST_FOCUS_CLASS);
      }
    }
    for (const e of current) {
      e.classList.add("tablist-focus");
    }
    return current;
  });

  createRenderEffect(() => {
    const callback = props.onFocusChanged;
    if (!callback) return;
    callback(focusOn());
  });

  let lastLeft = 0;
  let lastWidth = 0;

  const getNearestDistance = (
    srcRect: { x: number; width: number },
    prevEl: Element | null,
    nextEl: Element | null,
    offset?: number,
  ) => {
    if (!offset || offset === 0) return [0, 0] as const;
    if (offset > 0) {
      if (!nextEl) return [0, 0] as const;
      const rect = nextEl.getBoundingClientRect();
      return [
        (rect.x - srcRect.x) * offset,
        (rect.width - srcRect.width) * offset,
      ] as const;
    } else {
      if (!prevEl) return [0, 0] as const;
      const rect = prevEl.getBoundingClientRect();
      return [
        (rect.x - srcRect.x) * offset,
        (srcRect.width - rect.width) * offset,
      ] as const;
    }
  };

  const focusBoundingClientRect = () => {
    return focusOn()
      .map((x) => x.getBoundingClientRect())
      .reduce(
        (p, c) => {
          return {
            x: Math.min(p.x, c.x),
            width: p.width + c.width,
          };
        },
        { x: +Infinity, width: 0 },
      );
  };

  const focusSiblings = () => {
    const rects = focusOn().map((x) => [x, x.getBoundingClientRect()] as const);
    if (rects.length === 0) return [null, null] as const;
    rects.sort(([, rect1], [, rect2]) => rect1.x - rect2.x);
    return [
      rects[0][0].previousElementSibling,
      rects[rects.length - 1][0].nextElementSibling,
    ] as const;
  };

  const indicator = () => {
    const el = focusOn();
    if (!el) {
      return ["0px", "0px", "110ms", "110ms"] as const;
    }
    const rect = focusBoundingClientRect();
    const rootRect = self.getBoundingClientRect();
    const left = rect.x - rootRect.x;
    const width = rect.width;
    const [prevEl, nextEl] = focusSiblings();
    const [offset, widthChange] = getNearestDistance(
      rect,
      prevEl,
      nextEl,
      props.offset,
    );
    const result = [
      `${left + offset}px`,
      `${width + widthChange}px`,
      `${Math.max(Math.floor(Math.abs(left + offset - lastLeft)), 160) * ANIM_SPEED}ms`,
      `${Math.max(Math.floor(Math.abs(width - lastWidth)), 160) * ANIM_SPEED}ms`,
    ] as const;
    lastLeft = left;
    lastWidth = width;
    return result;
  };

  css`
    .tablist {
      width: 100%;
      position: relative;
      white-space: nowrap;
      overflow-x: auto;

      &::after {
        transition:
          left ${indicator()[2]} var(--tutu-anim-curve-std),
          width ${indicator()[3]} var(--tutu-anim-curve-std);
        position: absolute;
        content: "";
        display: block;
        background-color: white;
        height: 2px;
        width: ${indicator()[1]};
        left: ${indicator()[0]};
        bottom: 0;
      }
    }
  `;

  return (
    <TabListContext.Provider value={{ focusOn: [focusOn, setFocusOn] }}>
      <div ref={self!} class="tablist" role="tablist">
        {props.children}
      </div>
    </TabListContext.Provider>
  );
};

export default Tabs;
