// Auteur : Simon-Pierre Boucher — contact@spboucher.ai
/**
 * MetricViz — visualisations signature générées par les données du registre.
 * Chaque propriété produit ses propres dessins : façade SVG (étages, logements,
 * genre de construction), schéma de terrain à l'échelle (frontage × profondeur),
 * composition de valeur terrain/bâtiment, jauge de confiance, sparkline 2021-2026,
 * compteurs animés. SVG pur, palette encre/lime, aucun aléatoire : mêmes données,
 * même dessin.
 */
"use client";
import { useEffect, useState } from "react";

/** Nombre localisé (« 1 206,6 » en FR, « 1,206.6 » en EN). */
export function locNum(
  v: number | null | undefined,
  lang: string,
  opts?: { unit?: string; maxFrac?: number }
): string {
  if (v == null) return "—";
  const s = v.toLocaleString(lang === "fr" ? "fr-CA" : "en-CA", {
    maximumFractionDigits: opts?.maxFrac ?? 1,
  });
  return opts?.unit ? `${s} ${opts.unit}` : s;
}

/* ---------------------------- compteur animé ---------------------------- */
/** Rendu serveur = valeur finale (jamais « 0 $ » à l'écran) ; anime 0 → valeur au montage. */
export function CountUp({
  value,
  format,
  duration = 900,
}: {
  value: number;
  format: (v: number) => string;
  duration?: number;
}) {
  const [display, setDisplay] = useState(value);

  useEffect(() => {
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    let raf = 0;
    const t0 = performance.now();
    const tick = (t: number) => {
      const p = Math.min(1, (t - t0) / duration);
      const eased = 1 - Math.pow(1 - p, 3);
      setDisplay(value * eased);
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [value, duration]);

  return <span>{format(display)}</span>;
}

/* --------------------------- jauge de confiance --------------------------- */
export function ConfidenceDial({
  pct,
  level,
  label,
}: {
  pct: number;
  level: "A" | "B" | "C" | "D";
  label: string;
}) {
  const [mounted, setMounted] = useState(false);
  useEffect(() => {
    const id = setTimeout(() => setMounted(true), 60);
    return () => clearTimeout(id);
  }, []);
  const R = 52;
  const CIRC = Math.PI * R; // demi-cercle
  const frac = mounted ? Math.max(0.02, Math.min(1, pct / 100)) : 0.02;
  const color =
    level === "A" ? "var(--lime)" : level === "B" ? "var(--green)" : level === "C" ? "var(--amber)" : "var(--danger)";
  return (
    <div className="flex flex-col items-center" aria-label={`${label} : ${level} ${pct} %`}>
      <svg viewBox="0 0 128 78" className="w-[128px]">
        <path d="M 12 66 A 52 52 0 0 1 116 66" fill="none" stroke="var(--surface-2)" strokeWidth="12" strokeLinecap="round" />
        <path d="M 12 66 A 52 52 0 0 1 116 66" fill="none" stroke="rgba(20,24,20,0.85)" strokeWidth="13.5" strokeLinecap="round" opacity="0.12" />
        <path
          d="M 12 66 A 52 52 0 0 1 116 66"
          fill="none"
          stroke={color}
          strokeWidth="12"
          strokeLinecap="round"
          strokeDasharray={`${CIRC * frac} ${CIRC}`}
          style={{ transition: "stroke-dasharray 1s cubic-bezier(.2,.8,.2,1)" }}
        />
        {/* graduations */}
        {[0, 0.25, 0.5, 0.75, 1].map((g) => {
          const a = Math.PI * (1 - g);
          const x1 = 64 + Math.cos(a) * 40;
          const y1 = 66 - Math.sin(a) * 40;
          const x2 = 64 + Math.cos(a) * 45;
          const y2 = 66 - Math.sin(a) * 45;
          return <line key={g} x1={x1} y1={y1} x2={x2} y2={y2} stroke="var(--ink)" strokeWidth="1.4" opacity="0.5" />;
        })}
        <text x="64" y="52" textAnchor="middle" fontSize="26" fontWeight="700" fill="var(--ink)" fontFamily="var(--font-space-grotesk)">
          {level}
        </text>
        <text x="64" y="70" textAnchor="middle" fontSize="10.5" fontWeight="700" fill="var(--ink-2)" fontFamily="var(--font-jetbrains)">
          {pct} %
        </text>
      </svg>
      <p className="klabel mt-1 text-center">{label}</p>
    </div>
  );
}

/* --------------------------- façade de bâtiment --------------------------- */
/** Terrain sans bâtiment : borne d'arpenteur et conifères, pas de fausse maison. */
function VacantGlyph({ lang }: { lang: string }) {
  const tree = (x: number, sc: number) => (
    <g transform={`translate(${x}, 128) scale(${sc})`}>
      <path d="M0,-46 L13,-14 L5,-14 L16,8 L-16,8 L-5,-14 L-13,-14 Z" fill="var(--green)" stroke="var(--ink)" strokeWidth="1.6" />
      <rect x="-2.5" y="8" width="5" height="9" fill="var(--ink)" />
    </g>
  );
  return (
    <svg viewBox="0 0 190 150" className="w-full max-w-[210px]" aria-label={lang === "fr" ? "Terrain sans bâtiment" : "Vacant land"}>
      {tree(52, 1)}
      {tree(96, 0.72)}
      {tree(134, 0.9)}
      {/* borne d'arpenteur */}
      <g transform="translate(24, 128)">
        <rect x="-2.5" y="-26" width="5" height="26" fill="var(--ink)" />
        <rect x="-8" y="-34" width="16" height="10" fill="var(--lime)" stroke="var(--ink)" strokeWidth="1.4" />
      </g>
      <line x1="8" y1="145" x2="182" y2="145" stroke="var(--ink)" strokeWidth="2.2" />
      <text x="95" y="120" textAnchor="middle" fontSize="9" fontWeight="700" fontFamily="var(--font-jetbrains)" fill="var(--ink-2)" letterSpacing="2">
        {lang === "fr" ? "SANS BÂTIMENT" : "NO BUILDING"}
      </text>
    </svg>
  );
}

/** Façade dessinée à partir du registre : étages, logements, genre de construction.
 *  Sans bâtiment au registre (ni aire, ni année, ni étages) → borne d'arpenteur. */
export function BuildingGlyph({
  floors,
  dwellings,
  genre,
  yearBuilt,
  floorAreaM2,
  lang = "fr",
}: {
  floors: number | null;
  dwellings: number | null;
  genre: string | null;
  yearBuilt: number | null;
  floorAreaM2?: number | null;
  lang?: string;
}) {
  if (!floorAreaM2 && !yearBuilt && !floors && !dwellings) {
    return <VacantGlyph lang={lang} />;
  }
  const nF = Math.max(1, Math.min(6, Math.round(floors ?? 1)));
  const nD = Math.max(1, Math.min(24, Math.round(dwellings ?? 1)));
  const winPerFloor = Math.max(1, Math.min(4, Math.ceil(nD / nF)));
  const W = 190;
  const bw = 64 + winPerFloor * 18;
  const bx = (W - bw) / 2;
  const fh = 24;
  const groundY = 128;
  const bodyTop = groundY - nF * fh;
  const mansard = (genre ?? "").toLowerCase().includes("mansard");
  const flat = (genre ?? "").toLowerCase().includes("plain-pied") && nF === 1;

  const windows: React.ReactNode[] = [];
  for (let f = 0; f < nF; f++) {
    for (let wi = 0; wi < winPerFloor; wi++) {
      const idx = f * winPerFloor + wi;
      windows.push(
        <rect
          key={`${f}-${wi}`}
          x={bx + 14 + wi * ((bw - 28) / winPerFloor) + ((bw - 28) / winPerFloor - 12) / 2}
          y={bodyTop + f * fh + 6}
          width="12"
          height="11"
          fill={idx < nD ? "var(--lime)" : "var(--surface-2)"}
          stroke="var(--ink)"
          strokeWidth="1.2"
          className="vv-win"
          style={{ animationDelay: `${idx * 60}ms` }}
        />
      );
    }
  }

  return (
    <svg viewBox={`0 0 ${W} 150`} className="w-full max-w-[210px]" aria-label="Silhouette du bâtiment selon le registre">
      {/* soleil-repère */}
      <circle cx={W - 22} cy="22" r="9" fill="none" stroke="var(--ink)" strokeWidth="1.5" />
      <circle cx={W - 22} cy="22" r="3.5" fill="var(--lime)" stroke="var(--ink)" strokeWidth="1" />
      {/* ombre décalée signature */}
      <rect x={bx + 5} y={bodyTop + 5} width={bw} height={nF * fh} fill="rgba(20,24,20,0.12)" />
      {/* corps */}
      <rect x={bx} y={bodyTop} width={bw} height={nF * fh} fill="var(--surface)" stroke="var(--ink)" strokeWidth="2" />
      {/* séparations d'étages */}
      {Array.from({ length: nF - 1 }, (_, i) => (
        <line key={i} x1={bx} y1={bodyTop + (i + 1) * fh} x2={bx + bw} y2={bodyTop + (i + 1) * fh} stroke="var(--ink)" strokeWidth="1" strokeDasharray="3 3" opacity="0.4" />
      ))}
      {/* toit */}
      {flat ? (
        <rect x={bx - 6} y={bodyTop - 8} width={bw + 12} height="8" fill="var(--green)" stroke="var(--ink)" strokeWidth="1.6" />
      ) : mansard ? (
        <path d={`M ${bx - 6} ${bodyTop} L ${bx + 10} ${bodyTop - 20} L ${bx + bw - 10} ${bodyTop - 20} L ${bx + bw + 6} ${bodyTop} Z`} fill="var(--green)" stroke="var(--ink)" strokeWidth="1.6" />
      ) : (
        <path d={`M ${bx - 8} ${bodyTop} L ${bx + bw / 2} ${bodyTop - 24} L ${bx + bw + 8} ${bodyTop} Z`} fill="var(--green)" stroke="var(--ink)" strokeWidth="1.6" />
      )}
      {/* cheminée */}
      <rect x={bx + bw - 22} y={bodyTop - (flat ? 20 : 30)} width="9" height={flat ? 14 : 18} fill="var(--ink)" />
      {windows}
      {/* porte */}
      <rect x={bx + bw / 2 - 7} y={groundY - 17} width="14" height="17" fill="var(--ink)" />
      <circle cx={bx + bw / 2 + 3.5} cy={groundY - 8} r="1.4" fill="var(--lime)" />
      {/* sol */}
      <line x1="8" y1={groundY} x2={W - 8} y2={groundY} stroke="var(--ink)" strokeWidth="2.2" />
      <line x1="8" y1={groundY + 4} x2={W - 8} y2={groundY + 4} stroke="var(--ink)" strokeWidth="1" opacity="0.3" />
      {/* millésime gravé */}
      {yearBuilt && (
        <text x={W / 2} y="146" textAnchor="middle" fontSize="10" fontWeight="700" fontFamily="var(--font-jetbrains)" fill="var(--ink-2)" letterSpacing="2">
          ANNO {yearBuilt}
        </text>
      )}
    </svg>
  );
}

/* ----------------------------- schéma du terrain ----------------------------- */
/** Terrain à l'échelle réelle : frontage × profondeur déduite de la superficie. */
export function LotDiagram({
  areaM2,
  frontageM,
  footprintM2,
  lang,
  isCondo,
}: {
  areaM2: number | null;
  frontageM: number | null;
  footprintM2: number | null;
  lang: string;
  isCondo?: boolean;
}) {
  if (!areaM2 || areaM2 <= 0) {
    return (
      <svg viewBox="0 0 190 150" className="w-full max-w-[210px]" aria-label={lang === "fr" ? "Terrain non applicable" : "Lot not applicable"}>
        <rect x="30" y="30" width="130" height="86" fill="var(--surface-2)" stroke="var(--ink)" strokeWidth="1.6" strokeDasharray="5 5" rx="6" />
        <text x="95" y="66" textAnchor="middle" fontSize="20" fill="var(--ink-3)">⌂</text>
        <text x="95" y="90" textAnchor="middle" fontSize="8.5" fontWeight="700" fontFamily="var(--font-jetbrains)" fill="var(--ink-2)" letterSpacing="1">
          {isCondo
            ? lang === "fr" ? "QUOTE-PART DE COPROPRIÉTÉ" : "CONDOMINIUM SHARE"
            : lang === "fr" ? "TERRAIN NON INSCRIT AU RÔLE" : "LOT NOT ON ROLL"}
        </text>
        <text x="95" y="104" textAnchor="middle" fontSize="7" fontFamily="var(--font-jetbrains)" fill="var(--ink-3)">
          {isCondo
            ? lang === "fr" ? "terrain commun indivis" : "undivided common land"
            : "—"}
        </text>
      </svg>
    );
  }
  const front = frontageM && frontageM > 0 ? frontageM : Math.sqrt(areaM2);
  const depth = areaM2 / front;
  const ratio = Math.max(0.25, Math.min(4, depth / front));
  const maxW = 150;
  const maxH = 108;
  let w = maxW;
  let h = w * ratio;
  if (h > maxH) {
    h = maxH;
    w = h / ratio;
  }
  const x = (190 - w) / 2;
  const y = 118 - h;
  // empreinte du bâtiment, à l'échelle de la superficie
  const fpFrac = footprintM2 ? Math.min(0.8, footprintM2 / areaM2) : 0;
  const fw = w * Math.sqrt(fpFrac) * 0.9;
  const fh = h * Math.sqrt(fpFrac) * 0.9;
  const fmtM = (v: number) => `${v.toLocaleString(lang === "fr" ? "fr-CA" : "en-CA", { maximumFractionDigits: 1 })} m`;

  return (
    <svg viewBox="0 0 190 150" className="w-full max-w-[210px]" aria-label="Schéma du terrain à l'échelle">
      <defs>
        <pattern id="hatch" width="7" height="7" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
          <line x1="0" y1="0" x2="0" y2="7" stroke="var(--green)" strokeWidth="1" opacity="0.35" />
        </pattern>
      </defs>
      {/* ombre + parcelle */}
      <rect x={x + 4} y={y + 4} width={w} height={h} fill="rgba(20,24,20,0.12)" />
      <rect x={x} y={y} width={w} height={h} fill="var(--lime-soft)" stroke="var(--ink)" strokeWidth="2" />
      <rect x={x} y={y} width={w} height={h} fill="url(#hatch)" />
      {/* empreinte bâtiment */}
      {fpFrac > 0.005 && (
        <rect x={x + (w - fw) / 2} y={y + h - fh - h * 0.08} width={fw} height={fh} fill="var(--ink)" rx="2">
          <title>{lang === "fr" ? "Empreinte approximative du bâtiment" : "Approximate building footprint"}</title>
        </rect>
      )}
      {/* cotes : frontage */}
      <line x1={x} y1={y + h + 12} x2={x + w} y2={y + h + 12} stroke="var(--ink)" strokeWidth="1.3" />
      <line x1={x} y1={y + h + 8} x2={x} y2={y + h + 16} stroke="var(--ink)" strokeWidth="1.3" />
      <line x1={x + w} y1={y + h + 8} x2={x + w} y2={y + h + 16} stroke="var(--ink)" strokeWidth="1.3" />
      <text x={x + w / 2} y={y + h + 24} textAnchor="middle" fontSize="9.5" fontWeight="700" fontFamily="var(--font-jetbrains)" fill="var(--ink)">
        {frontageM ? fmtM(front) : `≈ ${fmtM(front)}`}
      </text>
      {/* cotes : profondeur */}
      <line x1={x - 10} y1={y} x2={x - 10} y2={y + h} stroke="var(--ink)" strokeWidth="1.3" />
      <line x1={x - 14} y1={y} x2={x - 6} y2={y} stroke="var(--ink)" strokeWidth="1.3" />
      <line x1={x - 14} y1={y + h} x2={x - 6} y2={y + h} stroke="var(--ink)" strokeWidth="1.3" />
      <text x={x - 16} y={y + h / 2} textAnchor="middle" fontSize="9.5" fontWeight="700" fontFamily="var(--font-jetbrains)" fill="var(--ink)" transform={`rotate(-90 ${x - 16} ${y + h / 2})`}>
        ≈ {fmtM(depth)}
      </text>
      {/* rue */}
      <line x1="8" y1="136" x2="182" y2="136" stroke="var(--ink)" strokeWidth="2.2" />
      <text x="95" y="147" textAnchor="middle" fontSize="8.5" fontWeight="700" fontFamily="var(--font-jetbrains)" fill="var(--ink-3)" letterSpacing="3">
        {lang === "fr" ? "RUE" : "STREET"}
      </text>
    </svg>
  );
}

/* ------------------------- composition de la valeur ------------------------- */
export function ValueSplit({
  land,
  building,
  previous,
  total,
  lang,
  labels,
}: {
  land: number | null;
  building: number | null;
  previous: number | null;
  total: number | null;
  lang: string;
  labels: { land: string; building: string; previous: string };
}) {
  const [on, setOn] = useState(false);
  useEffect(() => {
    const id = setTimeout(() => setOn(true), 80);
    return () => clearTimeout(id);
  }, []);
  const l = land ?? 0;
  const b = building ?? 0;
  const sum = l + b;
  if (sum <= 0) return null;
  const lPct = (l / sum) * 100;
  const fmt = (v: number) =>
    new Intl.NumberFormat(lang === "fr" ? "fr-CA" : "en-CA", { style: "currency", currency: "CAD", maximumFractionDigits: 0 }).format(v);
  const delta = previous && previous > 0 && total ? ((total / previous - 1) * 100) : null;
  return (
    <div>
      <div className="flex h-9 w-full overflow-hidden rounded-md border-[1.5px] border-ink">
        {lPct >= 1 && (
          <div
            className="flex items-center justify-center overflow-hidden bg-green transition-[width] duration-1000 ease-out"
            style={{ width: on ? `${lPct}%` : "2%" }}
          >
            {lPct >= 14 && (
              <span className="vp-mono px-1 text-[10px] font-bold text-paper">{Math.round(lPct)} %</span>
            )}
          </div>
        )}
        {lPct <= 99 && (
          <div className="flex flex-1 items-center justify-center overflow-hidden bg-ink">
            {100 - lPct >= 14 && (
              <span className="vp-mono px-1 text-[10px] font-bold text-lime">{Math.round(100 - lPct)} %</span>
            )}
          </div>
        )}
      </div>
      <div className="vp-mono mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-[10.5px] uppercase tracking-[0.05em]">
        <span className="flex items-center gap-1.5 text-ink-2">
          <span className="inline-block h-2.5 w-2.5 rounded-[2px] border border-ink bg-green" />
          {labels.land} {fmt(l)}
        </span>
        <span className="flex items-center gap-1.5 text-ink-2">
          <span className="inline-block h-2.5 w-2.5 rounded-[2px] border border-ink bg-ink" />
          {labels.building} {fmt(b)}
        </span>
      </div>
      {delta != null && (
        <p className="vp-mono mt-2.5 text-[10.5px] uppercase tracking-[0.05em] text-ink-3">
          {labels.previous} {fmt(previous!)}{" "}
          <span className={`ml-1 rounded-[4px] border border-ink px-1.5 py-0.5 font-bold ${delta >= 0 ? "bg-lime text-ink" : "bg-danger text-white"}`}>
            {delta >= 0 ? "+" : ""}
            {delta.toFixed(1)} %
          </span>
        </p>
      )}
    </div>
  );
}

/* -------------------------------- sparkline -------------------------------- */
export function Sparkline({
  history,
  height = 44,
}: {
  history: { year: number; value: number | null }[];
  height?: number;
}) {
  const pts = history.filter((h) => h.value != null) as { year: number; value: number }[];
  if (pts.length < 2) return null;
  const W = 132;
  const min = Math.min(...pts.map((p) => p.value));
  const max = Math.max(...pts.map((p) => p.value));
  const span = max - min || 1;
  const xy = pts.map((p, i) => [
    6 + (i / (pts.length - 1)) * (W - 12),
    height - 8 - ((p.value - min) / span) * (height - 18),
  ]);
  const line = xy.map(([x, y]) => `${x},${y}`).join(" ");
  const area = `${xy[0][0]},${height - 4} ${line} ${xy[xy.length - 1][0]},${height - 4}`;
  const growth = ((pts[pts.length - 1].value / pts[0].value - 1) * 100);
  const [x2, y2] = xy[xy.length - 1];
  return (
    <div className="flex items-end gap-2">
      <svg viewBox={`0 0 ${W} ${height}`} width={W} height={height} aria-label={`Évolution ${pts[0].year}-${pts[pts.length - 1].year}`}>
        <polygon points={area} fill="var(--lime-soft)" />
        <polyline points={line} fill="none" stroke="var(--green)" strokeWidth="2.2" strokeLinejoin="round" strokeLinecap="round" />
        {xy.slice(0, -1).map(([x, y], i) => (
          <circle key={i} cx={x} cy={y} r="2" fill="var(--surface)" stroke="var(--ink)" strokeWidth="1.1" />
        ))}
        <circle cx={x2} cy={y2} r="3.6" fill="var(--lime)" stroke="var(--ink)" strokeWidth="1.5" />
      </svg>
      <span className={`vp-mono mb-0.5 rounded-[4px] border border-ink px-1.5 py-0.5 text-[10px] font-bold ${growth >= 0 ? "bg-lime text-ink" : "bg-danger text-white"}`}>
        {growth >= 0 ? "+" : ""}
        {growth.toFixed(0)} %
      </span>
    </div>
  );
}

/* ------------------------------ frise temporelle ------------------------------ */
export function EraLine({ year, lang }: { year: number | null; lang: string }) {
  if (!year || year < 1750) return null;
  const min = Math.min(1900, Math.floor((year - 10) / 50) * 50);
  const max = 2026;
  const frac = Math.max(0, Math.min(1, (year - min) / (max - min)));
  const W = 190;
  const x = 12 + frac * (W - 24);
  const age = 2026 - year;
  return (
    <svg viewBox={`0 0 ${W} 40`} className="w-full max-w-[210px]" aria-label={`Construction ${year}`}>
      <line x1="12" y1="24" x2={W - 12} y2="24" stroke="var(--ink)" strokeWidth="1.6" />
      {[...new Set([min, Math.round((min + max) / 2 / 50) * 50, 2000])].map((yr) => {
        const gx = 12 + ((yr - min) / (max - min)) * (W - 24);
        return (
          <g key={yr}>
            <line x1={gx} y1="20" x2={gx} y2="28" stroke="var(--ink)" strokeWidth="1.2" opacity="0.5" />
            <text x={gx} y="38" textAnchor="middle" fontSize="7.5" fontFamily="var(--font-jetbrains)" fill="var(--ink-3)">
              {yr}
            </text>
          </g>
        );
      })}
      <g transform={`translate(${x}, 24)`}>
        <rect x="-7" y="-7" width="14" height="14" transform="rotate(45)" fill="var(--lime)" stroke="var(--ink)" strokeWidth="1.6" />
        <text y="-13" textAnchor="middle" fontSize="9.5" fontWeight="700" fontFamily="var(--font-jetbrains)" fill="var(--ink)">
          {year}
        </text>
      </g>
      <text x={W - 12} y="12" textAnchor="end" fontSize="7.5" fontFamily="var(--font-jetbrains)" fill="var(--ink-3)">
        {age} {lang === "fr" ? "ANS" : "YRS"}
      </text>
    </svg>
  );
}
