import { useMemo, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import type { ChatMessage } from './types';

export type BackgroundTask = Extract<ChatMessage, { role: 'task' }>;

/**
 * The background tasks of a conversation, lifted out of the transcript.
 *
 * They already exist as `role: 'task'` rows in `messages` — but a row scrolls away, and a task that was
 * started three screens ago is exactly the one you want to keep an eye on. This reads the same rows and
 * hands them to a tray that stays put above the input.
 *
 * Finished tasks stay in the tray until dismissed rather than vanishing on completion: the notification
 * lands while you are reading something else, and a chip that removes itself the instant it succeeds is a
 * chip you never saw. They do not stay forever, though — see FINISHED_KEPT.
 *
 * NEWEST FIRST. The tray is a horizontal strip, so the far end of it is off-screen, and the task you want
 * is almost always the one that just started. Chronological order put it exactly where you could not see
 * it without scrolling.
 */

/**
 * How many finished tasks the tray keeps behind the running ones.
 *
 * Enough that a notification you missed is still there when you look up, few enough that the strip does
 * not become a scrolling history of the whole conversation. Running tasks are never counted or dropped:
 * the tray's job is watching them, and a busy session could legitimately have more than this many.
 */
const FINISHED_KEPT = 5;

export function useBackgroundTasks(messages: ChatMessage[]) {
  const [dismissed, setDismissed] = useState<string[]>([]);
  const [pinnedIds, setPinnedIds] = useState<string[]>([]);

  const tasks = useMemo(() => {
    const byId = new Map<string, BackgroundTask>();
    for (const m of messages) if (m.role === 'task') byId.set(m.taskId, m);
    return [...byId.values()].reverse();
  }, [messages]);

  const visible = useMemo(() => {
    let finished = 0;
    // `tasks` is already newest-first, so counting forwards drops the oldest finished chips and keeps the
    // ones whose notifications are still recent enough to be worth reading.
    return tasks.filter((t) => {
      if (dismissed.includes(t.taskId)) return false;
      // A pin is an explicit "keep this", so it outranks the cap — otherwise pinning the one task you care
      // about would not stop five newer ones from pushing it out.
      if (!t.status || pinnedIds.includes(t.taskId)) return true;
      finished += 1;
      return finished <= FINISHED_KEPT;
    });
  }, [tasks, dismissed, pinnedIds]);

  const running = visible.filter((t) => !t.status);
  const pinned = visible.filter((t) => pinnedIds.includes(t.taskId));
  const unpinned = visible.filter((t) => !pinnedIds.includes(t.taskId));

  // Uncapped on purpose: clearing the finished chips must also clear the ones the cap is hiding behind
  // them, or the bulk clear just deals the next five off the same deck.
  const clearable = tasks.filter((t) => t.status && !pinnedIds.includes(t.taskId));

  return {
    tasks: visible,
    /** Raised to their own row above the rest, in the same newest-first order. */
    pinned,
    unpinned,
    running,
    clearable,
    /** Pinning survives the task finishing — you pinned it to watch it, and the outcome is the point. */
    togglePin: (taskId: string) =>
      setPinnedIds((prev) => (prev.includes(taskId) ? prev.filter((id) => id !== taskId) : [...prev, taskId])),
    /**
     * Close one chip. Only ever called for a finished task — running ones are not dismissable, because the
     * tray is the only thing watching them and a hidden running task is one you have no way back to.
     *
     * Dismissing the newest of a capped set promotes the next-oldest into view rather than leaving a gap:
     * `FINISHED_KEPT` is how many finished chips are shown, not which ones survive.
     */
    dismiss: (taskId: string) => {
      setPinnedIds((prev) => prev.filter((id) => id !== taskId));
      setDismissed((prev) => (prev.includes(taskId) ? prev : [...prev, taskId]));
    },
    /** Clear every finished chip at once, pinned ones excepted. Additive, so closed chips stay closed. */
    dismissFinished: () => setDismissed((prev) => [...new Set([...prev, ...clearable.map((t) => t.taskId)])]),
  };
}

export type TaskDetail =
  | { kind: 'pending' }
  | { kind: 'agent'; messages: ChatMessage[] }
  | { kind: 'log'; text: string; truncated: boolean };

/**
 * What one task is doing, from the file Claude Code streams it into. Polled while the task is running and
 * fetched once more after it finishes, so the panel settles on the final state instead of the last poll.
 */
export function useTaskDetail(taskId: string | null, running: boolean) {
  const { get } = useClient();
  return useQuery({
    // `running` is part of the key on purpose. Polling stops the moment a task finishes, and the last poll
    // fired up to two seconds before the end — so without a key change the panel would keep showing a
    // trace that stops just short of the last thing the task did.
    queryKey: ['chat', 'task', taskId, running],
    queryFn: () => get<TaskDetail>(`/chat/tasks/${taskId}`),
    enabled: !!taskId,
    refetchInterval: running ? 2000 : false,
    staleTime: 0,
  });
}
