import gsap from "gsap";

export const useButtonNavigation = <T extends Record<string, Rect>>({
  buttons,
  initialButton,
  canClickButton,
  onButtonClick,
  navigation,
  disabled,
  selectorAnimation,
}: {
  buttons: T;
  initialButton: keyof T;
  canClickButton?: (buttonName: keyof T) => boolean;
  onButtonClick?: (buttonName: keyof T) => void;
  navigation: Record<
    keyof T,
    {
      up?: keyof T | "last" | [buttonName: keyof T, blocked: boolean];
      down?: keyof T | "last" | [buttonName: keyof T, blocked: boolean];
      left?: keyof T | [buttonName: keyof T, blocked: boolean];
      right?: keyof T | [buttonName: keyof T, blocked: boolean];
      horizontalMode?: "navigate" | "preview";
    }
  >;
  disabled?: Ref<boolean>;
  selectorAnimation: {
    duration: number;
    ease: gsap.EaseString;
  };
}) => {
  type Entry = keyof T;

  const confirmationModal = useConfirmationModal();

  const selectedButton = ref(initialButton);
  const selectorPosition = ref<Rect>(buttons[initialButton]!);
  const nextButton = ref<Entry | undefined>();
  const isAnimating = ref(false);
  let currentTimeline: gsap.core.Timeline | null = null;

  const blockInteractions = computed(
    () => confirmationModal.isOpen || disabled?.value,
  );

  const getNavigationTarget = (
    value: Entry | [buttonName: Entry, blocked: boolean] | undefined,
  ): { target: Entry | "last"; blocked: boolean } | null => {
    if (!value) return null;
    if (Array.isArray(value)) {
      return { target: value[0]!, blocked: value[1] === false };
    }
    return { target: value, blocked: false };
  };

  const buildNavigationGraph = (
    nav: typeof navigation,
  ): Map<Entry, Set<Entry>> => {
    const edges = new Map<Entry, Set<Entry>>();

    for (const [button, navConfig] of Object.entries(nav) as [
      Entry,
      (typeof nav)[Entry],
    ][]) {
      if (!edges.has(button)) {
        edges.set(button, new Set());
      }

      const up = getNavigationTarget(navConfig.up);
      const down = getNavigationTarget(navConfig.down);
      const left = getNavigationTarget(navConfig.left);
      const right = getNavigationTarget(navConfig.right);

      // handle blocked paths
      if (up && up.target !== "last" && !up.blocked) {
        edges.get(button)!.add(up.target);
      }
      if (down && down.target !== "last" && !down.blocked) {
        edges.get(button)!.add(down.target);
      }
      if (left && !left.blocked) {
        edges.get(button)!.add(left.target);
      }
      if (right && !right.blocked) {
        edges.get(button)!.add(right.target);
      }

      if (up?.target === "last" || down?.target === "last") {
        for (const [otherButton, otherNav] of Object.entries(nav) as [
          Entry,
          (typeof nav)[Entry],
        ][]) {
          if (otherButton === button) continue;

          const otherUp = getNavigationTarget(otherNav.up);
          const otherDown = getNavigationTarget(otherNav.down);
          const otherLeft = getNavigationTarget(otherNav.left);
          const otherRight = getNavigationTarget(otherNav.right);

          if (
            (otherUp?.target === button && !otherUp.blocked) ||
            (otherDown?.target === button && !otherDown.blocked) ||
            (otherLeft?.target === button && !otherLeft.blocked) ||
            (otherRight?.target === button && !otherRight.blocked)
          ) {
            edges.get(button)!.add(otherButton);
          }
        }
      }
    }

    return edges;
  };

  const findPath = (
    graph: Map<Entry, Set<Entry>>,
    from: Entry,
    to: Entry,
  ): Array<Entry> | null => {
    if (from === to) return [];

    const queue: Array<{ button: Entry; path: Array<Entry> }> = [
      { button: from, path: [] },
    ];
    const visited = new Set<Entry>([from]);

    while (queue.length > 0) {
      const { button, path } = queue.shift()!;

      const neighbors = graph.get(button);
      if (!neighbors) continue;

      for (const neighbor of neighbors) {
        if (visited.has(neighbor)) continue;

        const newPath = [...path, neighbor];

        if (neighbor === to) {
          return newPath;
        }

        visited.add(neighbor);
        queue.push({ button: neighbor, path: newPath });
      }
    }

    return null;
  };

  const graph = buildNavigationGraph(navigation);

  const animateToButton = (targetButton: Entry, path?: Array<Entry> | null) => {
    if (currentTimeline) {
      currentTimeline.kill();
      currentTimeline = null;
    }

    const pathButtons = path && path.length > 0 ? path : [targetButton];

    isAnimating.value = true;
    const timeline = gsap.timeline({
      onComplete: () => {
        isAnimating.value = false;
        currentTimeline = null;
      },
    });
    currentTimeline = timeline;

    selectorPosition.value = [...selectorPosition.value];

    for (const button of pathButtons) {
      const buttonRect = buttons[button]!;

      timeline.to(
        selectorPosition.value,
        {
          [0]: buttonRect[0],
          [1]: buttonRect[1],
          [2]: buttonRect[2],
          [3]: buttonRect[3],
          ...selectorAnimation,
        },
        "+=0",
      );
    }

    selectedButton.value = targetButton;
  };

  const { onClick } = useScreen();

  onClick((x: number, y: number) => {
    if (blockInteractions.value) return;

    for (const [buttonName, buttonRect] of Object.entries(buttons) as [
      Entry,
      Rect,
    ][]) {
      const [sx, sy, sw, sh] = buttonRect;
      if (x >= sx && x <= sx + sw && y >= sy && y <= sy + sh) {
        if (canClickButton && !canClickButton(buttonName)) continue;

        if (selectedButton.value === buttonName) {
          onButtonClick?.(buttonName);
        } else {
          const path = findPath(graph, selectedButton.value, buttonName);

          if (
            (navigation[buttonName].down === "last" &&
              navigation[selectedButton.value]!.up === buttonName) ||
            (navigation[buttonName].up === "last" &&
              navigation[selectedButton.value]!.down === buttonName)
          ) {
            nextButton.value = selectedButton.value;
          }

          animateToButton(buttonName, path);
        }
        break;
      }
    }
  });

  useKeyDown((key) => {
    if (blockInteractions.value) return;

    const currentButton = selectedButton.value as Entry;
    const currentNav = navigation[currentButton];

    if (!currentNav) return;

    let targetButton: Entry | undefined;

    switch (key) {
      case "NDS_UP": {
        const upConfig = getNavigationTarget(currentNav.up);
        if (!upConfig) return;

        if (upConfig.target === "last") {
          if (nextButton.value) {
            targetButton = nextButton.value;
          } else {
            const leftConfig = getNavigationTarget(currentNav.left);
            const rightConfig = getNavigationTarget(currentNav.right);
            targetButton = leftConfig?.target ?? rightConfig?.target;
          }
        } else {
          const targetNav = navigation[upConfig.target];
          const targetDownConfig = getNavigationTarget(targetNav?.down);
          if (targetDownConfig?.target === "last") {
            nextButton.value = selectedButton.value as Entry;
          }
          targetButton = upConfig.target;
        }

        break;
      }

      case "NDS_DOWN": {
        const downConfig = getNavigationTarget(currentNav.down);
        if (!downConfig) return;

        if (downConfig.target === "last") {
          if (nextButton.value) {
            targetButton = nextButton.value;
          } else {
            const leftConfig = getNavigationTarget(currentNav.left);
            const rightConfig = getNavigationTarget(currentNav.right);
            targetButton = leftConfig?.target ?? rightConfig?.target;
          }
        } else {
          const targetNav = navigation[downConfig.target];
          const targetUpConfig = getNavigationTarget(targetNav?.up);
          if (targetUpConfig?.target === "last") {
            nextButton.value = selectedButton.value as Entry;
          }
          targetButton = downConfig.target;
        }
        break;
      }

      case "NDS_LEFT": {
        const leftConfig = getNavigationTarget(currentNav.left);
        if (!leftConfig) return;

        if (currentNav.horizontalMode === "preview") {
          nextButton.value = leftConfig.target;
        } else {
          targetButton = leftConfig.target;
        }
        break;
      }

      case "NDS_RIGHT": {
        const rightConfig = getNavigationTarget(currentNav.right);
        if (!rightConfig) return;

        if (currentNav.horizontalMode === "preview") {
          nextButton.value = rightConfig.target;
        } else {
          targetButton = rightConfig.target;
        }
        break;
      }

      case "NDS_START":
      case "NDS_A": {
        onButtonClick?.(selectedButton.value);
        break;
      }

      default:
        return;
    }

    if (targetButton) {
      const path = findPath(graph, selectedButton.value, targetButton);
      animateToButton(targetButton, path);
    }
  });

  const select = (targetButton: Entry) => {
    if (disabled?.value) return;

    const path = findPath(graph, selectedButton.value, targetButton);
    animateToButton(targetButton, path);
  };

  return {
    selected: readonly(selectedButton),
    selectorPosition,
    select,
  };
};
