import type { Props } from "./schema";

const CSS = String.raw`
.asset{font-family:var(--font-sans);color:var(--color-on-surface);background:var(--color-surface);max-width:var(--container-slide);margin:auto;padding:2.5rem 3rem;line-height:1.5}.asset *{box-sizing:border-box}.asset h1{font-size:var(--text-h1);line-height:1.3;margin:0}.asset .subtitle{color:var(--color-secondary);margin:.6rem 0 1.25rem}.asset .eyebrow{font:600 var(--text-label) var(--font-mono);letter-spacing:.1em;color:var(--color-tertiary)}.asset .plot{display:block;width:100%;height:auto}.asset .plot text{font-family:var(--font-sans);font-size:13px;fill:var(--color-secondary)}.asset .plot .ink{fill:var(--color-on-surface)}.asset .plot .axis{stroke:var(--color-outline);stroke-width:1}.asset .plot .grid{stroke:var(--color-outline);stroke-width:1;opacity:.48}.asset .plot .curve{fill:none;stroke:var(--color-tertiary);stroke-width:4;stroke-linejoin:round;stroke-linecap:round}.asset .plot .chance{stroke:var(--color-secondary);stroke-width:2;stroke-dasharray:7 7}.asset .plot .point{fill:var(--color-tertiary)}.asset .plot .selected{fill:var(--color-primary);stroke:var(--color-surface);stroke-width:4}.asset .summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem;margin:1rem 0 1.5rem}.asset .metric{padding:1rem;background:var(--color-surface-subtle);border-top:3px solid var(--color-tertiary);min-width:0}.asset .metric dt{font-size:var(--text-label);color:var(--color-secondary)}.asset .metric dd{margin:.25rem 0 0;font:700 var(--text-h2) var(--font-mono);overflow-wrap:anywhere}.asset .ledger{border-collapse:collapse;width:100%;font-variant-numeric:tabular-nums}.asset .ledger th,.asset .ledger td{text-align:left;vertical-align:top;padding:.65rem .75rem;border-bottom:1px solid var(--color-outline);overflow-wrap:anywhere}.asset .ledger th{color:var(--color-secondary)}.asset .ledger tr[data-selected=true]{background:var(--color-tertiary-container)}.asset .ledger .mono{font-family:var(--font-mono)}.asset details{margin-top:1rem}.asset summary{cursor:pointer;min-height:2.75rem;padding:.5rem 0;color:var(--color-secondary)}.asset .note{border-left:3px solid var(--color-outline);padding:.75rem 1rem;color:var(--color-secondary)}
@media(max-width:640px){.asset{padding:1.5rem 1rem}.asset h1{font-size:var(--text-h2)}.asset .summary{grid-template-columns:1fr}.asset .ledger,.asset .ledger tbody,.asset .ledger tr,.asset .ledger td{display:block}.asset .ledger thead{display:none}.asset .ledger tr{padding:.65rem 0;border-bottom:1px solid var(--color-outline)}.asset .ledger td{display:grid;grid-template-columns:8rem minmax(0,1fr);gap:.75rem;border:0;padding:.2rem 0}.asset .ledger td:before{content:attr(data-label);font-size:var(--text-label);color:var(--color-secondary)}}
`;

const fmtRate = (value: number) => `${(value * 100).toFixed(0)}%`;
const pathFor = (points: Props["points"]) => points.map((point, index) => `${index ? "L" : "M"} ${100 + point.falsePositiveRate * 660} ${490 - point.truePositiveRate * 390}`).join(" ");
const auc = (points: Props["points"]) => points.slice(1).reduce((sum, point, index) => {
  const previous = points[index];
  return sum + (point.falsePositiveRate - previous.falsePositiveRate) * (point.truePositiveRate + previous.truePositiveRate) / 2;
}, 0);

