/** ISCC Cover Match — served by iscc-covers FastAPI backend. */

const { useState, useEffect, useRef, useCallback } = React;

// ─── API Endpoint ──────────────────────────────────────────────────────
const API_ISCC = "/iscc";
const API_TITLES = "/search/titles";

// ─── ISCC Brand Constants ──────────────────────────────────────────────
const B = {
  blue: "#0054b2",
  navy: "#123663",
  yellow: "#ffc300",
  coral: "#f56169",
  lime: "#a6db50",
  sky: "#4596f5",
  cyan: "#7ac2f7",
  white: "#ffffff",
  offWhite: "#f8f9fa",
  pageBg: "#f4f6f8",
  lightGray: "#e9ecef",
  medGray: "#6c757d",
  darkGray: "#343a40",
  nearBlack: "#212529",
};

const ISCC_LOGO = "logo.png";
const AMLET_LOGO = "amlet-logo.png";

const PROCESSING_STEPS = [
  { key: "upload", label: "Uploading image", detail: "Transferring to server…" },
  { key: "extract_title", label: "Extracting title", detail: "Running vision model…" },
  { key: "generate_iscc", label: "Generating ISCC", detail: "Computing fingerprint…" },
  { key: "search", label: "Querying index", detail: "Searching 3M covers…" },
  { key: "ranking", label: "Ranking results", detail: "Applying TSR scoring…" },
];
const STEP_INDEX = PROCESSING_STEPS.reduce((acc, s, i) => { acc[s.key] = i; return acc; }, {});

// ─── Helpers ───────────────────────────────────────────────────────────

function classifyScore(score) {
  if (score >= 0.95) return "exact";
  if (score >= 0.9) return "near-duplicate";
  if (score >= 0.8) return "similar";
  return "related";
}

const UNIT_INFO = {
  META: { color: B.sky, icon: "M", name: "Meta-Code" },
  SEMANTIC: { color: B.lime, icon: "S", name: "Semantic-Code" },
  CONTENT: { color: B.blue, icon: "C", name: "Content-Code" },
  DATA: { color: B.cyan, icon: "D", name: "Data-Code" },
  INSTANCE: { color: B.navy, icon: "I", name: "Instance-Code" },
};

function unitDisplayInfo(readable) {
  const type = readable.split("-")[0];
  return (
    UNIT_INFO[type] || {
      color: B.medGray,
      icon: "?",
      name: readable.split("-")[0] + "-Code",
    }
  );
}

function findUnitScore(perUnit, unitType) {
  if (!perUnit) return null;
  return perUnit[unitType] !== undefined ? perUnit[unitType] : null;
}

// ─── TSR: Tiered Semantic Ranking (per tsr-spec.md) ────────────────────
// Preset: adaptation — weights SEMANTIC and CONTENT for visual cover match.
const TSR_TAU_HIGH = 0.85;
const TSR_TAU_SOFT = 0.70;
const TSR_TAU_WEAK = 0.60;
const TSR_WEIGHTS = { M: 0.5, S: 2.0, C: 0.8, D: 0.3, I: 0.5 };
const TSR_TIER_ORDER = { T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T0: 8 };
const TSR_TIER_STATUS = {
  T1: "exact",
  T2: "near-duplicate",
  T3: "near-duplicate",
  T4: "similar",
  T5: "corroborated",
  T6: "title-match",
  T7: "weak",
  T0: "incoherent",
};
const TSR_TIER_LABEL = {
  T1: "EXACT",
  T2: "NEAR-IDENTICAL",
  T3: "RE-ENCODED",
  T4: "ADAPTATION",
  T5: "CORROBORATED",
  T6: "META-ONLY",
  T7: "ISOLATED",
  T0: "INCOHERENT",
};
// Per §8.6: tier-based score caps. T5 corroborated matches earn their score;
// T6 meta-only and T7 isolated are clamped because their evidence is
// categorically weaker than a multi-signal or high-confidence match.
const TSR_TIER_CAP = {
  T0: 0.0,
  T1: 1.0, T2: 1.0, T3: 1.0, T4: 1.0,
  T5: 1.0,
  T6: TSR_TAU_SOFT,
  T7: TSR_TAU_SOFT * 0.9,
};
// Per §8.7: when a T7 candidate has a single MainType above τ_soft, scale
// its raw score by the reliability of that signal type before capping.
// Content-Code is penalised hardest because perceptual-hash coincidences on
// layout/tonal structure are common in a cover-match corpus.
const TSR_ISOLATED_PENALTY = {
  SEMANTIC: 1.00,
  DATA: 0.95,
  META: 0.90,
  CONTENT: 0.80,
  INSTANCE: 1.00,
};

function tsrAggregate(queryMainTypes, typesDict) {
  var agg = {};
  for (var i = 0; i < queryMainTypes.length; i++) {
    var mt = queryMainTypes[i];
    var maxScore = null;
    for (var key in typesDict) {
      if (key.indexOf(mt + "_") === 0) {
        if (maxScore === null || typesDict[key] > maxScore) maxScore = typesDict[key];
      }
    }
    if (maxScore !== null) agg[mt] = maxScore;
  }
  return agg;
}

function tsrClassifyTier(agg) {
  var sI = agg.INSTANCE, sD = agg.DATA, sC = agg.CONTENT;
  var sS = agg.SEMANTIC, sM = agg.META;
  if (sI !== undefined && sI >= TSR_TAU_HIGH && (
      (sD !== undefined && sD < TSR_TAU_SOFT) ||
      (sC !== undefined && sC < TSR_TAU_SOFT)
  )) return "T0";
  if (sI !== undefined && sI >= TSR_TAU_HIGH) return "T1";
  if (sD !== undefined && sD >= TSR_TAU_HIGH && (sC === undefined || sC >= TSR_TAU_HIGH)) return "T2";
  var countSoft = 0, countWeak = 0;
  for (var k in agg) {
    if (agg[k] >= TSR_TAU_SOFT) countSoft++;
    if (agg[k] >= TSR_TAU_WEAK) countWeak++;
  }
  // T3 requires C-high AND at least one other unit at τ_soft (countSoft ≥ 2).
  // Content-Code alone is too noisy on templated corpora (book covers) to promote.
  if (sC !== undefined && sC >= TSR_TAU_HIGH && countSoft >= 2) return "T3";
  if (sS !== undefined && sS >= TSR_TAU_HIGH) return "T4";
  // T5: primary signal at τ_soft AND a distinct secondary at the weaker τ_weak floor.
  if (countSoft >= 1 && countWeak >= 2) return "T5";
  if (sM !== undefined && sM >= TSR_TAU_HIGH && countWeak === 1) return "T6";
  if (countSoft === 1) return "T7";
  return null;
}

function tsrWeightedScore(agg) {
  var wMap = {
    META: TSR_WEIGHTS.M, SEMANTIC: TSR_WEIGHTS.S, CONTENT: TSR_WEIGHTS.C,
    DATA: TSR_WEIGHTS.D, INSTANCE: TSR_WEIGHTS.I,
  };
  var num = 0, den = 0;
  for (var k in agg) {
    if (wMap[k] > 0) {
      num += wMap[k] * agg[k];
      den += wMap[k];
    }
  }
  var base = den > 0 ? num / den : 0;
  var intrinsic = [];
  ["INSTANCE", "DATA", "CONTENT", "SEMANTIC"].forEach(function(k) {
    if (agg[k] !== undefined) intrinsic.push(agg[k]);
  });
  var alpha = 1.0;
  if (intrinsic.length >= 2) {
    var allHigh = intrinsic.every(function(v) { return v >= TSR_TAU_HIGH; });
    var allLow = intrinsic.every(function(v) { return v < TSR_TAU_SOFT; });
    if (allHigh || allLow) alpha = 1.05;
  }
  return Math.min(1.0, base * alpha);
}

