/**
 * External Precedent Library hooks.
 *
 * The library is the authoritative case-law corpus — chair-uploaded
 * court rulings + other appeals committee decisions, with halachot
 * extracted automatically and queued for chair approval. Distinct from:
 *   - /api/training (Daphna's style corpus — sample decisions for tone)
 *   - /api/precedents (chair-attached quotes per case section)
 *
 * Endpoints touched (all under /api/precedent-library and /api/halachot):
 *   - POST /upload (multipart) → task_id (consumed by useProgress)
 *   - GET / (filters) → list
 *   - GET /{id} → detail with halachot
 *   - PATCH /{id} → metadata edit
 *   - DELETE /{id} → remove
 *   - POST /{id}/extract-halachot → re-run halacha extractor
 *   - GET /search → semantic search (halachot + chunks)
 *   - GET /stats
 *   - GET /api/halachot?status=... → review queue
 *   - PATCH /api/halachot/{id} → approve/reject/edit
 */

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

export type PracticeArea =
  | ""
  | "rishuy_uvniya"
  | "betterment_levy"
  | "compensation_197";

export type SourceType = "" | "court_ruling" | "appeals_committee";

export type Precedent = {
  id: string;
  case_number: string;
  case_name: string;
  court: string;
  date: string | null;
  practice_area: PracticeArea | "";
  appeal_subtype: string;
  source_type: SourceType | "";
  precedent_level: string;
  is_binding: boolean;
  summary: string;
  headnote: string;
  subject_tags: string[];
  source_kind: string;
  chair_name: string | null;
  district: string | null;
  citation_formatted: string;
  extraction_status: string;
  halacha_extraction_status: string;
  metadata_extraction_requested_at: string | null;
  halacha_extraction_requested_at: string | null;
  created_at: string;
  halachot_count: number;
  approved_count: number;
};

export type Halacha = {
  id: string;
  case_law_id: string;
  halacha_index: number;
  rule_statement: string;
  rule_type: string;
  reasoning_summary: string;
  supporting_quote: string;
  page_reference: string;
  practice_areas: string[];
  subject_tags: string[];
  cites: string[];
  confidence: number;
  quote_verified: boolean;
  review_status: "pending_review" | "approved" | "rejected" | "published";
  reviewer: string;
  reviewed_at: string | null;
  created_at: string;
  updated_at: string;
  /* Joined from case_law for review/list views */
  case_number?: string;
  case_name?: string;
  court?: string;
  decision_date?: string | null;
  precedent_level?: string;
};

export type RelatedCase = {
  id: string;
  case_number: string;
  case_name: string;
  court: string;
  precedent_level: string;
  date: string | null;
  relation_type: string;
};

export type PrecedentDetail = Precedent & {
  full_text: string;
  halachot: Halacha[];
  related_cases: RelatedCase[];
};

export type SearchHit =
  | {
      type: "halacha";
      score: number;
      halacha_id: string;
      case_law_id: string;
      rule_statement: string;
      reasoning_summary: string;
      supporting_quote: string;
      page_reference: string;
      practice_areas: string[];
      subject_tags: string[];
      confidence: number;
      rule_type: string;
      case_number: string;
      case_name: string;
      court: string;
      decision_date: string | null;
      precedent_level: string;
    }
  | {
      type: "passage";
      score: number;
      chunk_id: string;
      case_law_id: string;
      content: string;
      section_type: string;
      page_number: number | null;
      case_number: string;
      case_name: string;
      court: string;
      decision_date: string | null;
      precedent_level: string;
      practice_area: string;
    };

export type LibraryStats = {
  precedents_total: number;
  by_practice_area: { practice_area: string; count: number }[];
  by_precedent_level: { precedent_level: string; count: number }[];
  halachot_total: number;
  halachot_pending: number;
  halachot_approved: number;
};

export type ListFilters = {
  practiceArea?: PracticeArea;
  court?: string;
  precedentLevel?: string;
  sourceType?: SourceType;
  sourceKind?: string;
  search?: string;
  limit?: number;
  offset?: number;
};

export const libraryKeys = {
  all: ["precedent-library"] as const,
  list: (filters: ListFilters) =>
    [...libraryKeys.all, "list", filters] as const,
  detail: (id: string) => [...libraryKeys.all, "detail", id] as const,
  search: (q: string, filters: Record<string, string | boolean>) =>
    [...libraryKeys.all, "search", q, filters] as const,
  stats: () => [...libraryKeys.all, "stats"] as const,
  halachotPending: () => [...libraryKeys.all, "halachot", "pending"] as const,
  halachot: (filters: Record<string, string>) =>
    [...libraryKeys.all, "halachot", filters] as const,
};

