import { useEffect, useState } from "react";
import { api } from "./api.js";

export default function ResultsView({ jobId, onBack }) {
  const [data, setData] = useState(null);
  const [error, setError] = useState("");

  useEffect(() => {
    api.jobResults(jobId).then(setData).catch((e) => setError(e.message));
  }, [jobId]);

  if (error)
    return (
      <div className="card">
        <div className="error-banner">{error}</div>
        <button className="ghost" onClick={onBack}>
          ← Back
        </button>
      </div>
    );
  if (!data) return <div className="card">Loading results…</div>;

  const { summary, metadata } = data;
  // Multi-construct runs summarize per construct + a correlation matrix;
  // single-construct summaries keep the original flat shape.
  const multi = Array.isArray(summary.constructs);
  // Anchor-vector (bipolar) runs score along the axis between two poles: the
  // score is centered on 0, negative meaning "toward the opposite pole".
  const anchored = summary.anchored === true;

  return (
    <>
      <div className="results-toolbar">
        <button className="ghost" onClick={onBack}>
          ← Back to workspace
        </button>
        <div className="row result-actions">
          <a href={api.exportUrl(jobId)}>
            <button className="primary">Export results CSV</button>
          </a>
          <a href={api.scriptUrl(jobId)}>
            <button className="ghost">Python script</button>
          </a>
          <a href={api.scriptRequirementsUrl(jobId)}>
            <button className="ghost">requirements.txt</button>
          </a>
          <a href={api.metadataUrl(jobId)}>
            <button className="ghost">Run metadata (JSON)</button>
          </a>
        </div>
      </div>

      <div className="card">
        <h3>
          {metadata.construct} × {metadata.corpus_file}
        </h3>
        <p className="hint">
          {anchored ? (
            <>
              Anchored (bipolar) score = {summary.metric === "dot" ? "dot product" : "cosine"} of
              each text with the anchor vector (target centroid minus opposite centroid). Positive
              = toward <b>{summary.target_name}</b>; negative = toward <b>{summary.opposite_name}</b>.
              The two per-pole CCR scores are in the export.
            </>
          ) : (
            <>
              CCR score = mean cosine similarity between each text and a construct&apos;s scale
              items.{" "}
              {/* A construct whose items are ALL reverse-keyed scores in the
                  opposite direction, and the backend says so with
                  CONSTRUCT_ALL_ITEMS_REVERSED. Do not state the direction here
                  when that warning is present - the amber panel names which
                  construct is affected. */}
              {summary.warnings?.some((w) => w.code === "CONSTRUCT_ALL_ITEMS_REVERSED")
                ? "Score direction depends on how each construct's items are keyed - see the warning below."
                : "Higher = the text expresses the construct more strongly."}
              {multi &&
                " All constructs were scored on the same pass over the corpus, so scores are row-aligned and directly comparable."}
            </>
          )}
        </p>
        {/* Cautionary wording approved by the PI (2026-08-05); source_type
            comes from the construct snapshot in the run metadata (top-level
            construct_snapshot on single runs, constructs[].snapshot on multi). */}
        {[
          metadata.construct_snapshot,
          metadata.target_construct?.snapshot,
          metadata.opposite_construct?.snapshot,
          ...(metadata.constructs || []).map((c) => c.snapshot),
        ].some((s) => s?.source_type === "llm_generated") && (
          <p className="small muted">
            ⚠ This run uses a construct whose items were AI-generated and have not been
            psychometrically validated. Interpret scores with appropriate caution.
          </p>
        )}

        <div className="stat-grid">
          <Stat k="Texts scored" v={summary.n_docs.toLocaleString()} />
          {anchored ? (
            <>
              <Stat k="Mean anchor score" v={summary.score_mean.toFixed(3)} />
              <Stat k="SD" v={summary.score_sd.toFixed(3)} />
              <Stat k={`Mean toward ${summary.target_name}`} v={summary.target_score_mean.toFixed(3)} />
              <Stat k={`Mean toward ${summary.opposite_name}`} v={summary.opposite_score_mean.toFixed(3)} />
              <Stat k="Metric" v={summary.metric} />
            </>
          ) : multi ? (
            <Stat k="Constructs" v={summary.constructs.length} />
          ) : (
            <>
              <Stat k="Mean score" v={summary.score_mean.toFixed(3)} />
              <Stat k="SD" v={summary.score_sd.toFixed(3)} />
              <Stat k="Min" v={summary.score_min.toFixed(3)} />
              <Stat k="Max" v={summary.score_max.toFixed(3)} />
            </>
          )}
          {summary.n_dropped_empty > 0 && (
            <Stat k="Empty rows dropped" v={summary.n_dropped_empty} />
          )}
        </div>

        {summary.warnings?.length > 0 && (
          <div className="warnings mt">
            <strong className="small">Data-quality notes</strong>
            <ul className="small" style={{ margin: "4px 0 0", paddingLeft: 20 }}>
              {summary.warnings.map((w, i) => (
                <li key={i}>
                  {typeof w === "string" ? w : (
                    <>
                      <code style={{ fontSize: 11 }}>{w.code}</code> - {w.message}
                    </>
                  )}
                </li>
              ))}
            </ul>
          </div>
        )}
      </div>

      {multi && <CorrelationCard correlations={summary.correlations} />}

      {anchored ? (
        <AnchorBody summary={summary} />
      ) : multi ? (
        summary.constructs.map((c, i) => (
          <details
            className="card construct-results"
            key={c.construct_id || i}
            open={summary.constructs.length <= 2}
          >
            <summary>
              <strong>{c.construct_name}</strong>
              <span className="picker-meta">
                {" "}
                mean {c.score_mean.toFixed(3)} · SD {c.score_sd.toFixed(3)} ·{" "}
                {c.n_items} item{c.n_items === 1 ? "" : "s"} · CSV columns{" "}
                <code>{c.column_prefix}_*</code>
              </span>
            </summary>
            <div className="stat-grid mt">
              <Stat k="Mean score" v={c.score_mean.toFixed(3)} />
              <Stat k="SD" v={c.score_sd.toFixed(3)} />
              <Stat k="Min" v={c.score_min.toFixed(3)} />
              <Stat k="Max" v={c.score_max.toFixed(3)} />
            </div>
            <h4>Score distribution</h4>
            <Histogram histogram={c.histogram} />
            <h4>Per-item mean loadings</h4>
            <ItemBars itemMeans={c.item_means} />
            <div className="row">
              <div className="grow">
                <h4>Highest-scoring texts</h4>
                <DocTable docs={c.top_docs} />
              </div>
              <div className="grow">
                <h4>Lowest-scoring texts</h4>
                <DocTable docs={c.bottom_docs} />
              </div>
            </div>
          </details>
        ))
      ) : (
        <>
          <div className="card">
            <h3>Score distribution</h3>
            <Histogram histogram={summary.histogram} />
          </div>

          <div className="card">
            <h3>Per-item mean loadings</h3>
            <p className="hint">
              Mean similarity of the corpus to each scale item - a face-validity check on which
              items drive the construct signal.
            </p>
            <ItemBars itemMeans={summary.item_means} />
          </div>

          <div className="row">
            <div className="grow card">
              <h3>Highest-scoring texts</h3>
              <DocTable docs={summary.top_docs} />
            </div>
            <div className="grow card">
              <h3>Lowest-scoring texts</h3>
              <DocTable docs={summary.bottom_docs} />
            </div>
          </div>
        </>
      )}

      <div className="meta-footer">
        <strong>Reproducibility record</strong> - model: <code>{metadata.model}</code> (dim{" "}
        {metadata.embedding_dim})
        {!multi && !anchored && (
          <>
            {" "}· items hash: <code>{metadata.items_sha256_16}</code>
          </>
        )}
        {anchored && (
          <>
            {" "}· metric: <code>{summary.metric}</code> · anchor vector norm{" "}
            <code>{metadata.anchor_vector_norm}</code>
          </>
        )}{" "}
        · text column: <code>{metadata.text_column}</code> · run:{" "}
        {metadata.started_at} → {metadata.finished_at} ({metadata.duration_seconds}s) ·
        numpy {metadata.numpy}
        {metadata.sentence_transformers &&
          ` · sentence-transformers ${metadata.sentence_transformers}`}
        {anchored ? (
          <div className="mt small">
            <div>
              {metadata.target_construct?.name} (target) - items hash{" "}
              <code>{metadata.target_items_sha256_16}</code>
              {metadata.target_construct?.reference ? ` · ${metadata.target_construct.reference}` : ""}
            </div>
            <div>
              {metadata.opposite_construct?.name} (opposite) - items hash{" "}
              <code>{metadata.opposite_items_sha256_16}</code>
              {metadata.opposite_construct?.reference
                ? ` · ${metadata.opposite_construct.reference}`
                : ""}
            </div>
          </div>
        ) : multi ? (
          <div className="mt small">
            {metadata.constructs.map((c) => (
              <div key={c.column_prefix}>
                {c.name} - items hash <code>{c.items_sha256_16}</code>
                {c.reference ? ` · ${c.reference}` : ""}
              </div>
            ))}
          </div>
        ) : (
          <div className="mt small">
            Construct reference: {metadata.construct_reference || "-"}
          </div>
        )}
      </div>
    </>
  );
}

