import { useMemo, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
  AlertCircle,
  ArrowLeft,
  CircleDot,
  GitBranch,
  Network,
  RefreshCw,
  Search,
  SlidersHorizontal,
} from "lucide-react";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Progress } from "@/components/ui/progress";
import { Select } from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton";
import { useGraphNeighborhood } from "@/hooks/usePlatform";
import { useProject } from "@/hooks/useProjects";
import { GraphEdge, GraphNode } from "@/lib/api/platform";
import { formatDateTime, formatPercent, humanizeValue } from "@/lib/display";

const COLORS = [
  "#2563eb",
  "#059669",
  "#d97706",
  "#7c3aed",
  "#db2777",
  "#0891b2",
  "#4b5563",
  "#dc2626",
];

interface VisualNode {
  id: string;
  name: string;
  type: string;
  x: number;
  y: number;
  radius: number;
  color: string;
  source: "entity" | "literal";
  raw?: GraphNode;
}

interface VisualEdge {
  id: string;
  source: string;
  target: string;
  predicate: string;
  confidence: number;
  raw: GraphEdge;
}

function nodeColor(type: string, types: string[]): string {
  const index = Math.max(types.indexOf(type), 0);
  return COLORS[index % COLORS.length];
}

function buildGraph(
  nodes: GraphNode[],
  edges: GraphEdge[],
  options: {
    search: string;
    predicate: string;
    entityType: string;
    minConfidence: number;
  },
) {
  const search = options.search.trim().toLowerCase();
  const types = Array.from(
    new Set(nodes.map((node) => node.type || "Entity").concat("Literal")),
  ).sort();
  const nodeMap = new Map<string, VisualNode>();
  nodes.forEach((node) => {
    const type = node.type || "Entity";
    nodeMap.set(String(node.id), {
      id: String(node.id),
      name: node.name,
      type,
      x: 0,
      y: 0,
      radius: 18,
      color: nodeColor(type, types),
      source: "entity",
      raw: node,
    });
  });

  const visualEdges: VisualEdge[] = [];
  edges.forEach((edge) => {
    const confidence = edge.confidence ?? 0;
    if (confidence < options.minConfidence) return;
    if (options.predicate && edge.predicate !== options.predicate) return;
    const source = String(edge.source);
    const target =
      edge.target !== null && edge.target !== undefined
        ? String(edge.target)
        : `literal-${edge.claim_id}`;
    if (!nodeMap.has(source)) return;
    if (!nodeMap.has(target)) {
      const label = humanizeValue(edge.target_value ?? edge.object?.name);
      nodeMap.set(target, {
        id: target,
        name: label || "(empty value)",
        type: "Literal",
        x: 0,
        y: 0,
        radius: 14,
        color: nodeColor("Literal", types),
        source: "literal",
      });
    }
    visualEdges.push({
      id: String(edge.claim_id),
      source,
      target,
      predicate: edge.predicate,
      confidence,
      raw: edge,
    });
  });

  let visualNodes = Array.from(nodeMap.values()).filter((node) => {
    if (options.entityType && node.type !== options.entityType) return false;
    if (!search) return true;
    return (
      node.name.toLowerCase().includes(search) ||
      node.type.toLowerCase().includes(search)
    );
  });
  const visibleIds = new Set(visualNodes.map((node) => node.id));
  const filteredEdges = visualEdges.filter(
    (edge) => visibleIds.has(edge.source) && visibleIds.has(edge.target),
  );
  const connectedIds = new Set<string>();
  filteredEdges.forEach((edge) => {
    connectedIds.add(edge.source);
    connectedIds.add(edge.target);
  });
  if (options.predicate || options.minConfidence > 0 || search) {
    visualNodes = visualNodes.filter((node) => connectedIds.has(node.id));
  }

  const centerX = 430;
  const centerY = 300;
  const radius = Math.max(160, Math.min(260, visualNodes.length * 13));
  visualNodes.forEach((node, index) => {
    const angle = (Math.PI * 2 * index) / Math.max(visualNodes.length, 1);
    node.x = centerX + Math.cos(angle) * radius;
    node.y = centerY + Math.sin(angle) * radius;
  });
  return { nodes: visualNodes, edges: filteredEdges, types };
}