export function usePrecedents(filters: ListFilters = {}) {
  return useQuery({
    queryKey: libraryKeys.list(filters),
    queryFn: ({ signal }) => {
      const p = new URLSearchParams();
      if (filters.practiceArea) p.set("practice_area", filters.practiceArea);
      if (filters.court) p.set("court", filters.court);
      if (filters.precedentLevel) p.set("precedent_level", filters.precedentLevel);
      if (filters.sourceType) p.set("source_type", filters.sourceType);
      if (filters.sourceKind) p.set("source_kind", filters.sourceKind);
      if (filters.search) p.set("search", filters.search);
      if (filters.limit) p.set("limit", String(filters.limit));
      if (filters.offset) p.set("offset", String(filters.offset));
      const qs = p.toString();
      return apiRequest<{ items: Precedent[]; count: number }>(
        `/api/precedent-library${qs ? `?${qs}` : ""}`,
        { signal },
      );
    },
    staleTime: 30_000,
    /* Poll while any row is mid-processing or queued for the local MCP
     * worker. Once everything settles to completed/failed the polling
     * stops on its own — no fixed background timer. */
    refetchInterval: (query) => {
      const data = query.state.data;
      if (!data) return false;
      const active = data.items.some((p) => isPrecedentActive(p));
      return active ? 5000 : false;
    },
  });
}

/** A precedent is "active" while text/halacha extraction is in flight or
 * legitimately queued for the local MCP worker. Used by the auto-refresh
 * poller and by the row UI to disable destructive actions.
 *
 * Once a status is "completed" or "failed", the row is NEVER active —
 * even if the corresponding `*_requested_at` timestamp still has a value.
 * The worker is supposed to NULL it on success but in practice doesn't
 * always, and treating those rows as active leaves them permanently
 * undeletable. */
export function isPrecedentActive(p: Precedent): boolean {
  // Text extraction
  if (p.extraction_status === "processing") return true;

  // Halacha extraction
  if (p.halacha_extraction_status === "processing") return true;
  if (
    p.halacha_extraction_status === "pending" &&
    p.halacha_extraction_requested_at !== null
  ) {
    return true;
  }

  // Metadata extraction has no status column — only the timestamp.
  // Treat as active only when extraction hasn't yet fully completed
  // (otherwise stale timestamps linger after success).
  if (
    p.metadata_extraction_requested_at !== null &&
    p.extraction_status !== "completed"
  ) {
    return true;
  }

  return false;
}

export function usePrecedent(id: string | null) {
  return useQuery({
    queryKey: libraryKeys.detail(id ?? ""),
    queryFn: ({ signal }) =>
      apiRequest<PrecedentDetail>(
        `/api/precedent-library/${encodeURIComponent(id!)}`,
        { signal },
      ),
    enabled: Boolean(id),
    staleTime: 30_000,
  });
}

export function useLibraryStats() {
  return useQuery({
    queryKey: libraryKeys.stats(),
    queryFn: ({ signal }) =>
      apiRequest<LibraryStats>("/api/precedent-library/stats", { signal }),
    staleTime: 60_000,
  });
}

export type SearchFilters = {
  practiceArea?: PracticeArea;
  court?: string;
  precedentLevel?: string;
  appealSubtype?: string;
  subjectTag?: string;
  includeHalachot?: boolean;
  limit?: number;
};

export function useLibrarySearch(query: string, filters: SearchFilters = {}) {
  const params: Record<string, string | boolean> = {};
  if (filters.practiceArea) params.practice_area = filters.practiceArea;
  if (filters.court) params.court = filters.court;
  if (filters.precedentLevel) params.precedent_level = filters.precedentLevel;
  if (filters.appealSubtype) params.appeal_subtype = filters.appealSubtype;
  if (filters.subjectTag) params.subject_tag = filters.subjectTag;
  if (filters.includeHalachot !== undefined)
    params.include_halachot = filters.includeHalachot;

  return useQuery({
    queryKey: libraryKeys.search(query, params),
    queryFn: ({ signal }) => {
      const p = new URLSearchParams({ q: query });
      for (const [k, v] of Object.entries(params)) p.set(k, String(v));
      if (filters.limit) p.set("limit", String(filters.limit));
      return apiRequest<{ items: SearchHit[]; count: number }>(
        `/api/precedent-library/search?${p.toString()}`,
        { signal },
      );
    },
    enabled: query.trim().length >= 2,
    staleTime: 10_000,
    placeholderData: (prev) => prev,
  });
}

