import { useCallback, useEffect, useRef } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import { useWorkspace, usePanelConfig } from '../../components/Workspace';

/** What `/chat/agent-panels` returns — the row minus the bearer token and the user id. */
export type AgentPanelView = {
  id: number;
  dashboardId: string;
  panelId: string;
  name: string;
  sessionKey: string;
  cwd: string | null;
  rolePrompt: string | null;
  introducedAt: string | null;
  createdAt: string;
  updatedAt: string;
};

type AgentPanelConfig = { agentName?: string };

/**
 * Resolve which named agent, if any, this panel is.
 *
 * **The panel's memory is its name, not its id.** `movePanel` treats panel ids as positions in the
 * layout tree and moves *contents* between them, so dragging a panel across the dashboard gives it a
 * fresh id — an id-based lookup forgets the agent the first time the owner rearranges anything. So the
 * name lives in the panel's own `config` (which travels with the contents through swap and drag) and
 * the server row is found by that name. The row's `panelId` is bookkeeping; when it disagrees with
 * where the panel actually is, we re-anchor it rather than trust it.
 *
 * Only meaningful on a real dashboard. The fixed screens (`screens/chat`, `screens/email`, …) are not
 * places you assemble a team, so the query never runs there and every panel stays an anonymous chat —
 * exactly as before this existed.
 */
export function useAgentPanel(panelId: string) {
  const { workspace, cwd } = useWorkspace();
  const [config, setConfig] = usePanelConfig<AgentPanelConfig>(panelId);
  const agentName = config?.agentName;

  // The address book is keyed on the workspace *key*, which is what the row stores. Only a real dashboard
  // is addressable: a screen is not a place you assemble a team, and an unrecognised key is not a place at
  // all — `parseWorkspaceKey` returns null for it rather than calling it a dashboard, as the old
  // `!startsWith('screens/')` test did.
  const dashboardKey = workspace?.kind === 'dashboard' ? workspace.key : null;
  const addressable = dashboardKey !== null;

  // `useClient()` rebuilds its verbs on every render, so a verb in a dependency list changes identity
  // every render and silently re-runs whatever depends on it. Hold it in a ref instead.
  const client = useClient();
  const clientRef = useRef(client);
  clientRef.current = client;

  const queryClient = useQueryClient();
  const queryKey = ['agent-panels', dashboardKey];

  const { data, isLoading } = useQuery({
    queryKey,
    queryFn: () =>
      clientRef.current.get<{ agents: AgentPanelView[] }>(
        `/chat/agent-panels?dashboardId=${encodeURIComponent(dashboardKey ?? '')}`,
      ),
    enabled: addressable,
    staleTime: 30_000,
  });

  const agents = data?.agents ?? [];
  const agent = agentName ? agents.find((a) => a.name === agentName) : undefined;

  // Named, but no such row: the agent was deleted from another panel or another tab. Say so rather than
  // silently opening an anonymous chat — the panel would look identical while pointing at nothing.
  const orphaned = !!agentName && !isLoading && !!data && !agent;

  // The panel moved. Tell the server where it lives now — the row's `panelId` is how a future GET, and
  // anything else keyed on position, finds it.
  const reanchoredRef = useRef<string | null>(null);
  useEffect(() => {
    if (!agent || agent.panelId === panelId) return;
    if (reanchoredRef.current === `${agent.id}:${panelId}`) return;
    reanchoredRef.current = `${agent.id}:${panelId}`;
    void clientRef.current
      .patch<{ agent: AgentPanelView }>(`/chat/agent-panels/${agent.id}`, { panelId })
      .then(() => queryClient.invalidateQueries({ queryKey: ['agent-panels', dashboardKey] }))
      .catch(() => {
        // Non-fatal: the panel already knows its own name, which is the address that matters. Allow a
        // later render to try again rather than pinning the failed attempt.
        reanchoredRef.current = null;
      });
  }, [agent?.id, agent?.panelId, panelId, dashboardKey, queryClient]);

  const claim = useMutation({
    mutationFn: (input: { name: string; cwd?: string; rolePrompt?: string }) => {
      // Reject a name already on this dashboard before asking the server. The uniqueness that matters is
      // the server's — an address that resolved to two agents would make a handoff ambiguous — but the
      // list is already in hand, and the answer here is a sentence the human can act on.
      if (agents.some((a) => a.name === input.name)) {
        return Promise.reject(new Error(`There is already an agent named "${input.name}" on this dashboard`));
      }
      return clientRef.current.post<{ agent: AgentPanelView; created: boolean }>('/chat/agent-panels', {
        dashboardId: dashboardKey,
        panelId,
        // Record the directory the panel is already scoped to. A row with no cwd runs incoming handoffs
        // in the owner's home while the human's own turns run in the dashboard's directory — the same
        // agent working in two places depending on who spoke to it.
        ...(cwd && cwd !== '~' ? { cwd } : {}),
        ...input,
      });
    },
    onSuccess: ({ agent: created }) => {
      // Write the name into the panel before invalidating: the config is what survives a drag, and a
      // refetch that landed first would show an agent this panel does not yet claim.
      setConfig({ agentName: created.name });
      void queryClient.invalidateQueries({ queryKey: ['agent-panels', dashboardKey] });
    },
  });

  // Forget the name locally without deleting the agent. The session and its transcript are untouched —
  // another panel can claim the same name back and pick the conversation up where it stopped.
  const release = useCallback(() => setConfig(undefined), [setConfig]);

  return {
    addressable,
    agent,
    agents,
    orphaned,
    isLoading: addressable && isLoading,
    claim,
    release,
  };
}

export type UseAgentPanelType = ReturnType<typeof useAgentPanel>;