export default function Template(d: Props) {
  const selected = d.points.find(point => point.id === d.selectedPointId)!;
  const area = auc(d.points);
  const tick = [0, .25, .5, .75, 1];
  return <section className="asset asset-roc-curve">
    <style>{CSS}</style>
    <header>
      <p className="eyebrow">ROC CURVE / THRESHOLD SELECTION</p>
      <h1>{d.title}</h1>
      {d.subtitle && <p className="subtitle">{d.subtitle}</p>}
    </header>
    <svg className="plot" data-layout-still="true" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="roc-title roc-desc" fontFamily="sans-serif" fontSize="13" fill="var(--color-secondary)">
      <title id="roc-title">{d.title}</title>
      <desc id="roc-desc">陽性クラス {d.positiveClass} のROC曲線。選択閾値 {selected.threshold.toFixed(2)} の偽陽性率は {fmtRate(selected.falsePositiveRate)}、真陽性率は {fmtRate(selected.truePositiveRate)}、曲線下面積は {area.toFixed(2)}。</desc>
      <rect width="1280" height="720" fill="var(--color-surface)" />
      <text x="76" y="40" fontSize="13" fontWeight="700" letterSpacing="2" fill="var(--color-tertiary)">ROC / {d.positiveClass}</text>
      {tick.map((value, index) => {
        const px = 100 + value * 660;
        const py = 490 - value * 390;
        return <g key={index}>
          <line x1="100" y1={py} x2="760" y2={py} stroke="var(--color-outline)" strokeWidth="1" opacity=".48" />
          <line x1={px} y1="100" x2={px} y2="490" stroke="var(--color-outline)" strokeWidth="1" opacity=".48" />
          {value > 0 && <text x="84" y={py + 5} textAnchor="end">{fmtRate(value)}</text>}
          <text x={px} y="515" textAnchor="middle">{fmtRate(value)}</text>
        </g>;
      })}
      <line x1="100" y1="490" x2="760" y2="100" stroke="var(--color-secondary)" strokeWidth="2" strokeDasharray="7 7" />
      <path d={pathFor(d.points)} fill="none" stroke="var(--color-tertiary)" strokeWidth="4" strokeLinejoin="round" strokeLinecap="round" />
      {d.points.map(point => <circle key={point.id} cx={100 + point.falsePositiveRate * 660} cy={490 - point.truePositiveRate * 390} r={point.id === selected.id ? 10 : 5} fill="var(--color-tertiary)" stroke={point.id === selected.id ? "var(--color-surface)" : "none"} strokeWidth="4" />)}
      <line x1="100" y1="490" x2="760" y2="490" stroke="var(--color-outline)" strokeWidth="1" />
      <line x1="100" y1="100" x2="100" y2="490" stroke="var(--color-outline)" strokeWidth="1" />
      <text x="430" y="555" textAnchor="middle" fill="var(--color-on-surface)">偽陽性率｜誤って陽性とする割合</text>
      <text x="34" y="290" textAnchor="middle" transform="rotate(-90 34 290)" fill="var(--color-on-surface)">真陽性率｜陽性を正しく検出する割合</text>
      <text x="800" y="126" fontWeight="700" fill="var(--color-on-surface)">この分類器</text>
      <text x="800" y="151">左上へ近いほど識別性能が高い</text>
      <line x1="800" y1="184" x2="846" y2="184" stroke="var(--color-secondary)" strokeWidth="2" strokeDasharray="7 7" />
      <text x="860" y="189">無作為判定の基準</text>
      <line x1="800" y1="218" x2="846" y2="218" stroke="var(--color-tertiary)" strokeWidth="4" />
      <text x="860" y="223">閾値を動かした性能</text>
      <circle cx="823" cy="258" r="8" fill="var(--color-tertiary)" stroke="var(--color-surface)" strokeWidth="3" />
      <text x="846" y="263">選択した運用候補</text>
      <rect x="800" y="310" width="400" height="162" rx="8" fill="var(--color-surface)" stroke="var(--color-outline)" />
      <text x="828" y="348" fontWeight="700" fill="var(--color-on-surface)">選択点｜{selected.label}</text>
      <text x="828" y="385">閾値 {selected.threshold.toFixed(2)}</text>
      <text x="828" y="415">偽陽性率 {fmtRate(selected.falsePositiveRate)}</text>
      <text x="828" y="445">真陽性率 {fmtRate(selected.truePositiveRate)}</text>
      <text x="100" y="610" fontWeight="700" fill="var(--color-on-surface)">読み方</text>
      <text x="100" y="640">曲線下面積 AUC {area.toFixed(2)}｜閾値の選択は偽陽性コストと見逃しコストを合わせて判断する</text>
      <text x="100" y="673">ROCは全閾値の識別性能を比較する図で、単独では運用閾値を決めない</text>
    </svg>
    <dl className="summary">
      <div className="metric"><dt>ROC曲線下面積</dt><dd>{area.toFixed(2)} / 1.00</dd></div>
      <div className="metric"><dt>選択閾値</dt><dd>{selected.threshold.toFixed(2)}</dd></div>
      <div className="metric"><dt>選択点の真陽性率 / 偽陽性率</dt><dd>{fmtRate(selected.truePositiveRate)} / {fmtRate(selected.falsePositiveRate)}</dd></div>
    </dl>
    <p className="note">ROC曲線は閾値ごとの識別性能を比較します。実運用の閾値は、誤検知と見逃しの業務コストを合わせて決めてください。</p>
    <details open>
      <summary>全閾値点と率の対応を確認（{d.points.length}点）</summary>
      <table className="ledger"><thead><tr><th>候補</th><th>閾値</th><th>偽陽性率</th><th>真陽性率</th></tr></thead>
        <tbody>{d.points.map(point => <tr key={point.id} data-roc-point={point.id} data-selected={point.id === selected.id}>
          <td data-label="候補">{point.label}{point.id === selected.id ? "（選択）" : ""}</td>
          <td data-label="閾値" className="mono">{point.threshold.toFixed(2)}</td>
          <td data-label="偽陽性率" className="mono">{fmtRate(point.falsePositiveRate)}</td>
          <td data-label="真陽性率" className="mono">{fmtRate(point.truePositiveRate)}</td>
        </tr>)}</tbody>
      </table>
    </details>
  </section>;
}
