import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useCallback } from 'react';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';

const SESSIONS_KEY = 'CLAUDE_SESSIONS';

/** The conversation a session continues. Server-inferred — see `applyLineage` in claude-sessions.ts. */
export type ClaudeSessionOrigin = { id: string; title: string };

export type ClaudeSessionSummary = {
  id: string; // Claude session uuid (= transcript filename)
  title: string;
  cwd: string;
  createdAt: string;
  updatedAt: string;
  messageCount: number;
  harness?: 'claude' | 'opencode'; // which harness produced it (absent = claude)
  /** This conversation was opened by `/clear`ing another one. */
  bornFromClear?: boolean;
  /**
   * The conversation this one continues, when the server could identify it. Always absent on a listed
   * row — the list merges a chain into one row, so there is no earlier row left to point at. It still
   * carries the inference that made the merge possible.
   */
  continuedFrom?: ClaudeSessionOrigin | null;
  /**
   * Transcripts behind this row. `> 1` means a `/clear` chain the server has merged into one
   * conversation; absent or 1 is the ordinary case.
   */
  partCount?: number;
};

// Display-ready message, matching the frontend ChatMessage union (rebuilt from Claude's transcript).
export type ClaudeSessionMessage =
  | { role: 'user'; text: string }
  | { role: 'assistant'; id: string; text: string }
  | {
      role: 'tool';
      toolName: string;
      toolInput: Record<string, unknown>;
      toolCallId: string;
      output?: string;
      isError?: boolean;
    }
  /** The seam between two parts of a merged `/clear` chain. `sessionId` is the part that ends there. */
  | { role: 'divider'; sessionId: string }
  /** A turn that was stopped. Claude files its own marker as a user message; the server unpicks it. */
  | { role: 'interrupted' };

export type ClaudeSessionDetail = {
  id: string;
  model: string;
  cwd: string;
  messages: ClaudeSessionMessage[];
  total: number; // full transcript length (the loaded messages may be a tail window)
  offset: number; // absolute index of messages[0]; older messages remain above when > 0
  /** The title the list shows this session under, so the open conversation is labelled the same way. */
  title?: string | null;
  /** Transcripts spliced into `messages` — `> 1` when this is a merged `/clear` chain. */
  partCount?: number;
};

export type ClaudePwd = { cwd: string; sessionCount: number; updatedAt: string; isDefault: boolean };

// Query string for the selected working directory (null/undefined = the caller's own home).
const cwdQuery = (cwd?: string | null) => (cwd ? `?cwd=${encodeURIComponent(cwd)}` : '');

/** The default /chat dir plus every directory that already has Claude sessions. */
export function useChatPwds() {
  const client = useClient();
  const { isAuthenticated } = useAuth();
  const { data } = useQuery<{ pwds: ClaudePwd[]; default: string }>({
    queryKey: ['CHAT_PWDS'],
    enabled: isAuthenticated,
    queryFn: () => client.get<{ pwds: ClaudePwd[]; default: string }>('/chat/pwds'),
    staleTime: 30 * 1000,
  });
  return { pwds: data?.pwds ?? [], defaultCwd: data?.default ?? null };
}

/** Sessions for a working directory, read from Claude's own transcript store (source of truth). */
export function useClaudeSessions(cwd?: string | null) {
  const client = useClient();
  const queryClient = useQueryClient();
  const { isAuthenticated } = useAuth();
  const q = cwdQuery(cwd);

  const { data, isLoading, error, refetch } = useQuery<{ sessions: ClaudeSessionSummary[] }>({
    queryKey: [SESSIONS_KEY, cwd ?? 'default'],
    enabled: isAuthenticated,
    queryFn: () => client.get<{ sessions: ClaudeSessionSummary[] }>(`/chat/sessions${q}`),
    staleTime: 30 * 1000,
  });

  const loadSession = (id: string) => client.get<ClaudeSessionDetail>(`/chat/sessions/${id}${q}`);

  // Invalidate every cwd's list (also refreshes pwd counts) — cheap and avoids stale lists.
  const invalidate = useCallback(() => {
    queryClient.invalidateQueries({ queryKey: [SESSIONS_KEY] });
    queryClient.invalidateQueries({ queryKey: ['CHAT_PWDS'] });
  }, [queryClient]);

  const deleteSession = useCallback(
    async (id: string) => {
      await client.delete(`/chat/sessions/${id}${q}`);
      invalidate();
    },
    [client, q, invalidate],
  );

  const renameSession = useCallback(
    async (id: string, title: string) => {
      await client.patch(`/chat/sessions/${id}/title${q}`, { title });
      invalidate();
    },
    [client, q, invalidate],
  );

  return {
    sessions: data?.sessions ?? [],
    isLoading,
    // Surfaced so the list can distinguish "no sessions" from "the read failed" — they rendered
    // identically before, and the second one is the only one you can act on.
    error,
    refetch,
    loadSession,
    deleteSession,
    renameSession,
    invalidate,
  };
}
