/**
 * Training / style corpus hooks.
 *
 * Endpoints touched (all under /api/training/):
 *   - GET /style-report → the dashboard payload (corpus stats + anatomy
 *     + signature phrases + per-decision contribution)
 *   - GET /corpus → flat list of decisions for the corpus tab / compare tool
 *   - GET /compare?a=UUID&b=UUID → side-by-side comparison
 *   - DELETE /corpus/{id} → remove a decision from the corpus
 *   - POST /api/upload → multipart file → returns sanitized filename
 *   - POST /analyze → proofread + extract metadata for preview
 *   - POST /upload → commit a proofread decision to the corpus (task_id)
 */

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ApiError, apiRequest } from "./client";

export type StyleReport = {
  corpus: {
    decision_count: number;
    total_chars: number;
    avg_chars: number;
    date_range: [string | null, string | null];
    decisions: Array<{
      number: string;
      date: string;
      chars: number;
      subjects: string[];
    }>;
    subject_distribution: Array<{ label: string; count: number }>;
    headline: string;
  };
  anatomy: {
    sections: Array<{
      type: string;
      label: string;
      avg_chars: number;
      pct: number;
      coverage: number;
    }>;
    total_coverage: number;
    headline: string;
  };
  signature_phrases: {
    items: Array<{
      type: string;
      text: string;
      context: string;
      frequency: number;
      examples: string[];
    }>;
    total_decisions: number;
    top_display: string;
    headline: string;
  };
  contribution: {
    growth_curve: Array<{
      decision_number: string;
      date: string;
      cumulative: number;
    }>;
    decision_contributions: unknown[];
    total_patterns: number;
    headline: string;
  };
};

export type CorpusDecision = {
  id: string;
  decision_number: string;
  decision_date: string;
  subject_categories: string[];
  chars: number;
  created_at: string;
  // Enriched metadata (added in the corpus-page upgrade).
  summary: string;
  outcome: string;
  key_principles: string[];
  appeal_subtype: string;
  practice_area: string;
  page_count: number;
  document_id: string | null;
  doc_title: string;
  parties: { appellant: string; respondent: string };
  legal_citation: string;
  lessons_count: number;
};

export type CorpusDecisionPatch = {
  decision_number?: string;
  decision_date?: string;
  subject_categories?: string[];
  summary?: string;
  outcome?: string;
  key_principles?: string[];
  appeal_subtype?: string;
  practice_area?: string;
};

export type CompareResult = {
  a: CompareSide;
  b: CompareSide;
  shared: PatternEntry[];
  only_a: PatternEntry[];
  only_b: PatternEntry[];
};

export type CompareSide = {
  id: string;
  decision_number: string;
  decision_date: string;
  chars: number;
  subjects: string[];
  sections: Array<{ type: string; chars: number }>;
  patterns_count: number;
};

export type PatternEntry = {
  id: string;
  type: string;
  text: string;
  context: string;
};

export const trainingKeys = {
  all: ["training"] as const,
  report: () => [...trainingKeys.all, "style-report"] as const,
  corpus: () => [...trainingKeys.all, "corpus"] as const,
  compare: (a: string, b: string) =>
    [...trainingKeys.all, "compare", a, b] as const,
};

export function useStyleReport() {
  return useQuery({
    queryKey: trainingKeys.report(),
    queryFn: ({ signal }) =>
      apiRequest<StyleReport>("/api/training/style-report", { signal }),
    staleTime: 60_000,
  });
}

export function useCorpus() {
  return useQuery({
    queryKey: trainingKeys.corpus(),
    queryFn: ({ signal }) =>
      apiRequest<CorpusDecision[]>("/api/training/corpus", { signal }),
    staleTime: 60_000,
  });
}

export function useCompare(a: string | null, b: string | null) {
  return useQuery({
    queryKey: trainingKeys.compare(a ?? "", b ?? ""),
    queryFn: ({ signal }) =>
      apiRequest<CompareResult>(
        `/api/training/compare?a=${encodeURIComponent(a!)}&b=${encodeURIComponent(b!)}`,
        { signal },
      ),
    enabled: Boolean(a && b && a !== b),
    staleTime: Infinity,
  });
}