// Bipolar (anchor-vector) run body: distribution centered on 0, per-pole item
// loadings side by side, and top/bottom texts labeled by pole (spec 0006).
function AnchorBody({ summary }) {
  return (
    <>
      <div className="card">
        <h3>Score distribution</h3>
        <p className="hint">
          Centered on zero. Texts to the right lean toward <b>{summary.target_name}</b>; to the
          left, toward <b>{summary.opposite_name}</b>.
        </p>
        <Histogram histogram={summary.histogram} />
      </div>

      <div className="row">
        <div className="grow card">
          <h3>{summary.target_name} items</h3>
          <p className="hint">Mean similarity of the corpus to each target-pole item.</p>
          <ItemBars itemMeans={summary.target_item_means} />
        </div>
        <div className="grow card">
          <h3>{summary.opposite_name} items</h3>
          <p className="hint">Mean similarity to each opposite-pole item.</p>
          <ItemBars itemMeans={summary.opposite_item_means} />
        </div>
      </div>

      <div className="row">
        <div className="grow card">
          <h3>Most {summary.target_name}</h3>
          <DocTable docs={summary.top_docs} />
        </div>
        <div className="grow card">
          <h3>Most {summary.opposite_name}</h3>
          <DocTable docs={summary.bottom_docs} />
        </div>
      </div>
    </>
  );
}

