import { useEffect, useRef } from 'react';
import Graph from 'graphology';
import Sigma from 'sigma';
import { type Person, type Expense, type Transfer } from '@splint/shared';

interface Props {
  people: Person[];
  expenses: Expense[];
  transfers: Transfer[];
  balances: Map<string, number>;
}

// ── helpers ────────────────────────────────────────────────────────────

/** Small random jitter so the rings don't look rigidly geometric. */
function jitter(max = 0.3): number {
  return (Math.random() - 0.5) * max;
}

/** Format cents to dollars, dropping the decimal when whole. */
function fmt(amount: number): string {
  return `$${(amount / 100).toFixed(amount % 100 === 0 ? 0 : 2)}`;
}

/** Truncate long descriptions for node labels. */
function trunc(desc: string, max = 20): string {
  return desc.length > max ? desc.slice(0, max - 1) + '…' : desc;
}

/** Map balance to node color. */
function balanceColor(b: number): string {
  if (b < 0) return '#c94a4a'; // red — owes
  if (b > 0) return '#5a9e6f'; // green — is owed
  return '#8a8278';            // gray — settled
}

// ── layout constants ────────────────────────────────────────────────────

const P_RADIUS = 1.8;        // people circle radius
const E_RING_BASE = 3.5;     // innermost expense ring radius
const E_RING_STEP = 2.2;     // gap between consecutive rings
const SLOT_SPACING = 2.5;    // arc-length spacing between expense nodes
const MIN_SLOTS = 6;         // minimum slots per ring (for tiny ledgers)

/**
 * Interactive money-flow graph using graphology + sigma.js v3.
 *
 * Nodes: people (center cluster, colored by balance) + expenses (outer
 * concentric rings, gold).  Edges: directed arrows showing the actual flow
 * of money as it occurred in the real world — payer → expense and
 * sender → receiver for transfers.
 */
