import { ref, type Ref } from "vue";
import { apiGet } from "@/api/client";
import type { NoteListResponse } from "@/types/note";

export interface AutocompleteItem {
  label: string;
  value: string;
}

export function useAutocomplete(
  textareaRef: Ref<HTMLTextAreaElement | null>,
  textRef: Ref<string>,
  fetchTags: (q: string) => Promise<string[]>
) {
  const acItems = ref<AutocompleteItem[]>([]);
  const acVisible = ref(false);
  const acIndex = ref(0);
  const acTop = ref(0);
  const acLeft = ref(0);

  let acTriggerStart = -1;
  let acType: "tag" | "wikilink" | null = null;
  let debounceTimer: ReturnType<typeof setTimeout>;

  function detectTrigger() {
    const el = textareaRef.value;
    if (!el) return;
    const cursor = el.selectionStart;
    const text = textRef.value;

    // Check for [[ trigger
    for (let i = cursor - 1; i >= 1; i--) {
      if (text[i] === "\n") break;
      if (text[i - 1] === "[" && text[i] === "[") {
        const query = text.slice(i + 1, cursor);
        if (!/\]/.test(query)) {
          acTriggerStart = i - 1;
          acType = "wikilink";
          debouncedSearch(query);
          return;
        }
        break;
      }
    }

    // Check for # trigger
    for (let i = cursor - 1; i >= 0; i--) {
      const ch = text[i];
      if (ch === "\n" || ch === " " || ch === "\t") break;
      if (ch === "#") {
        // Must be start of line or preceded by whitespace
        if (i === 0 || /\s/.test(text[i - 1])) {
          const query = text.slice(i + 1, cursor);
          if (/^[\w/]*$/.test(query)) {
            acTriggerStart = i;
            acType = "tag";
            debouncedSearch(query);
            return;
          }
        }
        break;
      }
    }

    dismiss();
  }

  function debouncedSearch(query: string) {
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(() => doSearch(query), 200);
  }

  async function doSearch(query: string) {
    if (acType === "tag") {
      try {
        const tags = await fetchTags(query);
        acItems.value = tags.slice(0, 8).map((t) => ({
          label: `#${t}`,
          value: t,
        }));
      } catch (e) {
        console.error("Tag autocomplete fetch failed:", e);
        acItems.value = [];
      }
    } else if (acType === "wikilink") {
      try {
        const data = await apiGet<NoteListResponse>(
          `/api/notes?q=${encodeURIComponent(query)}&limit=8`
        );
        acItems.value = data.notes.map((n) => ({
          label: n.title || "Untitled",
          value: n.title || "Untitled",
        }));
      } catch (e) {
        console.error("Wikilink autocomplete fetch failed:", e);
        acItems.value = [];
      }
    }

    if (acItems.value.length > 0) {
      acIndex.value = 0;
      positionDropdown();
      acVisible.value = true;
    } else if (acType === "tag") {
      // Show "no tags" hint so the user knows the trigger is working
      acItems.value = [{ label: "No tags yet", value: "" }];
      acIndex.value = -1;
      positionDropdown();
      acVisible.value = true;
    } else {
      acVisible.value = false;
    }
  }

  function positionDropdown() {
    const el = textareaRef.value;
    if (!el) return;

    // Use a mirror div to measure cursor position
    const mirror = document.createElement("div");
    const style = getComputedStyle(el);
    const props = [
      "fontFamily",
      "fontSize",
      "fontWeight",
      "lineHeight",
      "letterSpacing",
      "wordSpacing",
      "textIndent",
      "paddingTop",
      "paddingRight",
      "paddingBottom",
      "paddingLeft",
      "borderTopWidth",
      "borderRightWidth",
      "borderBottomWidth",
      "borderLeftWidth",
      "boxSizing",
      "whiteSpace",
      "wordWrap",
      "overflowWrap",
    ] as const;
    mirror.style.position = "absolute";
    mirror.style.visibility = "hidden";
    mirror.style.overflow = "hidden";
    mirror.style.width = style.width;
    for (const prop of props) {
      mirror.style[prop] = style[prop];
    }

    const textBefore = el.value.slice(0, el.selectionStart);
    mirror.textContent = textBefore;
    const span = document.createElement("span");
    span.textContent = "|";
    mirror.appendChild(span);

    document.body.appendChild(mirror);
    const rect = el.getBoundingClientRect();
    const spanRect = span.getBoundingClientRect();
    const mirrorRect = mirror.getBoundingClientRect();

    acTop.value =
      rect.top + (spanRect.top - mirrorRect.top) - el.scrollTop + 20;
    acLeft.value =
      rect.left + (spanRect.left - mirrorRect.left) - el.scrollLeft;

    document.body.removeChild(mirror);
  }

  function accept(index?: number) {
    const el = textareaRef.value;
    if (!el || !acVisible.value) return;

    const idx = index ?? acIndex.value;
    const item = acItems.value[idx];
    if (!item || !item.value) return;

    const cursor = el.selectionStart;
    const text = textRef.value;

    let replacement: string;
    let end = cursor;

    if (acType === "tag") {
      replacement = `#${item.value} `;
    } else {
      replacement = `[[${item.value}]]`;
    }

    textRef.value =
      text.slice(0, acTriggerStart) + replacement + text.slice(end);

    const newPos = acTriggerStart + replacement.length;
    dismiss();

    // Restore cursor position after Vue updates the DOM
    requestAnimationFrame(() => {
      el.setSelectionRange(newPos, newPos);
      el.focus();
    });
  }

  function dismiss() {
    acVisible.value = false;
    acItems.value = [];
    acType = null;
    acTriggerStart = -1;
    clearTimeout(debounceTimer);
  }

  function onKeydown(e: KeyboardEvent): boolean {
    if (!acVisible.value) return false;

    if (e.key === "ArrowDown") {
      e.preventDefault();
      acIndex.value = (acIndex.value + 1) % acItems.value.length;
      return true;
    }
    if (e.key === "ArrowUp") {
      e.preventDefault();
      acIndex.value =
        (acIndex.value - 1 + acItems.value.length) % acItems.value.length;
      return true;
    }
    if (e.key === "Tab") {
      e.preventDefault();
      // Single match: accept immediately. Multiple: cycle then accept on second Tab.
      if (acItems.value.length === 1) {
        accept();
      } else {
        acIndex.value = (acIndex.value + 1) % acItems.value.length;
      }
      return true;
    }
    if (e.key === "Enter") {
      e.preventDefault();
      accept();
      return true;
    }
    if (e.key === "Escape") {
      e.preventDefault();
      dismiss();
      return true;
    }
    return false;
  }

  return {
    acItems,
    acVisible,
    acIndex,
    acTop,
    acLeft,
    detectTrigger,
    accept,
    dismiss,
    onKeydown,
  };
}