export function useDeleteCorpusEntry() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string) =>
      apiRequest<{ deleted: boolean }>(
        `/api/training/corpus/${encodeURIComponent(id)}`,
        { method: "DELETE" },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: trainingKeys.corpus() });
      qc.invalidateQueries({ queryKey: trainingKeys.report() });
      // LRN-8 (INV-IA2): deleting a corpus row nulls style_corpus_id on any
      // chat that referenced it — refresh the conversation list so those
      // chats no longer show a stale corpus link.
      qc.invalidateQueries({ queryKey: chatKeys.conversations() });
    },
  });
}

// ── Style-agent chat ─────────────────────────────────────────────

export type ChatConversation = {
  id: string;
  title: string;
  style_corpus_id: string | null;
  decision_number: string;
  claude_session_id: string | null;
  message_count: number;
  created_at: string;
  last_message_at: string;
};

export type ChatMessage = {
  id: string;
  role: "user" | "assistant";
  content: string;
  created_at: string;
};

export type ChatHealth = {
  reachable: boolean;
  status?: number;
  url: string;
  error?: string;
};

export const chatKeys = {
  conversations: () => [...trainingKeys.all, "chat", "conversations"] as const,
  conversation: (id: string) =>
    [...trainingKeys.all, "chat", "conversations", id] as const,
  health: () => [...trainingKeys.all, "chat", "health"] as const,
};

export function useChatConversations() {
  return useQuery({
    queryKey: chatKeys.conversations(),
    queryFn: ({ signal }) =>
      apiRequest<ChatConversation[]>("/api/training/chat/conversations", { signal }),
    staleTime: 15_000,
  });
}

export function useChatConversation(convId: string | null) {
  return useQuery({
    queryKey: chatKeys.conversation(convId ?? ""),
    queryFn: ({ signal }) =>
      apiRequest<{ conversation: ChatConversation; messages: ChatMessage[] }>(
        `/api/training/chat/conversations/${encodeURIComponent(convId!)}`,
        { signal },
      ),
    enabled: Boolean(convId),
    staleTime: 5_000,
  });
}

export function useChatHealth() {
  return useQuery({
    queryKey: chatKeys.health(),
    queryFn: ({ signal }) =>
      apiRequest<ChatHealth>("/api/training/chat/health", { signal }),
    staleTime: 30_000,
    retry: false,
  });
}

export function useCreateChat() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (body: { title?: string; style_corpus_id?: string | null }) =>
      apiRequest<ChatConversation>("/api/training/chat/conversations", {
        method: "POST",
        body,
      }),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: chatKeys.conversations() });
    },
  });
}

export function useDeleteChat() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string) =>
      apiRequest<{ deleted: boolean }>(
        `/api/training/chat/conversations/${encodeURIComponent(id)}`,
        { method: "DELETE" },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: chatKeys.conversations() });
    },
  });
}

// ── Curator portrait ──────────────────────────────────────────────

export type CuratorPrompt = {
  content: string;
  filename: string;
  bytes: number;
  last_modified: number;
  gitea_url: string;
};

export type StyleAnalyzerPrompts = {
  analysis_prompt: string;
  single_decision_prompt: string;
  synthesis_prompt: string;
  max_input_tokens: number;
};

export type CuratorFinding = {
  id: string;
  lesson_text: string;
  category: string;
  // review_status replaces the removed applied_to_skill flag (LRN-1/INV-IA3).
  review_status: "proposed" | "approved" | "rejected";
  decision_number: string;
  decision_date: string;
  created_at: string;
};

// One decision_lessons channel split by the INV-LRN1 review gate.
export type LessonChannelStats = {
  total: number;
  approved: number;
  proposed: number;
  rejected: number;
};

// The three honest learning channels that feed the writer (INV-IA2/IA5).
// Earlier this was a single flat count of source='curator' — which nothing
// wrote, so it always read 0 while the panel/methodology channels carried all
// the real learning. See get_curator_stats (web/app.py).
export type CuratorStats = {
  decisions_total: number;
  channels: {
    // A — Claude draft↔final distillation → methodology overrides; flows now.
    distillation: {
      discussion_rules: number;
      transition_phrases: number;
      items_total: number;
      pairs_folded: number;
      pairs_total: number;
    };
    // B — DeepSeek+Gemini panel → decision_lessons; flows when chair-approved.
    panel: LessonChannelStats & {
      decisions: number;
      by_practice_area: Record<string, number>;
    };
    // C — curator agent's qualitative findings → decision_lessons (source='curator').
    curator: LessonChannelStats & {
      decisions_reviewed: number;
    };
  };
  recent_findings: CuratorFinding[];
};

