import { useCallback, useEffect } from "react";

const NAV_ATTR = "data-nav-id";
const NAV_ACTION_ATTR = "data-nav-action";

export type NavAction = "toggle" | "expand" | "collapse";

function findNavItems(container: HTMLElement): HTMLElement[] {
  return Array.from(container.querySelectorAll<HTMLElement>(`[${NAV_ATTR}]`));
}

function isFocusTarget(node: EventTarget | null): node is HTMLElement {
  return node instanceof HTMLElement;
}

function findClosestNavItem(target: HTMLElement, container: HTMLElement): HTMLElement | null {
  let el: HTMLElement | null = target;
  while (el !== null && el !== container) {
    if (el.hasAttribute(NAV_ATTR)) return el;
    el = el.parentElement;
  }
  if (target.hasAttribute(NAV_ATTR)) return target;
  return null;
}

function getAction(el: HTMLElement): NavAction | null {
  const val = el.getAttribute(NAV_ACTION_ATTR);
  if (val === "toggle" || val === "expand" || val === "collapse") return val;
  return null;
}

function focusAndScroll(el: HTMLElement): void {
  el.focus({ preventScroll: true });
  el.scrollIntoView({ block: "nearest", behavior: "smooth" });
}

function safeItemAt(items: HTMLElement[], index: number): HTMLElement | null {
  const el = items[index];
  return el ?? null;
}

function isEditableTarget(target: HTMLElement): boolean {
  const tag = target.tagName;
  if (tag === "INPUT" || tag === "TEXTAREA") return true;
  if (target.isContentEditable) return true;
  return false;
}

/**
 * Enables keyboard navigation within a log-list container.
 *
 * Navigation keys:
 * - ArrowUp / Shift+W : previous item
 * - ArrowDown / Shift+S : next item
 * - ArrowLeft / Shift+A : collapse or move to prev item
 * - ArrowRight / Shift+D : expand or move to next item
 * - Space : toggle expand/collapse
 *
 * Attach `data-nav-id` to any focusable navigation item.
 * Attach `data-nav-action` ("toggle" | "expand" | "collapse") for items
 * that support expand/collapse.
 */
export function useKeyboardNavigation(
  /** Ref to the inner container that holds all nav items. */
  containerRef: React.RefObject<HTMLElement | null>,
  /** Ref to the outer focus-receiving wrapper (the one with tabIndex).
   *  Defaults to `containerRef` when omitted. */
  wrapperRef?: React.RefObject<HTMLElement | null>,
): void {
  const rootRef = wrapperRef ?? containerRef;

  const handleFocusContainer = useCallback(
    (e: FocusEvent) => {
      const container = containerRef.current;
      if (!container) return;
      const target = e.target;
      if (!isFocusTarget(target)) return;
      if (target !== rootRef.current) return;
      if (!container.contains(target)) return;
      const items = findNavItems(container);
      const first = safeItemAt(items, 0);
      if (first !== null) focusAndScroll(first);
    },
    [containerRef, rootRef],
  );

  useEffect(() => {
    const root = rootRef.current;
    const container = containerRef.current;
    if (!root || !container) return;

    const handleKeyDown = (e: KeyboardEvent) => {
      const target = e.target;
      if (!isFocusTarget(target)) return;

      if (!container.contains(target)) return;

      if (isEditableTarget(target)) return;

      const items = findNavItems(container);
      if (items.length === 0) return;

      const current = findClosestNavItem(target, container);
      const currentIdx = current !== null ? items.indexOf(current) : -1;

      let handled = false;

      switch (e.key) {
        case "ArrowUp":
        case "W": {
          if (e.key === "W" && !e.shiftKey) break;
          e.preventDefault();
          const prevIdx =
            currentIdx > 0 ? currentIdx - 1 : currentIdx === -1 ? items.length - 1 : -1;
          if (prevIdx !== -1) {
            const prev = safeItemAt(items, prevIdx);
            if (prev !== null) focusAndScroll(prev);
          }
          handled = true;
          break;
        }
        case "ArrowDown":
        case "S": {
          if (e.key === "S" && !e.shiftKey) break;
          e.preventDefault();
          const nextIdx =
            currentIdx < items.length - 1 ? currentIdx + 1 : currentIdx === -1 ? 0 : -1;
          if (nextIdx !== -1) {
            const next = safeItemAt(items, nextIdx);
            if (next !== null) focusAndScroll(next);
          }
          handled = true;
          break;
        }
        case "ArrowLeft":
        case "A": {
          if (e.key === "A" && !e.shiftKey) break;
          if (current === null) break;
          e.preventDefault();
          const action = getAction(current);
          if (action === "collapse") {
            current.click();
          } else if (currentIdx > 0) {
            const prev = safeItemAt(items, currentIdx - 1);
            if (prev !== null) focusAndScroll(prev);
          }
          handled = true;
          break;
        }
        case "ArrowRight":
        case "D": {
          if (e.key === "D" && !e.shiftKey) break;
          if (current === null) break;
          e.preventDefault();
          const action = getAction(current);
          if (action === "expand") {
            current.click();
          } else if (currentIdx < items.length - 1) {
            const next = safeItemAt(items, currentIdx + 1);
            if (next !== null) focusAndScroll(next);
          }
          handled = true;
          break;
        }
        case " ": {
          if (current === null) break;
          e.preventDefault();
          const action = getAction(current);
          if (action !== null) current.click();
          handled = true;
          break;
        }
      }

      if (handled) {
        e.stopPropagation();
      }
    };

    document.addEventListener("keydown", handleKeyDown, { capture: true });
    return () => document.removeEventListener("keydown", handleKeyDown, { capture: true });
  }, [containerRef, rootRef]);

  useEffect(() => {
    const root = rootRef.current;
    if (!root) return;
    root.addEventListener("focus", handleFocusContainer);
    return () => root.removeEventListener("focus", handleFocusContainer);
  }, [handleFocusContainer, rootRef]);
}