function edgeEndpoint(edge: VisualEdge, nodes: VisualNode[]) {
  const source = nodes.find((node) => node.id === edge.source);
  const target = nodes.find((node) => node.id === edge.target);
  return { source, target };
}

export default function GraphViewPage() {
  const navigate = useNavigate();
  const { projectId } = useParams<{ projectId: string }>();
  const projectName = projectId ?? "";
  const { data: project } = useProject(projectName);
  const [statusScope, setStatusScope] = useState("validated_claim");
  const [includeCandidates, setIncludeCandidates] = useState(true);
  const [search, setSearch] = useState("");
  const [predicate, setPredicate] = useState("");
  const [entityType, setEntityType] = useState("");
  const [minConfidence, setMinConfidence] = useState(0);
  const [selected, setSelected] = useState<VisualNode | VisualEdge | null>(null);
  const graph = useGraphNeighborhood(projectName, {
    includeCandidates: includeCandidates && statusScope === "validated_claim",
    limit: 300,
    status:
      includeCandidates && statusScope === "validated_claim"
        ? undefined
        : statusScope,
  });

  const predicates = useMemo(
    () =>
      Array.from(
        new Set((graph.data?.edges ?? []).map((edge) => edge.predicate)),
      ).sort(),
    [graph.data?.edges],
  );
  const visual = useMemo(
    () =>
      buildGraph(graph.data?.nodes ?? [], graph.data?.edges ?? [], {
        search,
        predicate,
        entityType,
        minConfidence,
      }),
    [
      entityType,
      graph.data?.edges,
      graph.data?.nodes,
      minConfidence,
      predicate,
      search,
    ],
  );
  const averageConfidence = visual.edges.length
    ? visual.edges.reduce((sum, edge) => sum + edge.confidence, 0) /
      visual.edges.length
    : 0;

  return (
    <div className="mx-auto max-w-7xl px-6 py-8">
      <div className="mb-6 flex items-center gap-3">
        <Button
          variant="ghost"
          size="icon"
          onClick={() => navigate(`/quality/${projectName}`)}
          aria-label="Back"
        >
          <ArrowLeft className="h-4 w-4" />
        </Button>
        <div className="flex-1">
          <h1 className="flex items-center gap-2 text-2xl font-bold tracking-tight">
            <Network className="h-6 w-6 text-primary" />
            Graph View
          </h1>
          <p className="text-sm text-muted-foreground">
            {project?.name ?? projectName} relation graph, candidates, and edge evidence.
          </p>
        </div>
        <Button variant="outline" onClick={() => graph.refetch()}>
          <RefreshCw className="h-4 w-4" />
          Refresh
        </Button>
      </div>

      {graph.isError && (
        <Card className="mb-6 border-destructive">
          <CardContent className="flex items-center gap-2 py-4 text-sm text-destructive">
            <AlertCircle className="h-4 w-4" />
            {(graph.error as Error).message}
          </CardContent>
        </Card>
      )}

      <div className="mb-4 grid gap-3 xl:grid-cols-[1fr_180px_180px_180px_220px]">
        <div className="relative">
          <Search className="pointer-events-none absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
          <Input
            value={search}
            onChange={(event) => setSearch(event.target.value)}
            className="pl-9"
            placeholder="Search node or type"
          />
        </div>
        <Select
          value={entityType}
          onChange={(event) => setEntityType(event.target.value)}
        >
          <option value="">All types</option>
          {visual.types.map((type) => (
            <option key={type} value={type}>
              {type}
            </option>
          ))}
        </Select>
        <Select
          value={predicate}
          onChange={(event) => setPredicate(event.target.value)}
        >
          <option value="">All predicates</option>
          {predicates.map((name) => (
            <option key={name} value={name}>
              {name}
            </option>
          ))}
        </Select>
        <Select
          value={statusScope}
          onChange={(event) => setStatusScope(event.target.value)}
        >
          <option value="validated_claim">Validated</option>
          <option value="candidate_claim">Candidate</option>
          <option value="rule_candidate">Rule candidate</option>
          <option value="active">Active</option>
          <option value="all">All statuses</option>
        </Select>
        <label className="flex items-center gap-2 rounded-md border px-3 text-sm">
          <input
            type="checkbox"
            checked={includeCandidates}
            disabled={statusScope !== "validated_claim"}
            onChange={(event) => setIncludeCandidates(event.target.checked)}
          />
          Blend candidates
        </label>
      </div>

      <div className="mb-6 rounded-md border px-4 py-3">
        <div className="mb-2 flex items-center justify-between text-sm">
          <span className="flex items-center gap-2 text-muted-foreground">
            <SlidersHorizontal className="h-4 w-4" />
            Minimum confidence
          </span>
          <span>{formatPercent(minConfidence)}</span>
        </div>
        <input
          type="range"
          min={0}
          max={1}
          step={0.05}
          value={minConfidence}
          onChange={(event) => setMinConfidence(Number(event.target.value))}
          className="w-full"
        />
      </div>

      <div className="grid gap-6 xl:grid-cols-[1fr_360px]">
        <Card>
          <CardHeader>
            <div className="flex flex-wrap items-center justify-between gap-3">
              <div>
                <CardTitle>Ontology Network</CardTitle>
                <CardDescription>
                  {visual.nodes.length} nodes / {visual.edges.length} edges shown
                </CardDescription>
              </div>
              <div className="flex flex-wrap gap-2">
                {visual.types.slice(0, 8).map((type) => (
                  <span key={type} className="flex items-center gap-1 text-xs">
                    <span
                      className="h-2.5 w-2.5 rounded-full"
                      style={{ backgroundColor: nodeColor(type, visual.types) }}
                    />
                    {type}
                  </span>
                ))}
              </div>
            </div>
          </CardHeader>
          <CardContent>
            {graph.isLoading ? (
              <Skeleton className="h-[640px]" />
            ) : visual.nodes.length === 0 ? (
              <div className="flex h-[500px] flex-col items-center justify-center gap-2 text-center text-sm text-muted-foreground">
                <GitBranch className="h-12 w-12 opacity-50" />
                No graph is available for the current filters.
              </div>
            ) : (
              <svg
                viewBox="0 0 860 600"
                className="h-[640px] w-full rounded-md border bg-secondary/20"
                role="img"
                aria-label="Ontology graph"
              >
                <defs>
                  <marker
                    id="arrow"
                    markerWidth="10"
                    markerHeight="10"
                    refX="10"
                    refY="3"
                    orient="auto"
                    markerUnits="strokeWidth"
                  >
                    <path d="M0,0 L0,6 L9,3 z" fill="#64748b" />
                  </marker>
                </defs>
                {visual.edges.map((edge) => {
                  const { source, target } = edgeEndpoint(edge, visual.nodes);
                  if (!source || !target) return null;
                  const midX = (source.x + target.x) / 2;
                  const midY = (source.y + target.y) / 2;
                  return (
                    <g
                      key={edge.id}
                      className="cursor-pointer"
                      onClick={() => setSelected(edge)}
                    >
                      <line
                        x1={source.x}
                        y1={source.y}
                        x2={target.x}
                        y2={target.y}
                        stroke="#64748b"
                        strokeWidth={1 + edge.confidence * 3}
                        strokeOpacity="0.65"
                        markerEnd="url(#arrow)"
                      />
                      <text
                        x={midX}
                        y={midY}
                        textAnchor="middle"
                        className="fill-slate-600 text-[10px]"
                      >
                        {edge.predicate}
                      </text>
                    </g>
                  );
                })}
                {visual.nodes.map((node) => (
                  <g
                    key={node.id}
                    className="cursor-pointer"
                    onClick={() => setSelected(node)}
                  >
                    <circle
                      cx={node.x}
                      cy={node.y}
                      r={node.radius}
                      fill={node.color}
                      stroke={
                        selected && "id" in selected && selected.id === node.id
                          ? "#111827"
                          : "#ffffff"
                      }
                      strokeWidth="2"
                    />
                    <text
                      x={node.x}
                      y={node.y + node.radius + 14}
                      textAnchor="middle"
                      className="fill-slate-800 text-[11px] font-medium"
                    >
                      {node.name.length > 24
                        ? `${node.name.slice(0, 24)}...`
                        : node.name}
                    </text>
                  </g>
                ))}
              </svg>
            )}
          </CardContent>
        </Card>

        <aside className="space-y-6">
          <Card>
            <CardHeader>
              <CardTitle>Selection</CardTitle>
              <CardDescription>Click a node or edge to inspect it.</CardDescription>
            </CardHeader>
            <CardContent>
              {!selected && (
                <p className="py-8 text-center text-sm text-muted-foreground">
                  Nothing selected.
                </p>
              )}
              {selected && "predicate" in selected && (
                <div className="space-y-3">
                  <Badge variant="secondary">{selected.predicate}</Badge>
                  <Detail label="Claim" value={`#${selected.id}`} />
                  <Detail label="Status" value={selected.raw.status} />
                  <Detail
                    label="Confidence"
                    value={formatPercent(selected.confidence)}
                  />
                  <Detail
                    label="Object"
                    value={selected.raw.object?.name ?? selected.raw.target_value}
                  />
                  <Detail
                    label="Last seen"
                    value={formatDateTime(selected.raw.last_seen_at)}
                  />
                  <Detail
                    label="Metadata"
                    value={humanizeValue(selected.raw.metadata)}
                  />
                </div>
              )}
              {selected && !("predicate" in selected) && (
                <div className="space-y-3">
                  <Badge variant="outline">{selected.type}</Badge>
                  <Detail label="Name" value={selected.name} />
                  <Detail label="Source" value={selected.source} />
                  <Detail label="Canonical" value={selected.raw?.canonical_name} />
                  <Detail
                    label="Metadata"
                    value={humanizeValue(selected.raw?.metadata)}
                  />
                </div>
              )}
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <CardTitle>Graph Health</CardTitle>
            </CardHeader>
            <CardContent className="space-y-3">
              <HealthRow label="Visible nodes" value={visual.nodes.length} />
              <HealthRow label="Visible edges" value={visual.edges.length} />
              <HealthRow
                label="Avg confidence"
                value={
                  visual.edges.length ? formatPercent(averageConfidence) : "-"
                }
              />
              <Progress value={averageConfidence * 100} />
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <CardTitle className="flex items-center gap-2">
                <CircleDot className="h-5 w-5" />
                Predicate Counts
              </CardTitle>
            </CardHeader>
            <CardContent>
              <ul className="space-y-2 text-sm">
                {predicates.map((name) => {
                  const count = (graph.data?.edges ?? []).filter(
                    (edge) => edge.predicate === name,
                  ).length;
                  return (
                    <li
                      key={name}
                      className="flex items-center justify-between rounded-md border px-3 py-2"
                    >
                      <span>{name}</span>
                      <span className="font-medium">{count}</span>
                    </li>
                  );
                })}
                {predicates.length === 0 && (
                  <li className="py-4 text-center text-muted-foreground">
                    No predicates found.
                  </li>
                )}
              </ul>
            </CardContent>
          </Card>
        </aside>
      </div>
    </div>
  );
}

function Detail({ label, value }: { label: string; value: unknown }) {
  return (
    <div className="rounded-md border px-3 py-2 text-sm">
      <div className="text-xs text-muted-foreground">{label}</div>
      <div className="mt-1 break-words font-medium">{humanizeValue(value)}</div>
    </div>
  );
}

function HealthRow({ label, value }: { label: string; value: number | string }) {
  return (
    <div className="flex items-center justify-between text-sm">
      <span className="text-muted-foreground">{label}</span>
      <span className="font-medium">
        {typeof value === "number" ? value.toLocaleString() : value}
      </span>
    </div>
  );
}