export type CuratorProposalInput = {
  title: string;
  proposed_change: string;
  rationale: string;
};

export type CuratorProposalFile = {
  filename: string;
  bytes: number;
  modified_at: number;
};

export const curatorKeys = {
  prompt: () => [...trainingKeys.all, "curator", "prompt"] as const,
  analyzerPrompt: () => [...trainingKeys.all, "curator", "analyzer-prompt"] as const,
  stats: () => [...trainingKeys.all, "curator", "stats"] as const,
  proposals: () => [...trainingKeys.all, "curator", "proposals"] as const,
};

export function useCuratorPrompt() {
  return useQuery({
    queryKey: curatorKeys.prompt(),
    queryFn: ({ signal }) =>
      apiRequest<CuratorPrompt>("/api/training/curator/prompt", { signal }),
    staleTime: 5 * 60_000,
  });
}

export function useStyleAnalyzerPrompts() {
  return useQuery({
    queryKey: curatorKeys.analyzerPrompt(),
    queryFn: ({ signal }) =>
      apiRequest<StyleAnalyzerPrompts>(
        "/api/training/curator/style-analyzer-prompt",
        { signal },
      ),
    staleTime: 5 * 60_000,
  });
}

export function useCuratorStats() {
  return useQuery({
    queryKey: curatorKeys.stats(),
    queryFn: ({ signal }) =>
      apiRequest<CuratorStats>("/api/training/curator/stats", { signal }),
    staleTime: 60_000,
  });
}

export function useCuratorProposals() {
  return useQuery({
    queryKey: curatorKeys.proposals(),
    queryFn: ({ signal }) =>
      apiRequest<CuratorProposalFile[]>("/api/training/curator/proposals", { signal }),
    staleTime: 30_000,
  });
}

export function useSubmitCuratorProposal() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (body: CuratorProposalInput) =>
      apiRequest<{ saved: boolean; filename: string }>(
        "/api/training/curator/proposals",
        { method: "POST", body },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: curatorKeys.proposals() });
    },
  });
}

// ── Upload flow ──────────────────────────────────────────────────
// Three-step pipeline:
//   1. useUploadFile   → POST /api/upload (multipart)        → { filename }
//   2. useAnalyzeFile  → POST /api/training/analyze (form)   → preview + extracted metadata
//   3. useCommitUpload → POST /api/training/upload (json)    → { task_id }
//      Track task_id via useProgress() from documents.ts.

export type UploadFileResponse = {
  filename: string;       // sanitized, time-prefixed name in UPLOAD_DIR
  original_name: string;
  size: number;
};

export type AnalyzeTrainingResponse = {
  filename: string;
  clean_text: string;
  preview: string;
  decision_number: string;
  decision_date: string;        // ISO YYYY-MM-DD or ""
  subject_categories: string[];
  stats: Record<string, unknown>;
  chars: number;
};

export type CommitTrainingRequest = {
  filename: string;
  decision_number: string;
  decision_date: string;        // YYYY-MM-DD or ""
  subject_categories: string[];
  title?: string;
};

export type CommitTrainingResponse = { task_id: string };

export function useUploadFile() {
  return useMutation({
    mutationFn: async (file: File): Promise<UploadFileResponse> => {
      const fd = new FormData();
      fd.append("file", file);
      const res = await fetch("/api/upload", { method: "POST", body: fd });
      const contentType = res.headers.get("content-type") ?? "";
      const parsed = contentType.includes("application/json")
        ? await res.json().catch(() => null)
        : await res.text().catch(() => null);
      if (!res.ok) {
        throw new ApiError(
          typeof parsed === "object" && parsed && "detail" in parsed
            ? String((parsed as { detail: unknown }).detail)
            : `Upload failed with ${res.status}`,
          res.status,
          parsed,
        );
      }
      return parsed as UploadFileResponse;
    },
  });
}

export function useAnalyzeTraining() {
  return useMutation({
    mutationFn: async (filename: string): Promise<AnalyzeTrainingResponse> => {
      const fd = new FormData();
      fd.append("filename", filename);
      const res = await fetch("/api/training/analyze", {
        method: "POST",
        body: fd,
      });
      const contentType = res.headers.get("content-type") ?? "";
      const parsed = contentType.includes("application/json")
        ? await res.json().catch(() => null)
        : await res.text().catch(() => null);
      if (!res.ok) {
        throw new ApiError(
          typeof parsed === "object" && parsed && "detail" in parsed
            ? String((parsed as { detail: unknown }).detail)
            : `Analyze failed with ${res.status}`,
          res.status,
          parsed,
        );
      }
      return parsed as AnalyzeTrainingResponse;
    },
  });
}