function tsrRank(queryMainTypes, candidates) {
  var ranked = [];
  for (var i = 0; i < candidates.length; i++) {
    var cand = candidates[i];
    var agg = tsrAggregate(queryMainTypes, cand.types || {});
    if (Object.keys(agg).length === 0) continue;
    var tier = tsrClassifyTier(agg);
    if (tier === null) continue;
    var score;
    if (tier === "T0") score = 0.0;
    else if (tier === "T1") score = 1.0;
    else {
      var raw = tsrWeightedScore(agg);
      if (tier === "T7") {
        var sole = null;
        for (var sk in agg) {
          if (agg[sk] >= TSR_TAU_SOFT) { sole = sk; break; }
        }
        if (sole && TSR_ISOLATED_PENALTY[sole] !== undefined) {
          raw = raw * TSR_ISOLATED_PENALTY[sole];
        }
      }
      score = Math.min(raw, TSR_TIER_CAP[tier]);
    }
    ranked.push(Object.assign({}, cand, {
      tier: tier,
      score: score,
      per_unit: agg,
      status: TSR_TIER_STATUS[tier],
    }));
  }
  ranked.sort(function(a, b) {
    var ta = TSR_TIER_ORDER[a.tier] - TSR_TIER_ORDER[b.tier];
    if (ta !== 0) return ta;
    if (b.score !== a.score) return b.score - a.score;
    var keys = ["INSTANCE", "DATA", "CONTENT", "SEMANTIC", "META"];
    for (var n = 0; n < keys.length; n++) {
      var av = a.per_unit[keys[n]] !== undefined ? a.per_unit[keys[n]] : -1;
      var bv = b.per_unit[keys[n]] !== undefined ? b.per_unit[keys[n]] : -1;
      if (bv !== av) return bv - av;
    }
    return 0;
  });
  return ranked;
}

function formatFileSize(bytes) {
  if (bytes < 1024) return bytes + " B";
  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
  return (bytes / (1024 * 1024)).toFixed(1) + " MB";
}

function formatMs(ms) {
  if (ms == null) return "";
  if (ms < 1000) return ms + " ms";
  return (ms / 1000).toFixed(2) + " s";
}

// Map unit dict keys to display-compatible array
const UNIT_KEY_TO_TYPE = {
  iscc_meta: "META",
  iscc_semantic: "SEMANTIC",
  iscc_content: "CONTENT",
  iscc_data: "DATA",
  iscc_instance: "INSTANCE",
};

function unitsToDisplayArray(unitsDict) {
  if (!unitsDict) return null;
  var arr = [];
  for (var key in UNIT_KEY_TO_TYPE) {
    if (unitsDict[key]) {
      arr.push({
        readable: UNIT_KEY_TO_TYPE[key] + "-CODE",
        iscc_unit: unitsDict[key],
      });
    }
  }
  return arr.length > 0 ? arr : null;
}

// ─── API Functions ─────────────────────────────────────────────────────

async function generateIscc(imageBytes, filename, onStep) {
  const t0 = performance.now();
  const blob = new Blob([imageBytes]);
  const form = new FormData();
  form.append("file", blob, filename);
  const resp = await fetch(API_ISCC, { method: "POST", body: form });
  if (!resp.ok) throw new Error("ISCC generation failed (" + resp.status + ")");

  const reader = resp.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let result = null;
  let uploadReported = false;

  const handleLine = (line) => {
    const trimmed = line.trim();
    if (!trimmed) return;
    const evt = JSON.parse(trimmed);
    if (!uploadReported) {
      onStep("upload", Math.round(performance.now() - t0));
      uploadReported = true;
      // Server emits a bare {"step":"upload"} marker first; ignore it
      // so we keep the client-measured network time.
      if (evt.step === "upload") return;
    }
    if (evt.event === "result") result = evt.data;
    else if (evt.event === "error") throw new Error(evt.detail || "Server error");
    else if (evt.step) onStep(evt.step, evt.ms);
  };

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const parts = buffer.split("\n");
    buffer = parts.pop();
    for (const part of parts) handleLine(part);
  }
  if (buffer.trim()) handleLine(buffer);
  if (!result) throw new Error("No result received");
  return result;
}

async function fetchImageAsBytes(url) {
  var resp;
  try {
    resp = await fetch(url);
  } catch (_) {
    throw new Error(
      "Cannot fetch image from this URL (likely blocked by CORS). " +
        "Please download the image and drag it here instead.",
    );
  }
  if (!resp.ok) throw new Error("Failed to fetch image (" + resp.status + ")");
  const blob = await resp.blob();
  const filename = url.split("/").pop().split("?")[0] || "image.jpg";
  const arrayBuf = await blob.arrayBuffer();
  return { bytes: new Uint8Array(arrayBuf), filename, blob };
}

// ─── Placeholder Cover SVG ─────────────────────────────────────────────
function CoverPlaceholder({ seed, w = 72, h = 100 }) {
  const hue = (seed * 53 + 200) % 360;
  return (
    <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
      <rect width={w} height={h} rx="4" fill={`hsl(${hue}, 20%, 93%)`} />
      <rect
        x="8"
        y="12"
        width={w - 16}
        height="5"
        rx="2"
        fill={`hsl(${hue}, 25%, 80%)`}
      />
      <rect
        x="8"
        y="22"
        width={w - 28}
        height="3"
        rx="1.5"
        fill={`hsl(${hue}, 15%, 86%)`}
      />
      <rect
        x="10"
        y="34"
        width={w - 20}
        height={h - 52}
        rx="3"
        fill={`hsl(${hue}, 12%, 88%)`}
      />
      <rect
        x="8"
        y={h - 14}
        width={w - 30}
        height="3"
        rx="1.5"
        fill={`hsl(${hue}, 15%, 82%)`}
      />
    </svg>
  );
}

// ─── Hex Spinner ───────────────────────────────────────────────────────
function HexSpinner({ size = 120, spinning = false, progress = 0 }) {
  const hex = (cx, cy, r) => {
    const p = [];
    for (let i = 0; i < 6; i++) {
      const a = (Math.PI / 3) * i - Math.PI / 2;
      p.push(`${cx + r * Math.cos(a)},${cy + r * Math.sin(a)}`);
    }
    return p.join(" ");
  };
  const c = size / 2;
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
      <g
        style={{
          transformOrigin: "center",
          animation: spinning ? "spin 3s linear infinite" : "none",
        }}
      >
        <polygon
          points={hex(c, c, c * 0.85)}
          fill="none"
          stroke={B.blue}
          strokeWidth="2"
          opacity={0.35 + progress * 0.5}
        />
        <polygon
          points={hex(c, c, c * 0.65)}
          fill="none"
          stroke={B.sky}
          strokeWidth="1.5"
          opacity={0.25 + progress * 0.35}
          style={{
            transformOrigin: "center",
            animation: spinning ? "spin-reverse 5s linear infinite" : "none",
          }}
        />
        <polygon
          points={hex(c, c, c * 0.45)}
          fill="none"
          stroke={B.cyan}
          strokeWidth="1"
          opacity={0.2 + progress * 0.25}
        />
      </g>
    </svg>
  );
}

// ─── Scan Line ─────────────────────────────────────────────────────────
function ScanLine({ active }) {
  if (!active) return null;
  return (
    <div
      style={{
        position: "absolute",
        left: 0,
        right: 0,
        height: "2px",
        background: B.blue,
        animation: "scanDown 2s ease-in-out infinite",
        zIndex: 10,
        boxShadow: `0 0 6px 1px ${B.sky}50`,
      }}
    />
  );
}