// Correlation table in the layout psychology papers use: rows "1. Name",
// columns numbered. Cell shading encodes sign (accent = positive, blue =
// negative) and strength (|r|).
function CorrelationCard({ correlations }) {
  const { constructs: names, matrix, n_texts } = correlations;

  function cellStyle(r, isDiag) {
    if (isDiag || r == null) return { color: "#98a2b3" };
    const alpha = Math.min(0.85, Math.abs(r));
    // Diverging pair: brand teal for positive, copper for negative.
    return {
      background: r >= 0 ? `rgba(38, 115, 111, ${alpha})` : `rgba(178, 96, 43, ${alpha})`,
      color: Math.abs(r) > 0.5 ? "#fff" : undefined,
      textAlign: "center",
    };
  }

  return (
    <div className="card">
      <h3>Construct interrelations</h3>
      <p className="hint">
        Pearson correlation between per-text CCR scores ({n_texts.toLocaleString()} texts).
        Positive r = the constructs rise and fall together in your corpus; negative r =
        texts high on one tend to be low on the other. The exported CSV contains every
        per-text score, so these are fully recomputable.
      </p>
      <div className="table-wrap">
        <table className="docs">
          <thead>
            <tr>
              <th />
              {names.map((n, i) => (
                <th key={i} style={{ textAlign: "center" }} title={n}>
                  {i + 1}
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {names.map((rowName, i) => (
              <tr key={i}>
                <th style={{ textAlign: "left", fontWeight: 500 }}>
                  {i + 1}. {rowName}
                </th>
                {matrix[i].map((r, j) => (
                  <td key={j} style={cellStyle(r, i === j)}>
                    {i === j ? "-" : r == null ? "n/a" : r.toFixed(2)}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function Stat({ k, v }) {
  return (
    <div className="stat">
      <div className="v">{v}</div>
      <div className="k">{k}</div>
    </div>
  );
}

function ItemBars({ itemMeans }) {
  const maxItemMean = Math.max(...itemMeans.map((m) => Math.abs(m.mean)), 1e-9);
  return (
    <>
      {itemMeans.map((m, i) => (
        <div className="item-bar-row" key={i}>
          <span className="item-bar-label" title={m.item}>
            {m.item.length > 80 ? m.item.slice(0, 80) + "…" : m.item}
          </span>
          <div className="item-bar-track">
            <div
              className="item-bar-fill"
              style={{ width: `${Math.max(2, (Math.abs(m.mean) / maxItemMean) * 100)}%` }}
            />
          </div>
          <span className="item-bar-val">{m.mean.toFixed(3)}</span>
        </div>
      ))}
    </>
  );
}

function DocTable({ docs }) {
  return (
    <div className="table-wrap">
      <table className="docs">
        <thead>
          <tr>
            <th style={{ width: 60 }}>Score</th>
            <th>Text</th>
          </tr>
        </thead>
        <tbody>
          {docs.map((d) => (
            <tr key={d.row}>
              <td className="score">{d.score.toFixed(3)}</td>
              <td>{d.text}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function Histogram({ histogram }) {
  const { counts, edges } = histogram;
  const W = 640;
  const H = 180;
  const PAD = { top: 10, right: 10, bottom: 26, left: 34 };
  const plotW = W - PAD.left - PAD.right;
  const plotH = H - PAD.top - PAD.bottom;
  const maxCount = Math.max(...counts, 1);
  const barW = plotW / counts.length;

  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width: "100%", maxWidth: 720 }}>
      {/* y gridlines */}
      {[0.25, 0.5, 0.75, 1].map((f) => {
        const y = PAD.top + plotH - f * plotH;
        return (
          <g key={f}>
            <line x1={PAD.left} x2={W - PAD.right} y1={y} y2={y} stroke="#eceef1" />
            <text x={PAD.left - 6} y={y + 4} fontSize="10" fill="#98a2b3" textAnchor="end">
              {Math.round(f * maxCount)}
            </text>
          </g>
        );
      })}
      {/* bars */}
      {counts.map((c, i) => {
        const h = (c / maxCount) * plotH;
        return (
          <rect
            key={i}
            x={PAD.left + i * barW + 1.5}
            y={PAD.top + plotH - h}
            width={Math.max(1, barW - 3)}
            height={h}
            rx="2"
            fill="#339b98"
            opacity="0.85"
          >
            <title>
              {edges[i].toFixed(3)} – {edges[i + 1].toFixed(3)}: {c}
            </title>
          </rect>
        );
      })}
      {/* x labels: first, middle, last edges */}
      {[0, Math.floor(counts.length / 2), counts.length].map((i) => (
        <text
          key={i}
          x={PAD.left + i * barW}
          y={H - 8}
          fontSize="10"
          fill="#98a2b3"
          textAnchor="middle"
        >
          {edges[i].toFixed(2)}
        </text>
      ))}
      <line
        x1={PAD.left}
        x2={W - PAD.right}
        y1={PAD.top + plotH}
        y2={PAD.top + plotH}
        stroke="#d0d5dd"
      />
    </svg>
  );
}
