import { create } from "@bufbuild/protobuf";
import { timestampDate } from "@bufbuild/protobuf/wkt";
import { useCallback, useEffect, useRef, useState } from "react";
import getCaretCoordinates from "textarea-caret";
import { cn } from "@/lib/utils";
import useCurrentUser from "@/hooks/useCurrentUser";
import { extractUserIdFromName } from "@/helpers/resource-names";
import { memoServiceClient } from "@/service";
import { type Memo, MemoRelation_MemoSchema, MemoRelation_Type, MemoRelationSchema } from "@/types/proto/api/v1/memo_service_pb";
import type { TagSuggestionsProps } from "../types";
import { useEditorContext } from "../state";

/**
 * BacklinkSuggestions — triggered by typing [[
 * Searches existing memos via API and creates a MemoRelation (REFERENCE) on selection.
 * The linked memo appears in the RelationList section of both memos.
 */
export default function BacklinkSuggestions({ editorRef, editorActions }: TagSuggestionsProps) {
  const user = useCurrentUser();
  const { state, actions, dispatch } = useEditorContext();

  const [position, setPosition] = useState<{ left: number; top: number; height: number } | null>(null);
  const [memos, setMemos] = useState<Memo[]>([]);
  const [selectedIndex, setSelectedIndex] = useState(0);
  const [searchQuery, setSearchQuery] = useState("");
  const [isOpen, setIsOpen] = useState(false);
  const [isFetching, setIsFetching] = useState(false);
  const isProcessingRef = useRef(false);
  const debounceTimerRef = useRef<ReturnType<typeof setTimeout>>();

  // Refs for event handlers (avoid stale closures)
  const memosRef = useRef(memos);
  memosRef.current = memos;
  const selectedRef = useRef(selectedIndex);
  selectedRef.current = selectedIndex;
  const isOpenRef = useRef(isOpen);
  isOpenRef.current = isOpen;

  // Get the current [[ word and its start index
  const getCurrentWord = useCallback((): [string, number] => {
    const editor = editorRef.current;
    if (!editor) return ["", 0];
    const cursorPos = editor.selectionEnd;
    const textBefore = editor.value.slice(0, cursorPos);
    // Look for [[ backwards from cursor
    const bracketIdx = textBefore.lastIndexOf("[[");
    if (bracketIdx === -1) return ["", 0];
    // Check nothing between [[ and cursor is a space (allow searching multi-word)
    const textAfterBracket = textBefore.slice(bracketIdx);
    // Also get text after cursor until whitespace or end
    const textAfterCursor = editor.value.slice(cursorPos).match(/^[^\s\]]*/) || { 0: "" };
    return [textAfterBracket + textAfterCursor[0], bracketIdx];
  }, [editorRef]);

  const hide = useCallback(() => {
    setIsOpen(false);
    setPosition(null);
    setMemos([]);
    setSearchQuery("");
  }, []);

  // Fetch memos from API with debounce
  useEffect(() => {
    if (!isOpen) return;

    if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
    setIsFetching(true);

    debounceTimerRef.current = setTimeout(async () => {
      try {
        const conditions = [`creator_id == ${extractUserIdFromName(user?.name ?? "")}`];
        if (searchQuery) {
          conditions.push(`content.contains("${searchQuery}")`);
        }
        const { memos: fetchedMemos } = await memoServiceClient.listMemos({
          filter: conditions.join(" && "),
        });

        // Filter out memos already in relations
        const existingNames = new Set(state.metadata.relations.map((r) => r.relatedMemo?.name));
        const filtered = fetchedMemos.filter((m) => !existingNames.has(m.name));
        setMemos(filtered);
        setSelectedIndex(0);
      } catch (error) {
        console.error("BacklinkSuggestions: fetch error", error);
        setMemos([]);
      } finally {
        setIsFetching(false);
      }
    }, 300);

    return () => {
      if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
    };
  }, [isOpen, searchQuery, user?.name, state.metadata.relations]);

  // Handle selecting a memo — use ref-based values to avoid stale closures
  const handleSelectMemoRef = useRef<(memo: Memo) => void>();
  handleSelectMemoRef.current = (memo: Memo) => {
    if (!editorActions || !("current" in editorActions) || !editorActions.current) return;

    isProcessingRef.current = true;

    // 1. Create MemoRelation
    const relation = create(MemoRelationSchema, {
      type: MemoRelation_Type.REFERENCE,
      relatedMemo: create(MemoRelation_MemoSchema, {
        name: memo.name,
        snippet: memo.snippet,
      }),
    });
    dispatch(actions.addRelation(relation));

    // 2. Replace the [[ text with a readable reference
    const [word, index] = getCurrentWord();
    const snippet = memo.snippet.slice(0, 30) || "memo";
    const ea = editorActions.current;
    ea.removeText(index, word.length);
    ea.insertText(`📎${snippet} `);

    hide();
    queueMicrotask(() => {
      isProcessingRef.current = false;
    });
  };

  // Handle keyboard and input events
  useEffect(() => {
    const editor = editorRef.current;
    if (!editor) return;

    const handleInput = () => {
      if (isProcessingRef.current) return;

      setSelectedIndex(0);
      const editorEl = editorRef.current;
      if (!editorEl) return;

      const cursorPos = editorEl.selectionEnd;
      const textBefore = editorEl.value.slice(0, cursorPos);
      const bracketIdx = textBefore.lastIndexOf("[[");

      if (bracketIdx !== -1) {
        // Check that there's no ]] between [[ and cursor
        const between = textBefore.slice(bracketIdx + 2);
        if (!between.includes("]]") && !between.includes("\n")) {
          const query = between.toLowerCase();
          setSearchQuery(query);
          const coords = getCaretCoordinates(editorEl, bracketIdx);
          coords.top -= editorEl.scrollTop;
          setPosition(coords);
          setIsOpen(true);
          return;
        }
      }
      // No valid [[ found
      if (isOpenRef.current) {
        setIsOpen(false);
        setPosition(null);
        setMemos([]);
        setSearchQuery("");
      }
    };

    const handleKeyDown = (e: KeyboardEvent) => {
      if (!isOpenRef.current) return;

      const currentMemos = memosRef.current;
      const selected = selectedRef.current;

      if (["Escape", "ArrowLeft", "ArrowRight"].includes(e.code)) {
        setIsOpen(false);
        setPosition(null);
        setMemos([]);
        setSearchQuery("");
        return;
      }

      if (e.code === "ArrowDown" && currentMemos.length > 0) {
        setSelectedIndex((selected + 1) % currentMemos.length);
        e.preventDefault();
        e.stopPropagation();
        return;
      }
      if (e.code === "ArrowUp" && currentMemos.length > 0) {
        setSelectedIndex((selected - 1 + currentMemos.length) % currentMemos.length);
        e.preventDefault();
        e.stopPropagation();
        return;
      }

      if (["Enter", "Tab"].includes(e.code) && currentMemos.length > 0) {
        handleSelectMemoRef.current?.(currentMemos[selected]);
        e.preventDefault();
        e.stopImmediatePropagation();
      }
    };

    const handleHide = () => {
      setIsOpen(false);
      setPosition(null);
      setMemos([]);
      setSearchQuery("");
    };

    editor.addEventListener("input", handleInput);
    editor.addEventListener("keydown", handleKeyDown);
    editor.addEventListener("click", handleHide);
    editor.addEventListener("blur", handleHide);

    return () => {
      editor.removeEventListener("input", handleInput);
      editor.removeEventListener("keydown", handleKeyDown);
      editor.removeEventListener("click", handleHide);
      editor.removeEventListener("blur", handleHide);
    };
  }, [editorRef]);

  if (!isOpen || !position) return null;

  return (
    <div
      className="z-20 absolute p-1 mt-1 -ml-2 max-w-72 max-h-60 rounded border bg-popover text-popover-foreground shadow-lg flex flex-col overflow-y-auto overflow-x-hidden"
      style={{ left: position.left, top: position.top + position.height }}
    >
      {isFetching && memos.length === 0 && (
        <div className="p-2 text-sm text-muted-foreground">Đang tìm...</div>
      )}
      {!isFetching && memos.length === 0 && (
        <div className="p-2 text-sm text-muted-foreground">Không tìm thấy memo</div>
      )}
      {memos.map((memo, i) => (
        <div
          key={memo.name}
          onMouseDown={(e) => {
            e.preventDefault();
            handleSelectMemoRef.current?.(memo);
          }}
          className={cn(
            "rounded p-1.5 px-2 w-full text-sm cursor-pointer transition-colors select-none hover:bg-accent hover:text-accent-foreground",
            i === selectedIndex && "bg-accent text-accent-foreground",
          )}
        >
          <div className="flex flex-col gap-0.5">
            <span className="text-xs text-muted-foreground">
              {memo.displayTime && timestampDate(memo.displayTime).toLocaleDateString()}
            </span>
            <span className="line-clamp-2 leading-tight">{memo.snippet || memo.content.slice(0, 50)}</span>
          </div>
        </div>
      ))}
    </div>
  );
}
