import { useCallback, useRef } from 'react';
import { useSessionState } from 'hooks/useSessionState';

/** How many prompts back you can reach. A recall list, not an archive — the transcript is the archive. */
const LIMIT = 10;

/**
 * Per TAB, and shared by every chat in it.
 *
 * Shell-like rather than conversation-like, deliberately: the prompt you most want to reach for is often
 * the one you just sent somewhere else — re-asking in a fresh chat, or in the other panel. Scoping it per
 * session would empty the history exactly when a new chat makes it most useful.
 *
 * sessionStorage rather than memory, so a reload does not drop it, and rather than localStorage, so two
 * windows keep their own — the same split `useSessionState` exists for.
 */
const HISTORY_KEY = 'CHAT_PROMPT_HISTORY';

export type PromptHistory = {
  /** Remember a prompt that was just sent, and leave navigation at the newest end. */
  record: (text: string) => void;
  /**
   * Step back (`-1`) or forward (`+1`), given what is in the composer now. Returns the text to show, or
   * `null` when there is nowhere to go — the caller leaves the composer untouched and lets the key do
   * whatever it normally does.
   */
  step: (direction: -1 | 1, current: string) => string | null;
};

export function usePromptHistory(): PromptHistory {
  // Newest first: index 0 is the last thing sent, which is what the first press of Up should reach.
  const [entries, setEntries] = useSessionState<string[]>(HISTORY_KEY, []);

  // Refs, not state: stepping must read the position set by the press before it, and a re-render in
  // between is not guaranteed. Nothing renders from either.
  //
  // `null` means "composing" — not in the history. That is distinct from index 0, which is the newest
  // entry, and the difference is what lets Down at the newest end return to the draft.
  const indexRef = useRef<number | null>(null);
  const draftRef = useRef('');

  // The ref is what `step` reads, and `record` writes it BEFORE the state — so a prompt sent and recalled
  // in the same tick, with no render in between, is already there. Reading the state alone made that
  // sequence walk the list as it was one prompt ago. The render-time assignment is still what carries a
  // sibling panel's sends into this instance, since they arrive as a new `entries` and nothing else.
  const entriesRef = useRef(entries);
  entriesRef.current = entries;

  const record = useCallback(
    (text: string) => {
      indexRef.current = null;
      draftRef.current = '';
      if (!text.trim()) return;
      // Drop an identical earlier copy rather than keeping both: re-sending the same prompt is common, and
      // a history holding it twice costs a slot and two presses to get past.
      const deduped = entriesRef.current.filter((entry) => entry !== text);
      const next = [text, ...deduped].slice(0, LIMIT);
      entriesRef.current = next;
      setEntries(next);
    },
    [setEntries],
  );

  const step = useCallback((direction: -1 | 1, current: string): string | null => {
    const list = entriesRef.current;
    if (list.length === 0) return null;
    const at = indexRef.current;

    if (direction === -1) {
      // Entering the history stashes whatever was being composed, so Down can hand it back.
      if (at === null) {
        draftRef.current = current;
        indexRef.current = 0;
        return list[0] ?? null;
      }
      if (at >= list.length - 1) return null; // already at the oldest — let the key move the caret
      indexRef.current = at + 1;
      return list[at + 1] ?? null;
    }

    if (at === null) return null; // not in the history; Down is just a caret move
    if (at === 0) {
      indexRef.current = null;
      return draftRef.current;
    }
    indexRef.current = at - 1;
    return list[at - 1] ?? null;
  }, []);

  return { record, step };
}