// ─── Processing Steps ──────────────────────────────────────────────────
function ProcessingSteps({ currentStep, steps, timings, outcome }) {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: "6px",
        width: "100%",
      }}
    >
      {steps.map((step, i) => {
        const isActive = i === currentStep;
        const isDone = i < currentStep;
        const ms = timings && timings[step.key];
        return (
          <div
            key={step.key}
            style={{
              display: "flex",
              alignItems: "center",
              gap: "10px",
              padding: "6px 12px",
              borderRadius: "8px",
              background: isActive ? `${B.blue}06` : B.white,
              border: `1px solid ${isActive ? B.blue + "30" : isDone ? "#2e8b57" + "25" : B.lightGray}`,
              transition: "all 0.4s ease",
              opacity: i > currentStep + 1 ? 0.35 : 1,
            }}
          >
            <div
              style={{
                width: "18px",
                height: "18px",
                borderRadius: "50%",
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                fontSize: "10px",
                fontFamily: "'JetBrains Mono', Consolas, monospace",
                fontWeight: 600,
                background: isDone
                  ? "#2e8b57"
                  : isActive
                    ? B.blue
                    : B.lightGray,
                color: isDone || isActive ? B.white : B.medGray,
                transition: "all 0.3s ease",
                flexShrink: 0,
              }}
            >
              {i + 1}
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div
                style={{
                  fontSize: "12.5px",
                  fontWeight: isActive ? 600 : 400,
                  color: isDone
                    ? "#2e8b57"
                    : isActive
                      ? B.nearBlack
                      : B.medGray,
                }}
              >
                {step.label}
              </div>
              {isActive && step.detail && (
                <div
                  style={{
                    fontSize: "10.5px",
                    color: B.sky,
                    fontFamily: "'JetBrains Mono', Consolas, monospace",
                    marginTop: "1px",
                    animation: "pulse 1.5s ease-in-out infinite",
                  }}
                >
                  {step.detail}
                </div>
              )}
            </div>
            {isDone && ms != null && (
              <div
                style={{
                  fontSize: "10.5px",
                  fontFamily: "'JetBrains Mono', Consolas, monospace",
                  color: B.medGray,
                  flexShrink: 0,
                }}
              >
                {formatMs(ms)}
              </div>
            )}
            {isActive && (
              <div
                style={{
                  width: "14px",
                  height: "14px",
                  borderRadius: "50%",
                  border: `2px solid ${B.blue}`,
                  borderTopColor: "transparent",
                  animation: "spin 0.8s linear infinite",
                  flexShrink: 0,
                }}
              />
            )}
          </div>
        );
      })}
      {outcome && (
        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: "10px",
            padding: "6px 12px",
            borderRadius: "8px",
            background: B.white,
            border: `1px solid ${outcome.ok ? "#2e8b57" + "25" : B.lightGray}`,
            transition: "all 0.4s ease",
          }}
        >
          <div
            style={{
              width: "18px",
              height: "18px",
              borderRadius: "50%",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              fontSize: "10px",
              fontFamily: "'JetBrains Mono', Consolas, monospace",
              fontWeight: 600,
              background: outcome.ok ? "#2e8b57" : B.medGray,
              color: B.white,
              flexShrink: 0,
            }}
          >
            {steps.length + 1}
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div
              style={{
                fontSize: "12.5px",
                fontWeight: 400,
                color: outcome.ok ? "#2e8b57" : B.medGray,
              }}
            >
              {outcome.label}
            </div>
          </div>
          {outcome.value != null && (
            <div
              style={{
                fontSize: "10.5px",
                fontFamily: "'JetBrains Mono', Consolas, monospace",
                color: B.medGray,
                flexShrink: 0,
              }}
            >
              {outcome.value}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ─── ISCC Code Display ─────────────────────────────────────────────────
function IsccCodeDisplay({ units, animate, unitScores }) {
  return (
    <div
      style={{
        background: B.white,
        borderRadius: "8px",
        padding: "12px 14px",
        border: `1px solid ${B.lightGray}`,
      }}
    >
      <div
        style={{
          fontSize: "9.5px",
          color: B.medGray,
          textTransform: "uppercase",
          letterSpacing: "0.08em",
          fontWeight: 600,
          marginBottom: "8px",
        }}
      >
        Best Match
      </div>
      {units && units.length > 0 && (
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            gap: "5px",
          }}
        >
          {units.map((unit, i) => {
            const info = unitDisplayInfo(unit.readable);
            const type = unit.readable.split("-")[0];
            const score = findUnitScore(unitScores, type);
            return (
              <div
                key={unit.iscc_unit}
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "8px",
                  animation: animate
                    ? `fadeSlideUp 0.3s ease ${i * 0.08}s both`
                    : "none",
                }}
              >
                <div
                  style={{
                    width: "14px",
                    height: "14px",
                    borderRadius: "2px",
                    background: info.color,
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    fontSize: "7px",
                    fontWeight: 700,
                    color: B.white,
                    flexShrink: 0,
                  }}
                >
                  {info.icon}
                </div>
                <span
                  style={{
                    fontSize: "10px",
                    color: B.nearBlack,
                    fontFamily: "'JetBrains Mono', Consolas, monospace",
                    fontWeight: 500,
                    width: "95px",
                    flexShrink: 0,
                  }}
                >
                  {info.name}
                </span>
                {score !== null && (
                  <React.Fragment>
                    <div
                      style={{
                        flex: 1,
                        height: "3px",
                        borderRadius: "2px",
                        background: B.lightGray,
                        overflow: "hidden",
                      }}
                    >
                      <div
                        style={{
                          width: `${score * 100}%`,
                          height: "100%",
                          borderRadius: "2px",
                          background: info.color,
                          transition: "width 0.8s cubic-bezier(.4,0,.2,1)",
                        }}
                      />
                    </div>
                    <span
                      style={{
                        fontSize: "10px",
                        fontFamily: "'JetBrains Mono', Consolas, monospace",
                        fontWeight: 600,
                        color: B.nearBlack,
                        width: "42px",
                        textAlign: "right",
                        flexShrink: 0,
                      }}
                    >
                      {(score * 100).toFixed(1)}%
                    </span>
                  </React.Fragment>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ─── Similarity Bar ────────────────────────────────────────────────────
function SimilarityBar({ value, status }) {
  const color =
    status === "exact"
      ? "#2e8b57"
      : status === "near-duplicate"
        ? "#c89200"
        : status === "similar"
          ? B.sky
          : status === "corroborated"
            ? B.cyan
            : B.medGray;
  return (
    <div>
      <div
        style={{
          display: "flex",
          justifyContent: "space-between",
          marginBottom: "3px",
        }}
      >
        <span
          style={{
            fontSize: "10px",
            color,
            fontFamily: "'JetBrains Mono', Consolas, monospace",
            fontWeight: 600,
            textTransform: "uppercase",
            letterSpacing: "0.06em",
          }}
        >
          {status}
        </span>
        <span
          style={{
            fontSize: "12px",
            color: B.nearBlack,
            fontFamily: "'JetBrains Mono', Consolas, monospace",
            fontWeight: 700,
          }}
        >
          {(value * 100).toFixed(1)}%
        </span>
      </div>
      <div
        style={{
          height: "3px",
          borderRadius: "2px",
          background: B.lightGray,
          overflow: "hidden",
        }}
      >
        <div
          style={{
            width: `${value * 100}%`,
            height: "100%",
            borderRadius: "2px",
            background: color,
            transition: "width 0.8s cubic-bezier(.4,0,.2,1)",
          }}
        />
      </div>
    </div>
  );
}

// ─── Per-Unit Score Dots (compact inline breakdown) ────────────────────
const UNIT_DOT_ORDER = ["META", "SEMANTIC", "CONTENT", "DATA", "INSTANCE"];

function UnitScoreDots({ perUnit }) {
  if (!perUnit) return null;
  const present = UNIT_DOT_ORDER.filter((t) => perUnit[t] !== undefined);
  if (present.length === 0) return null;
  return (
    <div
      style={{
        display: "flex",
        gap: "4px",
        marginTop: "6px",
        alignItems: "center",
        flexWrap: "wrap",
      }}
      aria-label="Per-unit match scores"
    >
      {present.map((type) => {
        const info = UNIT_INFO[type];
        const score = Math.max(0, Math.min(1, perUnit[type]));
        const pct = Math.round(score * 100);
        const opacity = 0.55 + 0.45 * score;
        return (
          <div
            key={type}
            aria-label={`${info.name}: ${pct}%`}
            style={{
              height: "15px",
              padding: "0 5px",
              borderRadius: "3px",
              background: info.color,
              opacity: opacity,
              display: "inline-flex",
              alignItems: "center",
              gap: "4px",
              fontSize: "9px",
              fontWeight: 700,
              color: B.white,
              fontFamily: "'JetBrains Mono', Consolas, monospace",
              lineHeight: 1,
              flexShrink: 0,
              letterSpacing: "0.02em",
            }}
          >
            <span>{info.icon}</span>
            <span>{pct}</span>
          </div>
        );
      })}
    </div>
  );
}

// ─── Result Card ───────────────────────────────────────────────────────
function ResultCard({ result, index, visible }) {
  const [imgOk, setImgOk] = useState(false);
  const bdr =
    result.status === "exact"
      ? "#2e8b57" + "40"
      : result.status === "near-duplicate"
        ? "#c89200" + "30"
        : result.status === "corroborated"
          ? B.cyan + "40"
          : B.lightGray;
  const coverSrc = result.metadata && result.metadata.source;
  const title = (result.metadata && result.metadata.name) || "Unknown Title";
  const subtitle = [
    result.metadata && result.metadata.isbn
      ? "ISBN: " + result.metadata.isbn
      : null,
    result.metadata && result.metadata.source_row_id
      ? "ASIN: " + result.metadata.source_row_id
      : null,
    result.metadata && result.metadata.publisher,
  ]
    .filter(Boolean)
    .join(" · ");

  return (
    <div
      style={{
        display: "flex",
        gap: "12px",
        padding: "12px",
        borderRadius: "8px",
        background: B.white,
        border: `1px solid ${bdr}`,
        opacity: visible ? 1 : 0,
        transform: visible ? "translateY(0)" : "translateY(12px)",
        transition: `all 0.4s ease ${index * 0.08}s`,
      }}
    >
      <div
        style={{
          width: "68px",
          height: "94px",
          borderRadius: "5px",
          overflow: "hidden",
          flexShrink: 0,
          background: B.offWhite,
          position: "relative",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
        }}
      >
        {coverSrc && (
          <img
            src={coverSrc}
            alt=""
            referrerPolicy="no-referrer"
            onLoad={(e) => {
              var w = e.target.naturalWidth;
              var h = e.target.naturalHeight;
              setImgOk(w > 10 && h > 10);
            }}
            onError={() => setImgOk(false)}
            style={{
              width: "100%",
              height: "100%",
              objectFit: "contain",
              display: imgOk ? "block" : "none",
            }}
          />
        )}
        {!imgOk && <CoverPlaceholder seed={index} w={68} h={94} />}
        {result.status === "exact" && (
          <div
            style={{
              position: "absolute",
              top: "3px",
              right: "3px",
              width: "16px",
              height: "16px",
              borderRadius: "50%",
              background: "#2e8b57",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              fontSize: "9px",
              fontWeight: 800,
              color: B.white,
            }}
          >
            ✓
          </div>
        )}
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div
          style={{
            fontSize: "13px",
            fontWeight: 600,
            color: B.nearBlack,
            marginBottom: "1px",
            whiteSpace: "nowrap",
            overflow: "hidden",
            textOverflow: "ellipsis",
          }}
        >
          {title}
        </div>
        <div
          style={{ fontSize: "11.5px", color: B.medGray, marginBottom: "4px" }}
        >
          {subtitle || "\u00A0"}
        </div>
        <div
          style={{
            fontSize: "9.5px",
            fontFamily: "'JetBrains Mono', Consolas, monospace",
            color: B.blue,
            marginBottom: "7px",
            letterSpacing: "0.04em",
            whiteSpace: "nowrap",
            overflow: "hidden",
            textOverflow: "ellipsis",
          }}
        >
          {result.iscc_id}
        </div>
        <SimilarityBar value={result.score} status={result.status} />
        <UnitScoreDots perUnit={result.per_unit} />
      </div>
    </div>
  );
}

// ─── Query Image Display ───────────────────────────────────────────────
function QueryImage({ src, thumbnail, processing, interactive, dragOver }) {
  const [imgOk, setImgOk] = useState(false);
  const [hovering, setHovering] = useState(false);
  const displaySrc = imgOk || !thumbnail ? src : thumbnail;

  useEffect(() => {
    setImgOk(false);
  }, [src]);

  useEffect(() => {
    if (!interactive) setHovering(false);
  }, [interactive]);

  return (
    <div
      onMouseEnter={interactive ? () => setHovering(true) : undefined}
      onMouseLeave={interactive ? () => setHovering(false) : undefined}
      style={{
        position: "relative",
        borderRadius: "8px",
        overflow: "hidden",
        border: `1px solid ${processing ? B.blue + "40" : dragOver ? B.blue : B.lightGray}`,
        background: B.white,
        minHeight: "180px",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        transition: "border-color 0.3s ease",
        cursor: interactive ? "pointer" : "default",
      }}
    >
      <ScanLine active={processing} />
      {src && (
        <img
          src={src}
          alt=""
          onLoad={() => setImgOk(true)}
          onError={() => setImgOk(false)}
          style={{
            width: "100%",
            display: imgOk ? "block" : "none",
            maxHeight: "360px",
            objectFit: "contain",
          }}
        />
      )}
      {!imgOk && thumbnail && (
        <img
          src={thumbnail}
          alt=""
          style={{ width: "100%", maxHeight: "360px", objectFit: "contain" }}
        />
      )}
      {!imgOk && !thumbnail && (
        <div style={{ padding: "32px", textAlign: "center" }}>
          <svg width="100" height="140" viewBox="0 0 100 140">
            <rect width="100" height="140" rx="5" fill={B.lightGray} />
            <rect
              x="12"
              y="16"
              width="76"
              height="7"
              rx="3"
              fill={`${B.medGray}35`}
            />
            <rect
              x="12"
              y="28"
              width="50"
              height="4"
              rx="2"
              fill={`${B.medGray}25`}
            />
            <rect
              x="16"
              y="42"
              width="68"
              height="72"
              rx="3"
              fill={`${B.medGray}18`}
            />
            <text
              x="50"
              y="82"
              textAnchor="middle"
              fontSize="10"
              fill={B.medGray}
              fontFamily="'JetBrains Mono', Consolas, monospace"
            >
              COVER
            </text>
            <rect
              x="12"
              y="122"
              width="42"
              height="4"
              rx="2"
              fill={`${B.medGray}20`}
            />
          </svg>
          <div style={{ marginTop: "6px", fontSize: "11px", color: B.medGray }}>
            {src ? "Loading image…" : "No image"}
          </div>
        </div>
      )}
      {interactive && (dragOver || hovering) && (
        <div
          style={{
            position: "absolute",
            inset: 0,
            background: dragOver
              ? "rgba(0, 84, 178, 0.7)"
              : "rgba(0, 84, 178, 0.45)",
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
            justifyContent: "center",
            gap: "8px",
            zIndex: 20,
            pointerEvents: "none",
          }}
        >
          <svg
            width="32"
            height="32"
            viewBox="0 0 24 24"
            fill="none"
            stroke={B.white}
            strokeWidth="2"
            strokeLinecap="round"
            strokeLinejoin="round"
          >
            <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4" />
            <polyline points="17 8 12 3 7 8" />
            <line x1="12" y1="3" x2="12" y2="15" />
          </svg>
          <div
            style={{
              fontSize: "12.5px",
              color: B.white,
              fontWeight: 600,
              textShadow: "0 1px 3px rgba(0,0,0,0.3)",
            }}
          >
            {dragOver
              ? "Drop image to search"
              : "Click or drop to search another"}
          </div>
        </div>
      )}
    </div>
  );
}

// ─── Dot Grid Background ───────────────────────────────────────────────
function DotGrid() {
  return (
    <svg
      style={{
        position: "fixed",
        inset: 0,
        width: "100%",
        height: "100%",
        pointerEvents: "none",
        zIndex: 0,
      }}
    >
      <defs>
        <pattern
          id="dots"
          x="0"
          y="0"
          width="28"
          height="28"
          patternUnits="userSpaceOnUse"
        >
          <circle cx="1" cy="1" r="0.5" fill={`${B.medGray}22`} />
        </pattern>
      </defs>
      <rect width="100%" height="100%" fill="url(#dots)" />
    </svg>
  );
}

// ─── Score Threshold Slider ────────────────────────────────────────────
function ScoreThresholdSlider({ value, onChange, maxWidth }) {
  return (
    <div
      style={{
        padding: "10px 12px",
        borderRadius: "6px",
        background: B.white,
        border: `1px solid ${B.lightGray}`,
        maxWidth: maxWidth || "none",
      }}
    >
      <div
        style={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          marginBottom: "6px",
        }}
      >
        <span
          style={{
            fontSize: "9.5px",
            color: B.medGray,
            textTransform: "uppercase",
            letterSpacing: "0.08em",
            fontWeight: 600,
          }}
        >
          Score Threshold
        </span>
        <span
          style={{
            fontSize: "12px",
            fontFamily: "'JetBrains Mono', Consolas, monospace",
            fontWeight: 700,
            color: B.nearBlack,
          }}
        >
          {(value * 100).toFixed(0)}%
        </span>
      </div>
      <input
        type="range"
        min="0"
        max="100"
        value={Math.round(value * 100)}
        onChange={(e) => onChange(Number(e.target.value) / 100)}
        style={{
          width: "100%",
          height: "4px",
          appearance: "none",
          WebkitAppearance: "none",
          background: `linear-gradient(to right, ${B.blue} ${value * 100}%, ${B.lightGray} ${value * 100}%)`,
          borderRadius: "2px",
          outline: "none",
          cursor: "pointer",
        }}
      />
    </div>
  );
}

// ─── Title Typeahead Search ────────────────────────────────────────────
function TitleSearch() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const [open, setOpen] = useState(false);
  const [selected, setSelected] = useState(null);
  const timerRef = useRef(null);
  const wrapRef = useRef(null);

  useEffect(() => {
    function handleClickOutside(e) {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) {
        setOpen(false);
      }
    }
    document.addEventListener("mousedown", handleClickOutside);
    return () => document.removeEventListener("mousedown", handleClickOutside);
  }, []);

  function doSearch(q) {
    if (!q || q.trim().length < 2) {
      setResults([]);
      setOpen(false);
      return;
    }
    setLoading(true);
    fetch(API_TITLES + "?q=" + encodeURIComponent(q.trim()) + "&limit=8")
      .then((r) => r.json())
      .then((data) => {
        setResults(data.results || []);
        setOpen((data.results || []).length > 0);
        setLoading(false);
      })
      .catch(() => {
        setResults([]);
        setLoading(false);
      });
  }

  function handleInput(e) {
    var val = e.target.value;
    setQuery(val);
    setSelected(null);
    clearTimeout(timerRef.current);
    timerRef.current = setTimeout(() => doSearch(val), 200);
  }

  function handleSelect(item) {
    setSelected(item);
    setQuery(item.title || "");
    setOpen(false);
  }

  function handleClear() {
    setQuery("");
    setResults([]);
    setSelected(null);
    setOpen(false);
  }

  const googleSearchUrl = selected
    ? `https://www.google.com/search?tbm=isch&q=${encodeURIComponent(
        selected.isbn || selected.title + " book cover",
      )}`
    : null;

  return (
    <div ref={wrapRef} style={{ position: "relative" }}>
      <div style={{ position: "relative" }}>
        <div
          style={{
            position: "absolute",
            left: "12px",
            top: "50%",
            transform: "translateY(-50%)",
            color: B.medGray,
            fontSize: "14px",
            pointerEvents: "none",
            zIndex: 2,
          }}
        >
          <svg
            width="16"
            height="16"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth="2"
            strokeLinecap="round"
            strokeLinejoin="round"
          >
            <circle cx="11" cy="11" r="8" />
            <line x1="21" y1="21" x2="16.65" y2="16.65" />
          </svg>
        </div>
        <input
          type="text"
          value={query}
          onChange={handleInput}
          onFocus={() => results.length > 0 && setOpen(true)}
          placeholder="Search by title, publisher, or ISBN…"
          style={{
            width: "100%",
            padding: "11px 36px 11px 36px",
            borderRadius: "8px",
            border: `1px solid ${open ? B.blue : B.lightGray}`,
            background: B.white,
            color: B.nearBlack,
            fontSize: "13.5px",
            outline: "none",
            transition: "border-color 0.2s ease",
            boxSizing: "border-box",
          }}
        />
        {query && (
          <button
            onClick={handleClear}
            style={{
              position: "absolute",
              right: "10px",
              top: "50%",
              transform: "translateY(-50%)",
              background: "none",
              border: "none",
              color: B.medGray,
              cursor: "pointer",
              fontSize: "16px",
              padding: "2px 4px",
              lineHeight: 1,
              zIndex: 2,
            }}
          >
            ×
          </button>
        )}
        {loading && (
          <div
            style={{
              position: "absolute",
              right: query ? "32px" : "12px",
              top: "50%",
              transform: "translateY(-50%)",
              width: "14px",
              height: "14px",
              borderRadius: "50%",
              border: `2px solid ${B.lightGray}`,
              borderTopColor: B.blue,
              animation: "spin 0.6s linear infinite",
            }}
          />
        )}
      </div>

      {/* Dropdown */}
      {open && results.length > 0 && (
        <div
          style={{
            position: "absolute",
            left: 0,
            right: 0,
            top: "100%",
            marginTop: "4px",
            background: B.white,
            borderRadius: "8px",
            border: `1px solid ${B.lightGray}`,
            boxShadow: "0 8px 24px rgba(0,0,0,0.1)",
            zIndex: 100,
            maxHeight: "360px",
            overflowY: "auto",
          }}
        >
          {results.map((item, i) => (
            <button
              key={i}
              onClick={() => handleSelect(item)}
              style={{
                display: "flex",
                alignItems: "center",
                gap: "10px",
                width: "100%",
                padding: "8px 12px",
                background: "none",
                border: "none",
                borderBottom:
                  i < results.length - 1
                    ? `1px solid ${B.lightGray}`
                    : "none",
                cursor: "pointer",
                textAlign: "left",
                transition: "background 0.15s ease",
              }}
              onMouseEnter={(e) =>
                (e.currentTarget.style.background = B.offWhite)
              }
              onMouseLeave={(e) =>
                (e.currentTarget.style.background = "none")
              }
            >
              <div
                style={{
                  width: "36px",
                  height: "50px",
                  borderRadius: "3px",
                  overflow: "hidden",
                  flexShrink: 0,
                  background: B.offWhite,
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "center",
                }}
              >
                {item.image_url ? (
                  <img
                    src={item.image_url}
                    alt=""
                    referrerPolicy="no-referrer"
                    style={{
                      width: "100%",
                      height: "100%",
                      objectFit: "cover",
                    }}
                  />
                ) : (
                  <CoverPlaceholder seed={i} w={36} h={50} />
                )}
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div
                  style={{
                    fontSize: "12.5px",
                    fontWeight: 600,
                    color: B.nearBlack,
                    whiteSpace: "nowrap",
                    overflow: "hidden",
                    textOverflow: "ellipsis",
                  }}
                >
                  {item.title}
                </div>
                <div
                  style={{
                    fontSize: "11px",
                    color: B.medGray,
                    whiteSpace: "nowrap",
                    overflow: "hidden",
                    textOverflow: "ellipsis",
                  }}
                >
                  {[item.isbn ? "ISBN: " + item.isbn : null, item.publisher]
                    .filter(Boolean)
                    .join(" · ")}
                </div>
              </div>
            </button>
          ))}
        </div>
      )}

      {/* Selected confirmation */}
      {selected && (
        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: "10px",
            marginTop: "10px",
            padding: "10px 12px",
            borderRadius: "8px",
            background: "#2e8b570a",
            border: "1px solid #2e8b5725",
            animation: "fadeSlideUp 0.25s ease both",
          }}
        >
          <div
            style={{
              width: "40px",
              height: "56px",
              borderRadius: "4px",
              overflow: "hidden",
              flexShrink: 0,
              background: B.offWhite,
            }}
          >
            {selected.image_url ? (
              <img
                src={selected.image_url}
                alt=""
                referrerPolicy="no-referrer"
                style={{
                  width: "100%",
                  height: "100%",
                  objectFit: "cover",
                }}
              />
            ) : (
              <CoverPlaceholder seed={0} w={40} h={56} />
            )}
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div
              style={{
                fontSize: "12.5px",
                fontWeight: 600,
                color: B.nearBlack,
                whiteSpace: "nowrap",
                overflow: "hidden",
                textOverflow: "ellipsis",
              }}
            >
              {selected.title}
            </div>
            <div style={{ fontSize: "11px", color: B.medGray }}>
              {[
                selected.isbn ? "ISBN: " + selected.isbn : null,
                selected.publisher,
              ]
                .filter(Boolean)
                .join(" · ")}
            </div>
          </div>
          <div
            style={{
              display: "flex",
              alignItems: "center",
              gap: "5px",
              padding: "4px 10px",
              borderRadius: "14px",
              background: "#2e8b57",
              color: B.white,
              fontSize: "10.5px",
              fontWeight: 600,
              flexShrink: 0,
            }}
          >
            <span>✓</span> In index
          </div>
        </div>
      )}

      {selected && (
        <a
          href={googleSearchUrl}
          target="_blank"
          rel="noreferrer"
          onClick={(e) => {
            e.preventDefault();
            window.open(
              googleSearchUrl,
              "_blank",
              "popup=true,width=1200,height=900",
            );
          }}
          style={{
            display: "flex",
            alignItems: "center",
            gap: "12px",
            marginTop: "10px",
            padding: "10px 14px",
            borderRadius: "8px",
            background: B.offWhite,
            border: `1px solid ${B.lightGray}`,
            color: B.nearBlack,
            textDecoration: "none",
            fontSize: "12.5px",
            fontWeight: 500,
            transition: "all 0.15s ease",
            animation: "fadeSlideUp 0.25s ease both",
          }}
          onMouseEnter={(e) => {
            e.currentTarget.style.background = B.white;
            e.currentTarget.style.borderColor = B.blue;
          }}
          onMouseLeave={(e) => {
            e.currentTarget.style.background = B.offWhite;
            e.currentTarget.style.borderColor = B.lightGray;
          }}
        >
          <div
            style={{
              width: "22px",
              height: "22px",
              borderRadius: "50%",
              background: B.blue,
              color: B.white,
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              fontSize: "11px",
              fontWeight: 700,
              flexShrink: 0,
            }}
          >
            2
          </div>
          <span style={{ flex: 1 }}>
            Find cover variations on Google Images
          </span>
          <span style={{ color: B.blue, fontSize: "14px" }}>→</span>
        </a>
      )}
    </div>
  );
}

// ─── Step Card ─────────────────────────────────────────────────────────
function StepCard({ number, title, accent, children }) {
  return (
    <div
      style={{
        maxWidth: "600px",
        margin: "0 auto 20px",
        background: B.white,
        border: `1px solid ${B.lightGray}`,
        borderTop: accent ? `4px solid ${B.blue}` : `1px solid ${B.lightGray}`,
        padding: "22px 24px",
        boxShadow: "0 1px 3px rgba(0,0,0,0.03)",
      }}
    >
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: "12px",
          marginBottom: "16px",
        }}
      >
        <div
          style={{
            width: "28px",
            height: "28px",
            borderRadius: "50%",
            background: B.blue,
            color: B.white,
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            fontSize: "14px",
            fontWeight: 700,
            flexShrink: 0,
          }}
        >
          {number}
        </div>
        <div
          style={{ fontSize: "15px", fontWeight: 700, color: B.nearBlack }}
        >
          {title}
        </div>
      </div>
      {children}
    </div>
  );
}

// ─── Main App ──────────────────────────────────────────────────────────
function App() {
  const [phase, setPhase] = useState("idle");
  const [previewSrc, setPreviewSrc] = useState(null);
  const [currentStep, setCurrentStep] = useState(0);
  const [results, setResults] = useState([]);
  const [showResults, setShowResults] = useState(false);
  const [urlInput, setUrlInput] = useState("");
  const [dragOver, setDragOver] = useState(false);
  const [isccData, setIsccData] = useState(null);
  const [error, setError] = useState(null);
  const [isMobile, setIsMobile] = useState(window.innerWidth < 700);
  const [scoreThreshold, setScoreThreshold] = useState(0.65);
  const [stepTimings, setStepTimings] = useState({});
  const fileRef = useRef(null);
  const DEMO_URL = "demo.jpg";

  useEffect(() => {
    const onResize = () => setIsMobile(window.innerWidth < 700);
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);

  const processImage = useCallback(async (imageBytes, filename, previewUrl) => {
    setPreviewSrc(previewUrl);
    setPhase("processing");
    setCurrentStep(0);
    setShowResults(false);
    setResults([]);
    setIsccData(null);
    setError(null);
    setStepTimings({});

    try {
      const onStep = (key, ms) => {
        setStepTimings((prev) => ({ ...prev, [key]: ms }));
        const idx = STEP_INDEX[key];
        if (idx != null && idx + 1 < PROCESSING_STEPS.length) {
          setCurrentStep(idx + 1);
        }
      };

      const iscc = await generateIscc(imageBytes, filename, onStep);
      setIsccData(iscc);

      const rankT0 = performance.now();
      const queryMainTypes = Object.keys(UNIT_KEY_TO_TYPE)
        .filter((k) => iscc.units && iscc.units[k])
        .map((k) => UNIT_KEY_TO_TYPE[k]);
      const matches = tsrRank(
        queryMainTypes,
        (iscc.search && iscc.search.global_matches) || [],
      );
      const rankingMs = Math.round(performance.now() - rankT0);
      setStepTimings((prev) => ({ ...prev, ranking: rankingMs }));
      setCurrentStep(PROCESSING_STEPS.length);

      setPhase("results");
      setResults(matches);
      setTimeout(() => setShowResults(true), 80);
    } catch (err) {
      setError(err.message || "An unexpected error occurred");
      setPhase("error");
    }
  }, []);

  const handleFile = useCallback(
    (file) => {
      if (!file || !file.type.startsWith("image/")) return;
      const readerUrl = new FileReader();
      readerUrl.onload = (e) => {
        const previewUrl = e.target.result;
        const readerBuf = new FileReader();
        readerBuf.onload = (e2) => {
          processImage(new Uint8Array(e2.target.result), file.name, previewUrl);
        };
        readerBuf.readAsArrayBuffer(file);
      };
      readerUrl.readAsDataURL(file);
    },
    [processImage],
  );

  const handleUrlSubmit = useCallback(async () => {
    const url = urlInput.trim() || DEMO_URL;
    setPhase("processing");
    setCurrentStep(0);
    setShowResults(false);
    setResults([]);
    setIsccData(null);
    setError(null);
    setPreviewSrc(url);

    try {
      const { bytes, filename } = await fetchImageAsBytes(url);
      processImage(bytes, filename, url);
    } catch (err) {
      setError(err.message || "Failed to fetch image from URL");
      setPhase("error");
    }
  }, [urlInput, processImage]);

  const handleDrop = useCallback(
    (e) => {
      e.preventDefault();
      setDragOver(false);
      handleFile(e.dataTransfer.files && e.dataTransfer.files[0]);
    },
    [handleFile],
  );

  const handlePaste = useCallback(
    (e) => {
      const items = e.clipboardData && e.clipboardData.items;
      if (!items) return;
      for (let i = 0; i < items.length; i++) {
        if (items[i].type.startsWith("image/")) {
          e.preventDefault();
          handleFile(items[i].getAsFile());
          return;
        }
      }
    },
    [handleFile],
  );

  useEffect(() => {
    window.addEventListener("paste", handlePaste);
    return () => window.removeEventListener("paste", handlePaste);
  }, [handlePaste]);

  const reset = () => {
    setPhase("idle");
    setPreviewSrc(null);
    setResults([]);
    setShowResults(false);
    setUrlInput("");
    setCurrentStep(0);
    setIsccData(null);
    setError(null);
    setScoreThreshold(0.65);
    setStepTimings({});
  };

  const gridCols = isMobile ? "minmax(0, 1fr)" : "300px minmax(0, 1fr)";

  // Filter results by score threshold and compute category counts
  const filteredResults = results.filter((r) => r.score >= scoreThreshold);
  const resultCounts = filteredResults.reduce((acc, r) => {
    acc[r.status] = (acc[r.status] || 0) + 1;
    return acc;
  }, {});

  return (
    <div
      style={{
        minHeight: "100vh",
        background: B.pageBg,
        fontFamily: "'Readex Pro', Arial, sans-serif",
        color: B.nearBlack,
        position: "relative",
      }}
    >
      <DotGrid />

      {/* Header */}
      <header
        style={{
          position: "relative",
          zIndex: 10,
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          padding: "14px 28px",
          borderBottom: `5px solid ${B.blue}`,
          background: B.white,
          flexWrap: "wrap",
          gap: "8px",
        }}
      >
        <div
          onClick={reset}
          style={{ display: "flex", alignItems: "center", gap: "14px", cursor: "pointer" }}
        >
          <img src={ISCC_LOGO} alt="ISCC" style={{ height: "30px" }} />
          <div
            style={{ width: "1px", height: "22px", background: B.lightGray }}
          />
          <div>
            <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
              <div
                style={{
                  fontSize: "14px",
                  fontWeight: 700,
                  letterSpacing: "0.04em",
                  color: B.nearBlack,
                }}
              >
                COVER MATCH
              </div>
              <div
                style={{
                  padding: "2px 7px",
                  borderRadius: "4px",
                  background: B.yellow,
                  color: B.nearBlack,
                  fontSize: "9px",
                  fontWeight: 700,
                  letterSpacing: "0.1em",
                  textTransform: "uppercase",
                }}
              >
                Tech Demo
              </div>
            </div>
            <div
              style={{
                fontSize: "9.5px",
                color: B.medGray,
                letterSpacing: "0.09em",
                textTransform: "uppercase",
                fontWeight: 500,
              }}
            >
              ISO 24138 · Similarity Search · 3M Book Covers
            </div>
          </div>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
          <div
            style={{
              fontSize: "10.5px",
              color: B.blue,
              fontFamily: "'JetBrains Mono', Consolas, monospace",
              padding: "3px 10px",
              borderRadius: "14px",
              border: `1px solid ${B.blue}18`,
            }}
          >
            iscc/iscc-book-covers
          </div>
          <a
            href="https://huggingface.co/datasets/iscc/iscc-book-covers"
            target="_blank"
            rel="noreferrer"
            style={{
              fontSize: "10.5px",
              color: B.medGray,
              textDecoration: "none",
              padding: "3px 8px",
              borderRadius: "14px",
              border: `1px solid ${B.lightGray}`,
            }}
          >
            🤗 HuggingFace
          </a>
        </div>
      </header>

      {/* Main */}
      <main
        style={{
          position: "relative",
          zIndex: 10,
          maxWidth: "1040px",
          margin: "0 auto",
          padding: "36px 20px",
        }}
      >
        <input
          ref={fileRef}
          type="file"
          accept="image/*"
          style={{ display: "none" }}
          onChange={(e) =>
            handleFile(e.target.files && e.target.files[0])
          }
        />

        {phase === "idle" && (
          <div style={{ animation: "fadeSlideUp 0.4s ease both" }}>
            <div style={{ textAlign: "center", marginBottom: "40px" }}>
              <div
                style={{
                  fontSize: "10.5px",
                  textTransform: "uppercase",
                  letterSpacing: "0.14em",
                  color: B.medGray,
                  fontWeight: 600,
                  marginBottom: "14px",
                }}
              >
                Prepared for the{" "}
                <span style={{ color: B.blue }}>
                  W3C Anti-Counterfeit Taskforce
                </span>{" "}
                · Community Group Meeting
              </div>
              <h1
                style={{
                  fontSize: "clamp(24px, 3.2vw, 36px)",
                  fontWeight: 700,
                  lineHeight: 1.25,
                  marginBottom: "12px",
                  color: B.nearBlack,
                }}
              >
                Find matching book covers
                <br />
                across <span style={{ color: B.blue }}>3 million listings</span>
              </h1>
              <p
                style={{
                  fontSize: "14.5px",
                  color: B.medGray,
                  maxWidth: "540px",
                  margin: "0 auto",
                  lineHeight: 1.6,
                }}
              >
                Upload, paste, or drag a cover image. The ISCC fingerprint
                engine generates content-derived codes and searches for exact
                matches, near-duplicates, and similar covers.
              </p>
            </div>

            <StepCard number={1} title="Verify a book is in the 3M index">
              <TitleSearch />
            </StepCard>

            <StepCard number={3} title="Test ISCC cover matching" accent>
              <div
                onDragOver={(e) => {
                  e.preventDefault();
                  setDragOver(true);
                }}
                onDragLeave={() => setDragOver(false)}
                onDrop={handleDrop}
                onClick={() => fileRef.current && fileRef.current.click()}
                style={{
                  borderRadius: "12px",
                  border: `2px dashed ${dragOver ? B.blue : `${B.sky}80`}`,
                  background: dragOver ? `${B.sky}14` : `${B.sky}08`,
                  padding: "40px 24px",
                  textAlign: "center",
                  transition: "all 0.2s ease",
                  cursor: "pointer",
                }}
              >
                <div style={{ marginBottom: "14px" }}>
                  <HexSpinner size={56} />
                </div>
                <div
                  style={{
                    fontSize: "14px",
                    fontWeight: 600,
                    color: B.nearBlack,
                    marginBottom: "5px",
                  }}
                >
                  Drop a cover image here
                </div>
                <div
                  style={{
                    fontSize: "12.5px",
                    color: B.medGray,
                    marginBottom: "22px",
                  }}
                >
                  or click to browse · supports paste from clipboard (Ctrl+V)
                </div>
                <div
                  onClick={(e) => e.stopPropagation()}
                  style={{
                    display: "flex",
                    gap: "6px",
                    maxWidth: "440px",
                    margin: "0 auto",
                  }}
                >
                  <input
                    type="text"
                    value={urlInput}
                    onChange={(e) => setUrlInput(e.target.value)}
                    onKeyDown={(e) => e.key === "Enter" && handleUrlSubmit()}
                    placeholder="Paste image URL or press Enter for demo…"
                    style={{
                      flex: 1,
                      padding: "6px 12px",
                      borderRadius: "6px",
                      border: `1px solid ${B.blue}`,
                      background: B.white,
                      color: B.nearBlack,
                      fontSize: "12px",
                      fontFamily: "'JetBrains Mono', Consolas, monospace",
                      outline: "none",
                      transition: "box-shadow 0.15s ease",
                    }}
                    onFocus={(e) => (e.target.style.boxShadow = `0 0 0 3px ${B.blue}33`)}
                    onBlur={(e) => (e.target.style.boxShadow = "none")}
                  />
                  <button
                    onClick={handleUrlSubmit}
                    style={{
                      padding: "9px 18px",
                      borderRadius: "6px",
                      border: "none",
                      background: B.blue,
                      color: B.white,
                      fontSize: "12px",
                      fontWeight: 600,
                      cursor: "pointer",
                      whiteSpace: "nowrap",
                    }}
                  >
                    ▶ Search
                  </button>
                </div>
              </div>

              <div onClick={(e) => e.stopPropagation()} style={{ marginTop: "16px" }}>
                <ScoreThresholdSlider
                  value={scoreThreshold}
                  onChange={setScoreThreshold}
                />
              </div>
            </StepCard>

            <div
              style={{
                display: "flex",
                justifyContent: "center",
                gap: "8px",
                marginTop: "24px",
                flexWrap: "wrap",
              }}
            >
              {[
                { label: "Title Search", color: B.blue },
                { label: "Image URL", color: B.sky },
                { label: "Drag & Drop", color: B.cyan },
                { label: "Clipboard Paste", color: B.lime },
                { label: "ISCC Fingerprinting", color: B.yellow },
                { label: "Similarity Search", color: B.coral },
                { label: "ISO 24138", color: B.navy },
              ].map((f, i) => (
                <div
                  key={f.label}
                  style={{
                    padding: "4px 10px",
                    borderRadius: "14px",
                    background: `${f.color}14`,
                    border: `1px solid ${f.color}40`,
                    fontSize: "10.5px",
                    color: f.color,
                    fontWeight: 600,
                    animation: `fadeSlideUp 0.3s ease ${0.15 + i * 0.05}s both`,
                  }}
                >
                  {f.label}
                </div>
              ))}
            </div>
          </div>
        )}

        {phase === "error" && (
          <div
            style={{
              animation: "fadeSlideUp 0.4s ease both",
              textAlign: "center",
              maxWidth: "480px",
              margin: "0 auto",
            }}
          >
            <div
              style={{
                padding: "28px 24px",
                borderRadius: "12px",
                border: `1px solid ${B.coral}40`,
                background: B.white,
              }}
            >
              <div style={{ fontSize: "36px", marginBottom: "12px" }}>⚠</div>
              <div
                style={{
                  fontSize: "15px",
                  fontWeight: 600,
                  color: B.nearBlack,
                  marginBottom: "8px",
                }}
              >
                Something went wrong
              </div>
              <div
                style={{
                  fontSize: "13px",
                  color: B.medGray,
                  marginBottom: "20px",
                  fontFamily: "'JetBrains Mono', Consolas, monospace",
                  wordBreak: "break-word",
                }}
              >
                {error}
              </div>
              <button
                onClick={reset}
                style={{
                  padding: "9px 24px",
                  borderRadius: "6px",
                  border: "none",
                  background: B.blue,
                  color: B.white,
                  fontSize: "13px",
                  fontWeight: 600,
                  cursor: "pointer",
                }}
              >
                ← Try again
              </button>
            </div>
          </div>
        )}

        {(phase === "processing" || phase === "results") && (
          <div
            style={{
              display: "grid",
              gridTemplateColumns: gridCols,
              gap: "24px",
              animation: "fadeSlideUp 0.4s ease both",
            }}
          >
            {/* Left */}
            <div
              style={{ display: "flex", flexDirection: "column", gap: "12px" }}
            >
              <h2
                style={{
                  fontSize: "17px",
                  fontWeight: 700,
                  color: B.nearBlack,
                  marginBottom: "2px",
                }}
              >
                Query Image
              </h2>
              <div
                onDragOver={
                  phase === "results"
                    ? (e) => {
                        e.preventDefault();
                        setDragOver(true);
                      }
                    : undefined
                }
                onDragLeave={
                  phase === "results" ? () => setDragOver(false) : undefined
                }
                onDrop={phase === "results" ? handleDrop : undefined}
                onClick={
                  phase === "results"
                    ? () => fileRef.current && fileRef.current.click()
                    : undefined
                }
              >
                <QueryImage
                  src={previewSrc}
                  thumbnail={isccData && isccData.thumbnail}
                  processing={phase === "processing"}
                  interactive={phase === "results"}
                  dragOver={phase === "results" && dragOver}
                />
              </div>

              {(phase === "processing" || phase === "results") && (
                <ProcessingSteps
                  currentStep={currentStep}
                  steps={PROCESSING_STEPS}
                  timings={stepTimings}
                  outcome={
                    phase === "results"
                      ? {
                          ok: filteredResults.length > 0,
                          label:
                            filteredResults.length > 0
                              ? "Matches found"
                              : "No matches found",
                          value:
                            filteredResults.length > 0
                              ? filteredResults.length
                              : null,
                        }
                      : null
                  }
                />
              )}

              {phase === "results" && isccData && isccData.name && (
                <div
                  style={{
                    padding: "10px 12px",
                    borderRadius: "6px",
                    background: B.white,
                    border: `1px solid ${B.lightGray}`,
                    animation: "fadeSlideUp 0.3s ease 0.1s both",
                  }}
                >
                  <div
                    style={{
                      fontSize: "9.5px",
                      color: B.medGray,
                      textTransform: "uppercase",
                      letterSpacing: "0.08em",
                      fontWeight: 600,
                      marginBottom: "5px",
                    }}
                  >
                    Extracted Title
                  </div>
                  <div
                    style={{
                      fontSize: "12.5px",
                      color: B.nearBlack,
                      fontWeight: 600,
                    }}
                  >
                    {isccData.name}
                  </div>
                </div>
              )}

              {phase === "results" && results.length > 0 && (
                <ScoreThresholdSlider value={scoreThreshold} onChange={setScoreThreshold} />
              )}

              {isccData && (
                <IsccCodeDisplay
                  units={unitsToDisplayArray(isccData.units)}
                  animate
                  unitScores={filteredResults.length > 0 ? filteredResults[0].per_unit : null}
                />
              )}

            </div>

            {/* Right */}
            <div>
              {phase === "processing" && (
                <div
                  style={{
                    display: "flex",
                    flexDirection: "column",
                    alignItems: "center",
                    justifyContent: "center",
                    height: "100%",
                    gap: "18px",
                  }}
                >
                  <HexSpinner
                    size={130}
                    spinning
                    progress={currentStep / PROCESSING_STEPS.length}
                  />
                  <div style={{ textAlign: "center" }}>
                    <div
                      style={{
                        fontSize: "13.5px",
                        color: B.nearBlack,
                        fontWeight: 500,
                        marginBottom: "3px",
                      }}
                    >
                      {PROCESSING_STEPS[currentStep].label}
                    </div>
                    <div
                      style={{
                        fontSize: "11.5px",
                        color: B.sky,
                        fontFamily: "'JetBrains Mono', Consolas, monospace",
                        animation: "pulse 1.5s ease-in-out infinite",
                      }}
                    >
                      {PROCESSING_STEPS[currentStep].detail}
                    </div>
                  </div>
                  <div
                    style={{
                      width: "180px",
                      height: "2px",
                      borderRadius: "1px",
                      background: B.lightGray,
                      overflow: "hidden",
                      position: "relative",
                    }}
                  >
                    <div
                      style={{
                        position: "absolute",
                        width: "40%",
                        height: "100%",
                        background: B.blue,
                        borderRadius: "1px",
                        animation: "shimmer 1.2s ease-in-out infinite",
                      }}
                    />
                  </div>
                </div>
              )}

              {phase === "results" && filteredResults.length === 0 && (
                <div
                  style={{
                    display: "flex",
                    flexDirection: "column",
                    alignItems: "center",
                    justifyContent: "center",
                    height: "100%",
                    gap: "12px",
                    animation: "fadeSlideUp 0.3s ease both",
                  }}
                >
                  <div style={{ fontSize: "48px", opacity: 0.3 }}>🔍</div>
                  <div
                    style={{
                      fontSize: "15px",
                      fontWeight: 600,
                      color: B.nearBlack,
                    }}
                  >
                    No matches found
                  </div>
                  <div
                    style={{
                      fontSize: "13px",
                      color: B.medGray,
                      textAlign: "center",
                      maxWidth: "320px",
                    }}
                  >
                    {results.length > 0
                      ? "All " + results.length + " matches are below the " + (scoreThreshold * 100).toFixed(0) + "% score threshold. Try lowering it."
                      : "This cover did not match any entries in the book covers index. Try a different image."}
                  </div>
                </div>
              )}

              {phase === "results" && filteredResults.length > 0 && (
                <div>
                  <div
                    style={{
                      display: "flex",
                      alignItems: "center",
                      justifyContent: "space-between",
                      marginBottom: "14px",
                      animation: "fadeSlideUp 0.3s ease both",
                      flexWrap: "wrap",
                      gap: "8px",
                    }}
                  >
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <h2
                        style={{
                          fontSize: "17px",
                          fontWeight: 700,
                          color: B.nearBlack,
                          marginBottom: "2px",
                        }}
                      >
                        Match Results
                      </h2>
                      {isccData && (
                        <div
                          style={{
                            fontFamily:
                              "'JetBrains Mono', Consolas, monospace",
                            fontSize: "11px",
                            letterSpacing: "0.04em",
                            color: B.navy,
                            wordBreak: "break-all",
                            lineHeight: 1.5,
                            marginBottom: "2px",
                          }}
                        >
                          {isccData.iscc}
                        </div>
                      )}
                      <div style={{ fontSize: "11.5px", color: B.medGray }}>
                        {filteredResults.length} results from covers index
                      </div>
                    </div>
                    <div
                      style={{ display: "flex", gap: "5px", flexWrap: "wrap" }}
                    >
                      {[
                        { label: "Exact", color: "#2e8b57", key: "exact" },
                        {
                          label: "Near-dup",
                          color: "#c89200",
                          key: "near-duplicate",
                        },
                        { label: "Similar", color: B.sky, key: "similar" },
                        {
                          label: "Corroborated",
                          color: B.cyan,
                          key: "corroborated",
                        },
                        {
                          label: "Title-match",
                          color: B.medGray,
                          key: "title-match",
                        },
                        { label: "Weak", color: B.medGray, key: "weak" },
                      ]
                        .filter((cat) => resultCounts[cat.key])
                        .map((cat) => (
                          <div
                            key={cat.label}
                            style={{
                              padding: "2px 7px",
                              borderRadius: "10px",
                              border: `1px solid ${cat.color}30`,
                              fontSize: "9.5px",
                              color: cat.color,
                              fontWeight: 600,
                              fontFamily:
                                "'JetBrains Mono', Consolas, monospace",
                            }}
                          >
                            {resultCounts[cat.key]} {cat.label}
                          </div>
                        ))}
                    </div>
                  </div>
                  <div
                    style={{
                      display: "flex",
                      flexDirection: "column",
                      gap: "8px",
                    }}
                  >
                    {filteredResults.map((r, i) => (
                      <ResultCard
                        key={r.iscc_id || i}
                        result={r}
                        index={i}
                        visible={showResults}
                      />
                    ))}
                  </div>
                </div>
              )}
            </div>
          </div>
        )}
      </main>

      {/* Footer */}
      <footer
        style={{
          position: "relative",
          zIndex: 10,
          textAlign: "center",
          padding: "24px 20px",
          borderTop: `1px solid ${B.lightGray}`,
          marginTop: "36px",
        }}
      >
        <a
          href="https://amlet.ai"
          target="_blank"
          rel="noreferrer"
          style={{
            display: "inline-flex",
            alignItems: "center",
            gap: "10px",
            textDecoration: "none",
            marginBottom: "14px",
          }}
        >
          <span
            style={{
              fontSize: "10.5px",
              textTransform: "uppercase",
              letterSpacing: "0.14em",
              color: B.medGray,
              fontWeight: 600,
            }}
          >
            Sponsored by
          </span>
          <img src={AMLET_LOGO} alt="Amlet" style={{ height: "18px" }} />
        </a>
        <div style={{ fontSize: "10.5px", color: B.medGray }}>
          Developed by ISCC Foundation · ISO 24138:2024 ·{" "}
          <a
            href="https://iscc.io"
            target="_blank"
            rel="noreferrer"
            style={{ color: B.blue, textDecoration: "none" }}
          >
            iscc.io
          </a>
        </div>
      </footer>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