export type PrecedentUploadInput = {
  file: File;
  citation: string;
  case_name?: string;
  court?: string;
  decision_date?: string;
  source_type?: SourceType;
  precedent_level?: string;
  practice_area?: PracticeArea;
  appeal_subtype?: string;
  subject_tags?: string[];
  is_binding?: boolean;
  headnote?: string;
  summary?: string;
};

export function useUploadPrecedent() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (input: PrecedentUploadInput) => {
      const fd = new FormData();
      fd.append("file", input.file);
      fd.append("citation", input.citation);
      if (input.case_name) fd.append("case_name", input.case_name);
      if (input.court) fd.append("court", input.court);
      if (input.decision_date) fd.append("decision_date", input.decision_date);
      if (input.source_type) fd.append("source_type", input.source_type);
      if (input.precedent_level)
        fd.append("precedent_level", input.precedent_level);
      if (input.practice_area)
        fd.append("practice_area", input.practice_area);
      if (input.appeal_subtype)
        fd.append("appeal_subtype", input.appeal_subtype);
      if (input.subject_tags && input.subject_tags.length)
        fd.append("subject_tags", JSON.stringify(input.subject_tags));
      fd.append("is_binding", String(input.is_binding ?? true));
      if (input.headnote) fd.append("headnote", input.headnote);
      if (input.summary) fd.append("summary", input.summary);

      const res = await fetch("/api/precedent-library/upload", {
        method: "POST",
        body: fd,
      });
      const parsed = await res.json().catch(() => null);
      if (!res.ok) {
        throw new ApiError(
          `Upload failed with ${res.status}`,
          res.status,
          parsed,
        );
      }
      return parsed as { task_id: string };
    },
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: libraryKeys.all });
    },
  });
}

// Valid Hebrew districts for appeals-committee decisions. Mirrors
// VALID_DISTRICTS in mcp-server/src/legal_mcp/tools/internal_decisions.py —
// keep in sync with the service-side guard.
export const COMMITTEE_DISTRICTS = [
  "ירושלים",
  "תל אביב",
  "מרכז",
  "חיפה",
  "צפון",
  "דרום",
  "ארצי",
] as const;

export type CommitteeDistrict = (typeof COMMITTEE_DISTRICTS)[number];

// A citation that targets internal_decision_upload, not the external library.
// Matches the prefix list in precedent_library service (ערר/בל"מ/ARAR).
const COMMITTEE_PREFIXES = ["ערר ", "ערר(", "בל\"מ ", "בל\"מ(", "ARAR "];

export function isCommitteeCitation(citation: string): boolean {
  const c = citation.trimStart();
  return COMMITTEE_PREFIXES.some((p) => c.startsWith(p));
}

export type InternalDecisionUploadInput = {
  file: File;
  case_number: string;
  chair_name: string;
  district: CommitteeDistrict | string;
  case_name?: string;
  court?: string;
  decision_date?: string;
  practice_area?: PracticeArea;
  appeal_subtype?: string;
  subject_tags?: string[];
  is_binding?: boolean;
  summary?: string;
};

export function useUploadInternalDecision() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (input: InternalDecisionUploadInput) => {
      const fd = new FormData();
      fd.append("file", input.file);
      fd.append("case_number", input.case_number);
      fd.append("chair_name", input.chair_name);
      fd.append("district", input.district);
      if (input.case_name) fd.append("case_name", input.case_name);
      if (input.court) fd.append("court", input.court);
      if (input.decision_date) fd.append("decision_date", input.decision_date);
      if (input.practice_area) fd.append("practice_area", input.practice_area);
      if (input.appeal_subtype)
        fd.append("appeal_subtype", input.appeal_subtype);
      if (input.subject_tags && input.subject_tags.length)
        fd.append("subject_tags", JSON.stringify(input.subject_tags));
      fd.append("is_binding", String(input.is_binding ?? false));
      if (input.summary) fd.append("summary", input.summary);

      const res = await fetch("/api/internal-decisions/upload", {
        method: "POST",
        body: fd,
      });
      const parsed = await res.json().catch(() => null);
      if (!res.ok) {
        throw new ApiError(
          `Upload failed with ${res.status}`,
          res.status,
          parsed,
        );
      }
      return parsed as { task_id: string };
    },
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: libraryKeys.all });
    },
  });
}