// ── Per-decision lessons ─────────────────────────────────────────

export type DecisionLesson = {
  id: string;
  style_corpus_id: string;
  lesson_text: string;
  category: "style" | "structure" | "lexicon" | "tabular" | "general";
  // "panel:deepseek+gemini" — the two-judge style panel; (string) keeps it open
  // to future panel sources without a type break.
  // "synthesis" — a super-lesson merging overlapping lessons (#158/INV-LRN8);
  // "panel:deepseek+gemini" — the two-judge style panel; (string) keeps it open.
  source: "manual" | "curator" | "chair" | "style_analyzer" | "synthesis" | (string & {});
  // review gate (INV-LRN1/G10): only "approved" lessons flow to the writer.
  // Panel-written lessons start as "proposed" and wait for the chair here.
  // "superseded" — a source lesson merged into a synthesis (#158); kept as
  // provenance, no longer fed to the writer.
  // (applied_to_skill removed — LRN-1/INV-IA3: review_status is the single gate.)
  review_status: "proposed" | "approved" | "rejected" | "superseded";
  created_by: string;
  created_at: string;
  updated_at: string;
  // #158/INV-LRN8: ids of the source lessons a 'synthesis' row merged (provenance).
  synthesized_from?: string[];
};

export type LessonReviewStatus = DecisionLesson["review_status"];

export type LessonCreate = {
  lesson_text: string;
  category?: DecisionLesson["category"];
  source?: DecisionLesson["source"];
};

export type LessonPatch = {
  lesson_text?: string;
  category?: DecisionLesson["category"];
  review_status?: DecisionLesson["review_status"];
};

export const lessonsKeys = {
  forCorpus: (corpusId: string) =>
    [...trainingKeys.all, "lessons", corpusId] as const,
};

export function useCorpusLessons(corpusId: string | null) {
  return useQuery({
    queryKey: lessonsKeys.forCorpus(corpusId ?? ""),
    queryFn: ({ signal }) =>
      apiRequest<DecisionLesson[]>(
        `/api/training/corpus/${encodeURIComponent(corpusId!)}/lessons`,
        { signal },
      ),
    enabled: Boolean(corpusId),
    staleTime: 30_000,
  });
}

export function useAddLesson(corpusId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (body: LessonCreate) =>
      apiRequest<DecisionLesson>(
        `/api/training/corpus/${encodeURIComponent(corpusId)}/lessons`,
        { method: "POST", body },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: lessonsKeys.forCorpus(corpusId) });
      // lessons_count on the corpus row is computed server-side, so
      // invalidate the list too — otherwise the badge stays stale.
      qc.invalidateQueries({ queryKey: trainingKeys.corpus() });
    },
  });
}

export function usePatchLesson(corpusId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ id, patch }: { id: string; patch: LessonPatch }) =>
      apiRequest<{ updated: boolean }>(
        `/api/training/lessons/${encodeURIComponent(id)}`,
        { method: "PATCH", body: patch },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: lessonsKeys.forCorpus(corpusId) });
    },
  });
}

export function useDeleteLesson(corpusId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string) =>
      apiRequest<{ deleted: boolean }>(
        `/api/training/lessons/${encodeURIComponent(id)}`,
        { method: "DELETE" },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: lessonsKeys.forCorpus(corpusId) });
      qc.invalidateQueries({ queryKey: trainingKeys.corpus() });
    },
  });
}

export function usePatchCorpus() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ id, patch }: { id: string; patch: CorpusDecisionPatch }) =>
      apiRequest<{ updated: boolean; id: string }>(
        `/api/training/corpus/${encodeURIComponent(id)}`,
        { method: "PATCH", body: patch },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: trainingKeys.corpus() });
      qc.invalidateQueries({ queryKey: trainingKeys.report() });
    },
  });
}

export function useCommitTrainingUpload() {
  // No onSuccess invalidation here — the row only appears after the
  // background task finishes. The dialog watches useProgress(task_id)
  // and invalidates trainingKeys when status === "completed".
  return useMutation({
    mutationFn: (body: CommitTrainingRequest) =>
      apiRequest<CommitTrainingResponse>("/api/training/upload", {
        method: "POST",
        body,
      }),
  });
}