export function DebtGraph({ people, expenses, transfers, balances }: Props) {
  const containerRef = useRef<HTMLDivElement>(null);
  const sigmaRef = useRef<Sigma | null>(null);

  useEffect(() => {
    if (!containerRef.current || people.length === 0) return;

    const graph = new Graph({ multi: true });

    // ── 1. People nodes (inner circle) ────────────────────────────────

    for (let i = 0; i < people.length; i++) {
      const p = people[i];
      const angle = (2 * Math.PI * i) / people.length;
      const bal = balances.get(p.id) ?? 0;
      const color = balanceColor(bal);

      let label = p.name;
      let labelColor = '#8a8278'; // gray default
      if (bal > 0) {
        label = `${p.name}\n+${fmt(bal)}`;
        labelColor = '#5a9e6f'; // green
      } else if (bal < 0) {
        label = `${p.name}\n-${fmt(-bal)}`;
        labelColor = '#c94a4a'; // red
      }
      // zero balance: label is just name, gray

      graph.addNode(p.id, {
        label,
        size: 12,
        color,
        labelColor,
        x: P_RADIUS * Math.cos(angle) + jitter(),
        y: P_RADIUS * Math.sin(angle) + jitter(),
      });
    }

    // ── 2. Expense nodes (concentric rings) ────────────────────────────

    const sorted = [...expenses].sort(
      (a, b) => a.createdAt.localeCompare(b.createdAt),
    );

    let ring = 0;
    let slot = 0;

    for (const exp of sorted) {
      const radius = E_RING_BASE + ring * E_RING_STEP;
      const capacity = Math.max(
        MIN_SLOTS,
        Math.floor((2 * Math.PI * radius) / SLOT_SPACING),
      );
      const angle = (2 * Math.PI * slot) / capacity;

      graph.addNode(exp.id, {
        label: `${trunc(exp.description)} — ${fmt(exp.amount)}`,
        size: 8,
        color: '#c9a227', // gold — expense destination
        labelColor: '#c9a227', // gold
        x: radius * Math.cos(angle) + jitter(0.5),
        y: radius * Math.sin(angle) + jitter(0.5),
      });

      slot++;
      if (slot >= capacity) {
        ring++;
        slot = 0;
      }
    }

    // ── 3. Edges (money flow) ──────────────────────────────────────────

    // Compute settlement pairs first (so transfers can avoid duplicates)
    const settlementPairs = new Set<string>();
    {
      const debtors: { id: string; amount: number }[] = [];
      const creditors: { id: string; amount: number }[] = [];
      for (const [id, bal] of balances) {
        if (bal < 0) debtors.push({ id, amount: -bal });
        else if (bal > 0) creditors.push({ id, amount: bal });
      }
      let di = 0, ci = 0;
      while (di < debtors.length && ci < creditors.length) {
        const d = debtors[di];
        const c = creditors[ci];
        const transfer = Math.min(d.amount, c.amount);
        settlementPairs.add(`${d.id}→${c.id}`);
        d.amount -= transfer;
        c.amount -= transfer;
        if (d.amount <= 0) di++;
        if (c.amount <= 0) ci++;
      }
    }

    // Payer → Expense
    for (const exp of expenses) {
      if (graph.hasNode(exp.paidBy) && graph.hasNode(exp.id)) {
        graph.addDirectedEdge(exp.paidBy, exp.id, {
          label: fmt(exp.amount),
          size: 2,
          color: '#5a9e6f', // green — money flowing to expense
          labelColor: '#5a9e6f', // green
        });
      }
    }

    // Sender → Receiver (transfers) — skip pairs that already have a settlement edge
    for (const t of transfers) {
      if (graph.hasNode(t.from) && graph.hasNode(t.to)) {
        if (settlementPairs.has(`${t.from}→${t.to}`)) continue;
        graph.addDirectedEdge(t.from, t.to, {
          label: fmt(t.amount),
          size: 2,
          color: '#6a8fbf',
          labelColor: '#8a8278', // gray — transfers are neutral
        });
      }
    }

    // Settlement edges (gold) — add edges for computed pairs
    {
      const debtors: { id: string; amount: number }[] = [];
      const creditors: { id: string; amount: number }[] = [];
      for (const [id, bal] of balances) {
        if (bal < 0) debtors.push({ id, amount: -bal });
        else if (bal > 0) creditors.push({ id, amount: bal });
      }
      let di = 0, ci = 0;
      while (di < debtors.length && ci < creditors.length) {
        const d = debtors[di];
        const c = creditors[ci];
        const transfer = Math.min(d.amount, c.amount);
        graph.addDirectedEdge(d.id, c.id, {
          label: fmt(transfer),
          size: 2,
          color: '#c9a227', // gold — required settlement
          labelColor: '#c9a227', // gold
        });
        d.amount -= transfer;
        c.amount -= transfer;
        if (d.amount <= 0) di++;
        if (c.amount <= 0) ci++;
      }
    }

    // ── 4. Render ──────────────────────────────────────────────────────

    if (sigmaRef.current) {
      sigmaRef.current.kill();
    }

    const sigma = new Sigma(graph, containerRef.current, {
      renderLabels: true,
      renderEdgeLabels: true,
      labelColor: { color: '#d4cdc4' },
      labelSize: 14,
      labelFont: 'Inter, system-ui, sans-serif',
      defaultEdgeColor: '#6a8fbf',
      defaultNodeColor: '#8a8278',
      defaultEdgeType: 'arrow',
      edgeLabelColor: { color: '#8a8278' },
      edgeLabelSize: 11,
      edgeLabelFont: 'Inter, system-ui, sans-serif',
      autoRescale: true,
      autoCenter: true,
      minEdgeThickness: 1,
      stagePadding: 40,
    });

    sigmaRef.current = sigma;

    return () => {
      sigma.kill();
      sigmaRef.current = null;
    };
  }, [people, expenses, transfers, balances]);

  if (people.length === 0) {
    return (
      <p className="text-sm text-muted-foreground">
        Add people to see the money flow graph.
      </p>
    );
  }

  return (
    <div
      style={{
        position: 'relative',
        width: '100%',
        height: 450,
      }}
    >
      <div
        ref={containerRef}
        style={{
          width: '100%',
          height: '100%',
          background: 'var(--code-bg)',
          borderRadius: 'var(--radius)',
        }}
        role="img"
        aria-label={`Money flow graph showing ${people.length} people and ${expenses.length} expenses`}
      />
      {/* Overlay to prevent mouse interaction (drag/zoom) */}
      <div
        style={{ position: 'absolute', inset: 0 }}
        aria-hidden="true"
      />
    </div>
  );
}