export function useDeletePrecedent() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string) =>
      apiRequest<{ deleted: boolean }>(
        `/api/precedent-library/${encodeURIComponent(id)}`,
        { method: "DELETE" },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: libraryKeys.all });
    },
  });
}

export function useLinkRelatedCase(caseId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (vars: { relatedId: string; relationType?: string }) =>
      apiRequest<{ linked: boolean }>(
        `/api/precedent-library/${encodeURIComponent(caseId)}/relations`,
        {
          method: "POST",
          body: {
            related_id: vars.relatedId,
            relation_type: vars.relationType ?? "same_case_chain",
          },
        },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: libraryKeys.detail(caseId) });
    },
  });
}

export function useUnlinkRelatedCase(caseId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (relatedId: string) =>
      apiRequest<{ unlinked: boolean }>(
        `/api/precedent-library/${encodeURIComponent(caseId)}/relations/${encodeURIComponent(relatedId)}`,
        { method: "DELETE" },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: libraryKeys.detail(caseId) });
    },
  });
}

export type PrecedentPatch = Partial<{
  case_name: string;
  court: string;
  decision_date: string;
  practice_area: PracticeArea;
  appeal_subtype: string;
  subject_tags: string[];
  summary: string;
  headnote: string;
  source_type: SourceType;
  precedent_level: string;
  is_binding: boolean;
  district: string;
  chair_name: string;
  citation_formatted: string;
}>;

export function useUpdatePrecedent() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ id, patch }: { id: string; patch: PrecedentPatch }) =>
      apiRequest<Precedent>(
        `/api/precedent-library/${encodeURIComponent(id)}`,
        { method: "PATCH", body: patch },
      ),
    onSuccess: (_, { id }) => {
      qc.invalidateQueries({ queryKey: libraryKeys.detail(id) });
      qc.invalidateQueries({ queryKey: libraryKeys.all });
    },
  });
}

/* Extraction can't run inside the container (no `claude` CLI). The
 * "request" endpoints below stamp a queue marker in case_law; the chair
 * (or me) drains the queue from Claude Code by invoking the MCP tool
 * `precedent_process_pending`, which runs the actual extractor locally.
 * See the rule in mcp-server/src/legal_mcp/services/claude_session.py. */

export function useRequestMetadataExtraction() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string) =>
      apiRequest<{ queued: boolean }>(
        `/api/precedent-library/${encodeURIComponent(id)}/request-metadata`,
        { method: "POST" },
      ),
    onSuccess: (_, id) => {
      qc.invalidateQueries({ queryKey: libraryKeys.detail(id) });
      qc.invalidateQueries({ queryKey: libraryKeys.all });
    },
  });
}

export function useRequestHalachotExtraction() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string) =>
      apiRequest<{ queued: boolean }>(
        `/api/precedent-library/${encodeURIComponent(id)}/request-halachot`,
        { method: "POST" },
      ),
    onSuccess: (_, id) => {
      qc.invalidateQueries({ queryKey: libraryKeys.detail(id) });
      qc.invalidateQueries({ queryKey: libraryKeys.all });
    },
  });
}

export function useHalachotPending(limit = 200) {
  return useQuery({
    queryKey: libraryKeys.halachotPending(),
    queryFn: ({ signal }) =>
      apiRequest<{ items: Halacha[]; count: number }>(
        `/api/halachot?review_status=pending_review&limit=${limit}`,
        { signal },
      ),
    staleTime: 5_000,
    refetchOnMount: "always",
  });
}

export type HalachaPatch = Partial<{
  review_status: "pending_review" | "approved" | "rejected" | "published";
  reviewer: string;
  rule_statement: string;
  reasoning_summary: string;
  subject_tags: string[];
  practice_areas: string[];
}>;

export function useUpdateHalacha() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ id, patch }: { id: string; patch: HalachaPatch }) =>
      apiRequest<Halacha>(
        `/api/halachot/${encodeURIComponent(id)}`,
        { method: "PATCH", body: patch },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: libraryKeys.all });
    },
  });
}
