/**
 * Corpus graph hooks — feed the /graph page (the in-app, Obsidian-graph-view-
 * like network of the precedent corpus).
 *
 * The types below mirror web/graph_api.py (CorpusGraph / GraphNode / GraphEdge).
 * They are hand-declared for now because `npm run api:types` reads the PROD
 * OpenAPI schema, which won't expose /api/graph/* until this PR is deployed.
 * After deploy, run `npm run api:types` and these can be swapped for the
 * generated types (UI1) — same chicken-and-egg pattern documented in cases.ts.
 *
 * Read-only projection of the live corpus (G2): no parallel store, no drift.
 */

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

export type GraphNodeType =
  | "precedent"
  | "halacha"
  | "topic"
  | "practice_area"
  | "gap"
  | "digest";

export type GraphEdgeType =
  | "cites"
  | "same_chain"
  | "tagged"
  | "in_area"
  | "corroborates"
  | "equivalent"
  | "covers"
  | "extracted_from";

export type GraphNode = {
  id: string;
  type: GraphNodeType;
  label: string;
  size: number;
  practice_area: string | null;
  source_kind: string | null;
  precedent_level: string | null;
  court: string | null;
  date: string | null; // ISO date
  case_law_id: string | null;
  pagerank: number | null; // normalized 0–1, only when metrics requested
  betweenness: number | null; // normalized 0–1
  community: number | null; // dense cluster id, 0 = largest
  gap_status: string | null; // gap nodes only — open|uploaded|closed|irrelevant
  missing_precedent_id: string | null; // gap nodes only
  note: string | null; // digest nodes only — the holding line
  digest_id: string | null; // digest nodes only
};

export type GraphFacets = {
  courts: string[];
  precedent_levels: string[];
  chairs: string[];
  districts: string[];
};

export type GraphEdge = {
  source: string;
  target: string;
  type: GraphEdgeType;
  treatment: string | null;
  weight: number | null;
};

export type CorpusGraph = {
  nodes: GraphNode[];
  edges: GraphEdge[];
  truncated: boolean;
  total_available: number;
};

export type GraphFilters = {
  practice_area?: string;
  source?: string;
  node_types?: string;
  min_citations?: number;
  limit?: number;
  q?: string;
  court?: string;
  precedent_level?: string;
  chair?: string;
  district?: string;
  year_from?: number;
  year_to?: number;
  metrics?: boolean;
};

export const graphKeys = {
  all: ["graph"] as const,
  corpus: (f: GraphFilters) => [...graphKeys.all, "corpus", f] as const,
  neighborhood: (id: string, depth: number, nodeTypes: string) =>
    [...graphKeys.all, "neighborhood", id, depth, nodeTypes] as const,
};

function buildParams(f: GraphFilters): string {
  const p = new URLSearchParams();
  if (f.practice_area) p.set("practice_area", f.practice_area);
  if (f.source) p.set("source", f.source);
  if (f.node_types) p.set("node_types", f.node_types);
  if (f.min_citations != null) p.set("min_citations", String(f.min_citations));
  if (f.limit != null) p.set("limit", String(f.limit));
  if (f.q) p.set("q", f.q.trim());
  if (f.court) p.set("court", f.court);
  if (f.precedent_level) p.set("precedent_level", f.precedent_level);
  if (f.chair) p.set("chair", f.chair);
  if (f.district) p.set("district", f.district);
  if (f.year_from) p.set("year_from", String(f.year_from));
  if (f.year_to) p.set("year_to", String(f.year_to));
  if (f.metrics) p.set("metrics", "true");
  return p.toString();
}

/** Full corpus graph under the given filters. Disabled while a node is focused. */
export function useCorpusGraph(filters: GraphFilters, enabled = true) {
  return useQuery({
    queryKey: graphKeys.corpus(filters),
    queryFn: ({ signal }) =>
      apiRequest<CorpusGraph>(`/api/graph/corpus?${buildParams(filters)}`, { signal }),
    enabled,
    staleTime: 30_000,
    placeholderData: keepPreviousData,
  });
}

/** Local-graph: the focused node + its neighbors out to `depth` (1-2). */
export function useNodeNeighborhood(
  nodeId: string | null,
  depth = 1,
  nodeTypes = "",
) {
  return useQuery({
    queryKey: graphKeys.neighborhood(nodeId ?? "", depth, nodeTypes),
    queryFn: ({ signal }) => {
      const p = new URLSearchParams({ depth: String(depth) });
      if (nodeTypes) p.set("node_types", nodeTypes);
      return apiRequest<CorpusGraph>(
        `/api/graph/node/${encodeURIComponent(nodeId as string)}/neighborhood?${p.toString()}`,
        { signal },
      );
    },
    enabled: !!nodeId,
    staleTime: 30_000,
  });
}

/** Distinct filter values (courts / levels / chairs / districts) for dropdowns. */
export function useGraphFacets() {
  return useQuery({
    queryKey: [...graphKeys.all, "facets"] as const,
    queryFn: ({ signal }) => apiRequest<GraphFacets>("/api/graph/facets", { signal }),
    staleTime: 300_000,
  });
}
