import React, { useEffect, useMemo, useState } from "react"
import { api } from "./api"

const DEFAULT_SOURCES = [
  { id: "safebooru", label: "Safebooru", sfw_policy: "rating:safe", max_content_tags: null },
  { id: "danbooru", label: "Danbooru", sfw_policy: "rating:g", max_content_tags: 2 },
]

function pct(value) {
  if (value == null || Number.isNaN(Number(value))) return "n/a"
  return `${(Number(value) * 100).toFixed(1)}%`
}

export default function ClassifierDebugPage() {
  const [sources, setSources] = useState(DEFAULT_SOURCES)
  const [source, setSource] = useState("safebooru")
  const [pullTags, setPullTags] = useState(["1girl", "solo"])
  const [count, setCount] = useState(10)
  const [tagQuery, setTagQuery] = useState("")
  const [tagOptions, setTagOptions] = useState([])
  const [result, setResult] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState("")
  const [settingsSummary, setSettingsSummary] = useState({
    tagger_model: "wd_swinv2_v3",
    confidence_threshold: 0.6,
    selected_tags: [],
    experimental_style_detector_enabled: false,
  })

  const [realismCount, setRealismCount] = useState(12)
  const [realismCompare, setRealismCompare] = useState(true)
  const [realismLoading, setRealismLoading] = useState(false)
  const [realismError, setRealismError] = useState("")
  const [realismResult, setRealismResult] = useState(null)

  const [styleCount, setStyleCount] = useState(12)
  const [styleLoading, setStyleLoading] = useState(false)
  const [styleError, setStyleError] = useState("")
  const [styleResult, setStyleResult] = useState(null)

  const [tagRecallThreshold, setTagRecallThreshold] = useState(0.35)
  const [tagRecallTopK, setTagRecallTopK] = useState(20)
  const [tagRecallLoading, setTagRecallLoading] = useState(false)
  const [tagRecallError, setTagRecallError] = useState("")
  const [tagRecallResult, setTagRecallResult] = useState(null)

  const [tagFpThreshold, setTagFpThreshold] = useState(0.6)
  const [tagFpMinWeight, setTagFpMinWeight] = useState(0.85)
  const [tagFpAlsoWd, setTagFpAlsoWd] = useState(true)
  const [tagFpLoading, setTagFpLoading] = useState(false)
  const [tagFpError, setTagFpError] = useState("")
  const [tagFpResult, setTagFpResult] = useState(null)

  const sourceMeta = useMemo(
    () => sources.find((s) => s.id === source) || sources[0],
    [sources, source]
  )

  useEffect(() => {
    api
      .getSfwDebugSources()
      .then((data) => {
        if (Array.isArray(data?.sources) && data.sources.length) {
          setSources(data.sources)
        }
      })
      .catch(() => {})
    api
      .getSettings()
      .then((data) => {
        const thr = Number(data.confidence_threshold ?? 0.6)
        setSettingsSummary({
          tagger_model: data.tagger_model || "wd_swinv2_v3",
          confidence_threshold: thr,
          selected_tags: Array.isArray(data.selected_tags) ? data.selected_tags : [],
          experimental_style_detector_enabled: Boolean(
            data.experimental_style_detector_enabled
          ),
        })
        setTagFpThreshold(thr)
      })
      .catch((err) => setError(err.message))
  }, [])

  useEffect(() => {
    let cancelled = false
    const timer = setTimeout(() => {
      api
        .getTags(tagQuery, 20)
        .then((data) => {
          if (!cancelled) setTagOptions(data.items || [])
        })
        .catch(() => {
          if (!cancelled) setTagOptions([])
        })
    }, 150)
    return () => {
      cancelled = true
      clearTimeout(timer)
    }
  }, [tagQuery])

  const handleAddPullTag = (value) => {
    if (!value) return
    const maxTags = sourceMeta?.max_content_tags
    setPullTags((prev) => {
      if (prev.includes(value)) return prev
      if (maxTags != null && prev.length >= maxTags) {
        setError(`${sourceMeta.label} allows at most ${maxTags} content tag(s).`)
        return prev
      }
      setError("")
      return [...prev, value]
    })
  }

  const handleFetchEvaluate = async () => {
    if (pullTags.length === 0) {
      setError("Add at least one tag to pull for the SFW debug eval.")
      return
    }
    setLoading(true)
    setError("")
    try {
      const next = await api.runSfwDebugEval({
        source,
        tags: pullTags,
        count,
      })
      setResult(next)
      const refreshed = await api.getSettings()
      setSettingsSummary({
        tagger_model: refreshed.tagger_model || next.tagger_model,
        confidence_threshold: Number(
          refreshed.confidence_threshold ?? next.confidence_threshold ?? 0.6
        ),
        selected_tags: Array.isArray(refreshed.selected_tags)
          ? refreshed.selected_tags
          : next.destination_tags || [],
        experimental_style_detector_enabled: Boolean(
          refreshed.experimental_style_detector_enabled
        ),
      })
    } catch (err) {
      setError(`SFW debug eval failed: ${err.message}`)
    } finally {
      setLoading(false)
    }
  }

  const handleRealismEval = async () => {
    setRealismLoading(true)
    setRealismError("")
    try {
      const next = await api.runRealismDebugEval({
        count_per_class: realismCount,
        compare_models: realismCompare,
        tagger_model: realismCompare ? null : settingsSummary.tagger_model,
      })
      setRealismResult(next)
    } catch (err) {
      setRealismError(`Realism eval failed: ${err.message}`)
    } finally {
      setRealismLoading(false)
    }
  }

  const handleStyleEval = async () => {
    setStyleLoading(true)
    setStyleError("")
    try {
      const next = await api.runStyleDebugEval({
        count_per_class: styleCount,
        tagger_model: settingsSummary.tagger_model,
      })
      setStyleResult(next)
    } catch (err) {
      setStyleError(`Style detector eval failed: ${err.message}`)
    } finally {
      setStyleLoading(false)
    }
  }

  const handleTagRecallEval = async () => {
    setTagRecallLoading(true)
    setTagRecallError("")
    try {
      const next = await api.runTagRecallEval({
        models: ["ml_danbooru", "wd_swinv2_v3"],
        threshold: tagRecallThreshold,
        top_k: tagRecallTopK,
        refresh_cache: false,
        include_items: true,
      })
      setTagRecallResult(next)
    } catch (err) {
      setTagRecallError(`Tag recall eval failed: ${err.message}`)
    } finally {
      setTagRecallLoading(false)
    }
  }

  const handleTagFpEval = async () => {
    setTagFpLoading(true)
    setTagFpError("")
    try {
      const next = await api.runTagFpEval({
        models: ["ml_danbooru", "wd_swinv2_v3"],
        tag_threshold: tagFpThreshold,
        route_threshold: tagFpThreshold,
        min_weight: tagFpMinWeight,
        also_wd_threshold: tagFpAlsoWd,
        refresh_cache: false,
      })
      setTagFpResult(next)
    } catch (err) {
      setTagFpError(`Tag FP eval failed: ${err.message}`)
    } finally {
      setTagFpLoading(false)
    }
  }

  const multi = realismResult?.multi_model
  const overall = multi?.overall_conclusion
  const metrics = realismResult?.metrics || {}
  const conclusion = realismResult?.conclusion || {}
  const styleOverall = styleResult?.overall_conclusion || {}
  const styleReports = styleResult?.reports || []
  const tagRecallReports = tagRecallResult?.reports || []
  const tagRecallComparison = tagRecallResult?.comparison || {}
  const tagFpReports = tagFpResult?.reports || []
  const tagFpAlt = tagFpResult?.at_wd_general_threshold

  return (
    <div className="container">
      <header className="app-header">
        <div className="app-nav">
          <a href="#/" className="nav-link">
            THR3SHR
          </a>
          <a href="#/debug" className="nav-link active" aria-current="page">
            Debug
          </a>
        </div>
        <h1>THR3SHR debug</h1>
        <p className="lede">
          Curated tag recall / false-positive benchmarks, SFW pull-tag recall, and real-life vs
          anime separation. Uses saved settings from the main page — no migrate.
        </p>
      </header>

      {(error || realismError || styleError || tagRecallError || tagFpError) && (
        <div className="error">
          {error || realismError || styleError || tagRecallError || tagFpError}
        </div>
      )}

      <section className="panel" aria-label="Tag recall benchmark">
        <h2>Tag recall benchmark</h2>
        <span className="kicker">
          Fixed curated suite: <code>ml_danbooru</code> vs <code>wd_swinv2_v3</code>. Metrics are
          recall@threshold and recall@top‑K on each sample’s desired tags (dense WD scores).
          Pin/download images once via{" "}
          <code>scripts/fetch_tag_recall_suite.py --rebuild</code>.
        </span>
        <div className="grid">
          <label>
            Threshold
            <input
              type="number"
              min="0.05"
              max="1"
              step="0.05"
              value={tagRecallThreshold}
              disabled={tagRecallLoading}
              onChange={(e) =>
                setTagRecallThreshold(
                  Math.max(0.05, Math.min(1, Number(e.target.value) || 0.35))
                )
              }
              aria-label="Tag recall threshold"
            />
          </label>
          <label>
            Top‑K
            <input
              type="number"
              min="1"
              max="200"
              value={tagRecallTopK}
              disabled={tagRecallLoading}
              onChange={(e) =>
                setTagRecallTopK(Math.max(1, Math.min(200, Number(e.target.value) || 20)))
              }
              aria-label="Tag recall top K"
            />
          </label>
        </div>
        <div className="actions">
          <button
            type="button"
            disabled={tagRecallLoading}
            onClick={handleTagRecallEval}
            aria-label="Run tag recall benchmark"
          >
            {tagRecallLoading
              ? "Scoring curated suite…"
              : "Run tag recall benchmark"}
          </button>
        </div>
      </section>

      {tagRecallResult && (
        <section className="panel debug-eval-results" aria-label="Tag recall results">
          <h2>Tag recall results</h2>
          <div className="stats">
            <span>
              Best: <strong>{tagRecallComparison.best_model || "—"}</strong>
            </span>
            <span>Samples: {tagRecallResult.count_samples_cached ?? 0}</span>
            <span>
              thr={tagRecallResult.threshold} · topK={tagRecallResult.top_k}
            </span>
          </div>
          {(tagRecallComparison.pairwise || []).map((pair) => (
            <p className="muted" key={`${pair.model_a}-${pair.model_b}`}>
              Pairwise @threshold: {pair.model_a} wins {pair.a_wins}, {pair.model_b} wins{" "}
              {pair.b_wins}, ties {pair.ties} (n={pair.compared})
            </p>
          ))}
          <div className="table-wrap">
            <table>
              <thead>
                <tr>
                  <th scope="col">Model</th>
                  <th scope="col">Micro @thr</th>
                  <th scope="col">Micro @topK</th>
                  <th scope="col">Macro @thr</th>
                  <th scope="col">Macro @topK</th>
                  <th scope="col">Opp</th>
                  <th scope="col">N</th>
                </tr>
              </thead>
              <tbody>
                {tagRecallReports.map((row) => {
                  const s = row.summary || {}
                  return (
                    <tr key={row.tagger_model}>
                      <td>{row.tagger_model}</td>
                      <td>{pct(s.micro_recall_at_threshold)}</td>
                      <td>{pct(s.micro_recall_at_top_k)}</td>
                      <td>{pct(s.macro_recall_at_threshold)}</td>
                      <td>{pct(s.macro_recall_at_top_k)}</td>
                      <td>{s.opportunities ?? 0}</td>
                      <td>{row.count_evaluated}</td>
                    </tr>
                  )
                })}
              </tbody>
            </table>
          </div>
          {tagRecallReports[0] && (
            <>
              <h3>Per-tag (side by side)</h3>
              <div className="table-wrap">
                <table>
                  <thead>
                    <tr>
                      <th scope="col">Tag</th>
                      {tagRecallReports.map((r) => (
                        <th key={`${r.tagger_model}-thr`} scope="col">
                          {r.tagger_model} @thr
                        </th>
                      ))}
                      {tagRecallReports.map((r) => (
                        <th key={`${r.tagger_model}-top`} scope="col">
                          {r.tagger_model} @topK
                        </th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {Array.from(
                      new Set(
                        tagRecallReports.flatMap((r) =>
                          Object.keys(r.summary?.per_tag || {})
                        )
                      )
                    )
                      .sort()
                      .map((tag) => (
                        <tr key={tag}>
                          <td>{tag}</td>
                          {tagRecallReports.map((r) => (
                            <td key={`${r.tagger_model}-thr-${tag}`}>
                              {pct(r.summary?.per_tag?.[tag]?.recall_at_threshold)}
                            </td>
                          ))}
                          {tagRecallReports.map((r) => (
                            <td key={`${r.tagger_model}-top-${tag}`}>
                              {pct(r.summary?.per_tag?.[tag]?.recall_at_top_k)}
                            </td>
                          ))}
                        </tr>
                      ))}
                  </tbody>
                </table>
              </div>
            </>
          )}
          {tagRecallReports[0]?.items?.length > 0 && (
            <>
              <h3>Samples</h3>
              <div className="table-wrap">
                <table>
                  <thead>
                    <tr>
                      <th scope="col">Preview</th>
                      <th scope="col">Bucket</th>
                      <th scope="col">Post</th>
                      {tagRecallReports.map((r) => (
                        <th key={r.tagger_model} scope="col">
                          {r.tagger_model}
                        </th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {(tagRecallReports[0].items || []).map((item) => {
                      const preview = api.getTagRecallPreviewUrl(
                        item.source,
                        item.file_name
                      )
                      return (
                        <tr key={`${item.source}-${item.post_id}`}>
                          <td>
                            {preview ? (
                              <img
                                src={preview}
                                alt=""
                                className="debug-thumb"
                                loading="lazy"
                              />
                            ) : (
                              "—"
                            )}
                          </td>
                          <td>{item.bucket_id}</td>
                          <td>
                            {item.source}/{item.post_id}
                          </td>
                          {tagRecallReports.map((r) => {
                            const match = (r.items || []).find(
                              (x) =>
                                x.source === item.source && x.post_id === item.post_id
                            )
                            const parts = (match?.tag_results || []).map((tr) => {
                              const marks = [
                                tr.hit_at_threshold ? "T" : "·",
                                tr.hit_at_top_k ? "K" : "·",
                              ].join("")
                              const score =
                                tr.score == null ? "—" : Number(tr.score).toFixed(2)
                              return `${tr.tag}:${score}[${marks}]`
                            })
                            return (
                              <td key={`${r.tagger_model}-${item.post_id}`}>
                                <code className="muted">{parts.join(" · ") || "—"}</code>
                              </td>
                            )
                          })}
                        </tr>
                      )
                    })}
                  </tbody>
                </table>
              </div>
            </>
          )}
        </section>
      )}

      <section className="panel" aria-label="Tag false-positive benchmark">
        <h2>Tag false-positive benchmark</h2>
        <span className="kicker">
          Same curated suite. Probes taxonomy evidence tags (weight ≥ min) for your{" "}
          <strong>selected folders</strong> from Settings. FP = score ≥ threshold when the
          Danbooru post does not list that tag. Also reports folder-routing FPs.
        </span>
        <p className="muted">
          Selected:{" "}
          {(settingsSummary.selected_tags || []).length
            ? settingsSummary.selected_tags.join(", ")
            : "(none — pick folders on THR3SHR settings)"}
        </p>
        <div className="grid">
          <label>
            Tag / route threshold
            <input
              type="number"
              min="0.05"
              max="1"
              step="0.05"
              value={tagFpThreshold}
              disabled={tagFpLoading}
              onChange={(e) =>
                setTagFpThreshold(
                  Math.max(0.05, Math.min(1, Number(e.target.value) || 0.6))
                )
              }
              aria-label="Tag false positive threshold"
            />
          </label>
          <label>
            Min evidence weight
            <input
              type="number"
              min="0"
              max="1"
              step="0.05"
              value={tagFpMinWeight}
              disabled={tagFpLoading}
              onChange={(e) =>
                setTagFpMinWeight(
                  Math.max(0, Math.min(1, Number(e.target.value) || 0.85))
                )
              }
              aria-label="Minimum taxonomy evidence weight"
            />
          </label>
        </div>
        <label className="checkbox-row">
          <input
            type="checkbox"
            checked={tagFpAlsoWd}
            disabled={tagFpLoading}
            onChange={(e) => setTagFpAlsoWd(e.target.checked)}
          />
          Also report at WD general threshold (0.35)
        </label>
        <div className="actions">
          <button
            type="button"
            disabled={tagFpLoading || !(settingsSummary.selected_tags || []).length}
            onClick={handleTagFpEval}
            aria-label="Run tag false positive benchmark"
          >
            {tagFpLoading
              ? "Scoring curated suite for FPs…"
              : "Run false-positive benchmark"}
          </button>
        </div>
      </section>

      {tagFpResult && (
        <section className="panel debug-eval-results" aria-label="Tag false positive results">
          <h2>False-positive results</h2>
          <div className="stats">
            <span>Samples: {tagFpResult.count_samples ?? 0}</span>
            <span>Probe tags: {tagFpResult.probe_tag_count ?? 0}</span>
            <span>
              thr={tagFpResult.tag_threshold} · minW={tagFpResult.min_weight}
            </span>
          </div>
          <div className="table-wrap">
            <table>
              <thead>
                <tr>
                  <th scope="col">Model</th>
                  <th scope="col">Micro FPR</th>
                  <th scope="col">Macro FPR</th>
                  <th scope="col">FP / neg</th>
                  <th scope="col">Route FPR</th>
                  <th scope="col">Route FP</th>
                  <th scope="col">N</th>
                </tr>
              </thead>
              <tbody>
                {tagFpReports.map((row) => {
                  const tf = row.tag_fp || {}
                  const rf = row.folder_route_fp || {}
                  return (
                    <tr key={row.tagger_model}>
                      <td>{row.tagger_model}</td>
                      <td>{pct(tf.micro_fpr)}</td>
                      <td>{pct(tf.macro_fpr)}</td>
                      <td>
                        {tf.false_positives ?? 0}/{tf.negatives ?? 0}
                      </td>
                      <td>{pct(rf.fpr)}</td>
                      <td>
                        {rf.false_positives ?? 0}/{rf.evaluated ?? 0}
                      </td>
                      <td>{row.count_evaluated}</td>
                    </tr>
                  )
                })}
              </tbody>
            </table>
          </div>

          {tagFpAlt?.reports?.length > 0 && (
            <>
              <h3>Also at WD general thr={tagFpAlt.tag_threshold}</h3>
              <div className="table-wrap">
                <table>
                  <thead>
                    <tr>
                      <th scope="col">Model</th>
                      <th scope="col">Micro FPR</th>
                      <th scope="col">Route FPR</th>
                    </tr>
                  </thead>
                  <tbody>
                    {tagFpAlt.reports.map((row) => (
                      <tr key={`alt-${row.tagger_model}`}>
                        <td>{row.tagger_model}</td>
                        <td>{pct(row.tag_fp?.micro_fpr)}</td>
                        <td>{pct(row.folder_route_fp?.fpr)}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </>
          )}

          <h3>Top false-positive tags (side by side)</h3>
          <div className="table-wrap">
            <table>
              <thead>
                <tr>
                  <th scope="col">Tag</th>
                  {tagFpReports.map((r) => (
                    <th key={`${r.tagger_model}-fpr`} scope="col">
                      {r.tagger_model} FPR
                    </th>
                  ))}
                  {tagFpReports.map((r) => (
                    <th key={`${r.tagger_model}-cnt`} scope="col">
                      {r.tagger_model} fp/neg
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {Array.from(
                  new Set(
                    tagFpReports.flatMap((r) =>
                      (r.top_false_positive_tags || []).map((t) => t.tag)
                    )
                  )
                )
                  .slice(0, 25)
                  .map((tag) => {
                    const rowsByModel = Object.fromEntries(
                      tagFpReports.map((r) => [
                        r.tagger_model,
                        (r.top_false_positive_tags || []).find((t) => t.tag === tag),
                      ])
                    )
                    return (
                      <tr key={tag}>
                        <td>{tag}</td>
                        {tagFpReports.map((r) => (
                          <td key={`${r.tagger_model}-fpr-${tag}`}>
                            {pct(rowsByModel[r.tagger_model]?.fpr)}
                          </td>
                        ))}
                        {tagFpReports.map((r) => {
                          const row = rowsByModel[r.tagger_model]
                          return (
                            <td key={`${r.tagger_model}-cnt-${tag}`}>
                              {row
                                ? `${row.false_positives}/${row.negatives}`
                                : "0"}
                            </td>
                          )
                        })}
                      </tr>
                    )
                  })}
              </tbody>
            </table>
          </div>

          {tagFpReports.some((r) => (r.sample_route_fps || []).length > 0) && (
            <>
              <h3>Folder route false positives</h3>
              <div className="table-wrap">
                <table>
                  <thead>
                    <tr>
                      <th scope="col">Preview</th>
                      <th scope="col">Bucket</th>
                      <th scope="col">Truth roots</th>
                      {tagFpReports.map((r) => (
                        <th key={r.tagger_model} scope="col">
                          {r.tagger_model}
                        </th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {Array.from(
                      new Map(
                        tagFpReports
                          .flatMap((r) => r.sample_route_fps || [])
                          .map((i) => [`${i.source}:${i.post_id}`, i])
                      ).values()
                    ).map((item) => {
                      const preview = api.getTagRecallPreviewUrl(
                        item.source,
                        item.file_name
                      )
                      return (
                        <tr key={`${item.source}-${item.post_id}`}>
                          <td>
                            {preview ? (
                              <img
                                src={preview}
                                alt=""
                                className="debug-thumb"
                                loading="lazy"
                              />
                            ) : (
                              "—"
                            )}
                          </td>
                          <td>{item.bucket_id}</td>
                          <td>{(item.truth || []).join(", ") || "—"}</td>
                          {tagFpReports.map((r) => {
                            const match = (r.sample_route_fps || []).find(
                              (x) =>
                                x.source === item.source && x.post_id === item.post_id
                            )
                            if (!match) {
                              return (
                                <td key={`${r.tagger_model}-${item.post_id}`}>—</td>
                              )
                            }
                            const score =
                              match.score == null
                                ? ""
                                : ` (${Number(match.score).toFixed(2)})`
                            return (
                              <td key={`${r.tagger_model}-${item.post_id}`}>
                                {match.predicted || "—"}
                                {score}
                              </td>
                            )
                          })}
                        </tr>
                      )
                    })}
                  </tbody>
                </table>
              </div>
            </>
          )}
        </section>
      )}

      <section className="panel" aria-label="Style detector compare">
        <h2>Style detectors (real vs anime)</h2>
        <span className="kicker">
          Debug-only compare: dedicated ONNX classifiers (
          <code>deepghs/anime_real_cls</code> via imgutils) vs WD{" "}
          <code>real_life</code> taxonomy. Same photo/anime corpus as below. Enable the
          experimental style gate on the main THR3SHR settings (next to GIF/video) to use
          CAFormer in production runs.
        </span>
        <div className="grid">
          <label>
            Samples per class
            <input
              type="number"
              min="5"
              max="40"
              value={styleCount}
              disabled={styleLoading}
              onChange={(e) =>
                setStyleCount(Math.max(5, Math.min(40, Number(e.target.value) || 12)))
              }
              aria-label="Style detector samples per class"
            />
          </label>
        </div>
        <p className="muted">
          WD baseline uses settings model: <strong>{settingsSummary.tagger_model}</strong>
          {" · "}
          Experimental style gate:{" "}
          <strong>
            {settingsSummary.experimental_style_detector_enabled ? "ON" : "OFF"}
          </strong>{" "}
          (toggle on THR3SHR settings)
        </p>
        <div className="actions">
          <button
            type="button"
            disabled={styleLoading}
            onClick={handleStyleEval}
            aria-label="Run style detector comparison"
          >
            {styleLoading
              ? "Downloading samples & scoring detectors…"
              : "Compare style detectors"}
          </button>
        </div>
      </section>

      {styleResult && (
        <section className="panel debug-eval-results" aria-label="Style detector results">
          <h2>Style detector results</h2>
          <div className="stats">
            <span>
              Decision: <strong>{styleOverall.decision || "—"}</strong>
            </span>
            <span>Best: {styleOverall.best_detector || "—"}</span>
            <span>
              Paths: {styleResult.count_paths} (photos {styleResult.count_photos_fetched},
              anime {styleResult.count_anime_fetched})
            </span>
          </div>
          <p className="muted">{styleOverall.summary}</p>
          <p className="muted">{styleOverall.note}</p>
          <h3>Per detector</h3>
          <div className="table-wrap">
            <table>
              <thead>
                <tr>
                  <th scope="col">Detector</th>
                  <th scope="col">Decision</th>
                  <th scope="col">Typical P</th>
                  <th scope="col">Typical R</th>
                  <th scope="col">Typical F1</th>
                  <th scope="col">Anime FP</th>
                  <th scope="col">Uncertain</th>
                  <th scope="col">N</th>
                </tr>
              </thead>
              <tbody>
                {styleReports.map((row) => {
                  const t = row.conclusion?.typical_metrics || {}
                  return (
                    <tr key={row.detector_id}>
                      <td>{row.detector_id}</td>
                      <td>{row.conclusion?.decision}</td>
                      <td>{pct(t.precision)}</td>
                      <td>{pct(t.recall)}</td>
                      <td>{pct(t.f1)}</td>
                      <td>{pct(t.anime_false_positive_rate)}</td>
                      <td>{row.conclusion?.uncertain_count ?? 0}</td>
                      <td>{row.count_evaluated}</td>
                    </tr>
                  )
                })}
              </tbody>
            </table>
          </div>
          {styleReports[0]?.items?.length > 0 && (
            <>
              <h3>Samples ({styleResult.best_detector || styleReports[0].detector_id})</h3>
              <div className="table-wrap">
                <table>
                  <thead>
                    <tr>
                      <th scope="col">Preview</th>
                      <th scope="col">Truth</th>
                      {styleReports.map((r) => (
                        <th key={r.detector_id} scope="col">
                          {r.detector_id}
                        </th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {(styleReports[0].items || []).map((item, idx) => {
                      const preview = api.getRealismDebugPreviewUrl(
                        item.source,
                        item.file_name
                      )
                      return (
                        <tr key={item.sample_id}>
                          <td>
                            {preview ? (
                              <img
                                src={preview}
                                alt={item.title || item.sample_id}
                                className="debug-thumb"
                              />
                            ) : (
                              "—"
                            )}
                          </td>
                          <td>
                            {item.label}
                            <div className="muted">{item.bucket}</div>
                          </td>
                          {styleReports.map((r) => {
                            const cell = (r.items || [])[idx]
                            if (!cell) return <td key={r.detector_id}>—</td>
                            return (
                              <td key={r.detector_id}>
                                {cell.predicted_bucket}
                                <div className="muted">
                                  {pct(cell.confidence)} {cell.correct ? "ok" : "miss"}
                                </div>
                              </td>
                            )
                          })}
                        </tr>
                      )
                    })}
                  </tbody>
                </table>
              </div>
            </>
          )}
        </section>
      )}

      <section className="panel" aria-label="Real-life vs anime eval">
        <h2>Real-life vs anime</h2>
        <span className="kicker">
          Remote people portraits (RandomUser CDN) vs Safebooru anime, including realistic /
          photorealistic / 3d edge cases. Scores through production <code>real_life</code> taxonomy
          routing.
        </span>
        <div className="grid">
          <label>
            Samples per class
            <input
              type="number"
              min="5"
              max="40"
              value={realismCount}
              disabled={realismLoading}
              onChange={(e) =>
                setRealismCount(Math.max(5, Math.min(40, Number(e.target.value) || 12)))
              }
              aria-label="Realism samples per class"
            />
          </label>
          <label className="inline-check">
            <input
              type="checkbox"
              checked={realismCompare}
              disabled={realismLoading}
              onChange={(e) => setRealismCompare(e.target.checked)}
            />
            Compare all models (EVA02 / SwinV2 / ML-Danbooru)
          </label>
        </div>
        <p className="muted">
          Current settings model: <strong>{settingsSummary.tagger_model}</strong>
          {!realismCompare ? " (used when compare is off)" : " (ignored while comparing)"}
        </p>
        <div className="actions">
          <button
            type="button"
            disabled={realismLoading}
            onClick={handleRealismEval}
            aria-label="Run real-life versus anime evaluation"
          >
            {realismLoading
              ? "Fetching photos/anime & scoring… (can take a while)"
              : "Run real-life vs anime eval"}
          </button>
        </div>
      </section>

      {realismResult && (
        <section className="panel debug-eval-results" aria-label="Realism eval results">
          <h2>Realism results</h2>
          <div className="stats">
            <span>
              Decision:{" "}
              <strong>
                {(overall && overall.decision) || conclusion.decision || "—"}
              </strong>
            </span>
            <span>
              Evaluated: {realismResult.count_evaluated} (photos fetched{" "}
              {realismResult.count_photos_fetched}, anime {realismResult.count_anime_fetched})
            </span>
            <span>Model shown: {realismResult.tagger_model}</span>
            {overall?.best_model ? <span>Best model: {overall.best_model}</span> : null}
          </div>
          <p className="muted">
            {(overall && overall.summary) || conclusion.summary}
          </p>
          <p className="muted">
            {(overall && overall.video_and_gif) || conclusion.video_note}
          </p>

          <h3>Typical photo vs anime (GO gate)</h3>
          <div className="stats">
            {[
              ["Precision", (conclusion.typical_metrics || metrics).precision],
              ["Recall", (conclusion.typical_metrics || metrics).recall],
              ["F1", (conclusion.typical_metrics || metrics).f1],
              [
                "Anime FP",
                (conclusion.typical_metrics || metrics).anime_false_positive_rate,
              ],
            ].map(([label, value]) => (
              <div key={label} className="metric">
                <span className="label">{label}</span>
                <span className="value">{pct(value)}</span>
              </div>
            ))}
            <div className="metric">
              <span className="label">Edge quarantine</span>
              <span className="value">
                {conclusion.edge_quarantine_count ?? "—"}/{conclusion.edge_sample_count ?? "—"}
              </span>
            </div>
          </div>
          <h3>Including photoreal/3d edges</h3>
          <div className="stats">
            <div className="metric">
              <span className="label">Precision</span>
              <span className="value">{pct(metrics.precision)}</span>
            </div>
            <div className="metric">
              <span className="label">Recall</span>
              <span className="value">{pct(metrics.recall)}</span>
            </div>
            <div className="metric">
              <span className="label">Anime FP rate</span>
              <span className="value">{pct(metrics.anime_false_positive_rate)}</span>
            </div>
          </div>

          {multi?.reports?.length ? (
            <>
              <h3>Per-model</h3>
              <div className="table-wrap">
                <table>
                  <thead>
                    <tr>
                      <th>Model</th>
                      <th>Decision</th>
                      <th>P</th>
                      <th>R</th>
                      <th>F1</th>
                      <th>Anime FP</th>
                      <th>N</th>
                    </tr>
                  </thead>
                  <tbody>
                    {multi.reports.map((row) => (
                      <tr key={row.tagger_model}>
                        <td>{row.tagger_model}</td>
                        <td>{row.conclusion?.decision}</td>
                        <td>{pct(row.metrics?.precision)}</td>
                        <td>{pct(row.metrics?.recall)}</td>
                        <td>{pct(row.metrics?.f1)}</td>
                        <td>{pct(row.metrics?.anime_false_positive_rate)}</td>
                        <td>{row.count_evaluated}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </>
          ) : null}

          {realismResult.by_label && Object.keys(realismResult.by_label).length > 0 ? (
            <>
              <h3>Edge / label breakdown</h3>
              <div className="stats">
                {Object.entries(realismResult.by_label).map(([label, row]) => (
                  <div key={label} className="metric">
                    <span className="label">{label}</span>
                    <span className="value">
                      acc {pct(row.accuracy)} (n={row.count})
                    </span>
                  </div>
                ))}
              </div>
            </>
          ) : null}

          {realismResult.errors?.length > 0 && (
            <div className="error">{realismResult.errors.join(" · ")}</div>
          )}

          <h3>Samples</h3>
          <div className="table-wrap">
            <table>
              <thead>
                <tr>
                  <th>Preview</th>
                  <th>Truth</th>
                  <th>Predicted</th>
                  <th>Evidence</th>
                  <th>Folder</th>
                </tr>
              </thead>
              <tbody>
                {(realismResult.items || []).map((item) => {
                  const previewUrl = api.getRealismDebugPreviewUrl(item.source, item.file_name)
                  const ev = item.evidence_scores || {}
                  const evText = ["realistic", "photorealistic", "photo_(medium)", "3d"]
                    .map((tag) => `${tag}:${Number(ev[tag] || 0).toFixed(2)}`)
                    .join(" ")
                  return (
                    <tr
                      key={item.sample_id}
                      className={item.correct ? "" : "needs-review"}
                    >
                      <td>
                        {previewUrl ? (
                          <img
                            src={previewUrl}
                            alt={item.title || item.sample_id}
                            className="debug-thumb"
                          />
                        ) : (
                          <span className="muted">n/a</span>
                        )}
                      </td>
                      <td>
                        {item.bucket}
                        <div className="muted">
                          {item.label} · {item.query}
                        </div>
                      </td>
                      <td>
                        {item.predicted_bucket}{" "}
                        {item.correct ? (
                          <span className="muted">ok</span>
                        ) : (
                          <strong>miss</strong>
                        )}
                      </td>
                      <td>{evText}</td>
                      <td>
                        {item.primary_folder
                          ? `${item.primary_folder} (${Number(item.primary_score || 0).toFixed(3)})`
                          : "—"}
                      </td>
                    </tr>
                  )
                })}
              </tbody>
            </table>
          </div>
        </section>
      )}

      <section className="panel" aria-label="THR3SHR debug controls">
        <h2>SFW tag recall</h2>
        <span className="kicker">
          Safebooru / Danbooru only. Change tagger and destination tags on the main THR3SHR page,
          then return here.
        </span>
        <div className="grid">
          <label>
            Source
            <select
              value={source}
              onChange={(e) => {
                const next = e.target.value
                setSource(next)
                const meta = sources.find((s) => s.id === next)
                if (meta?.max_content_tags != null) {
                  setPullTags((prev) => prev.slice(0, meta.max_content_tags))
                }
              }}
              disabled={loading}
              aria-label="SFW image source"
            >
              {sources.map((src) => (
                <option key={src.id} value={src.id}>
                  {src.label} ({src.sfw_policy})
                </option>
              ))}
            </select>
          </label>
          <label>
            Sample count
            <input
              type="number"
              min="5"
              max="30"
              value={count}
              disabled={loading}
              onChange={(e) =>
                setCount(Math.max(5, Math.min(30, Number(e.target.value) || 10)))
              }
              aria-label="SFW sample count"
            />
          </label>
        </div>
        <p className="muted">
          Model: <strong>{settingsSummary.tagger_model}</strong> · Threshold:{" "}
          <strong>{Number(settingsSummary.confidence_threshold).toFixed(2)}</strong> · Destination
          tags:{" "}
          {settingsSummary.selected_tags.length
            ? settingsSummary.selected_tags.join(", ")
            : "(none — classify preview will need review)"}
          {sourceMeta?.max_content_tags != null
            ? ` · ${sourceMeta.label} max content tags: ${sourceMeta.max_content_tags}`
            : ""}
        </p>
        <label>
          Tags to pull
          <input
            value={tagQuery}
            onChange={(e) => setTagQuery(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter") {
                e.preventDefault()
                const value = tagQuery.trim()
                if (value) {
                  handleAddPullTag(value)
                  setTagQuery("")
                }
              }
            }}
            placeholder="Search or type a tag, Enter to add"
            disabled={loading}
            aria-label="Search tags to pull"
          />
        </label>
        <div className="tag-suggestions" role="listbox" aria-label="Tag suggestions for debug pull">
          {tagOptions.map((tag) => (
            <button
              key={tag}
              type="button"
              className="chip"
              disabled={loading || pullTags.includes(tag)}
              onClick={() => handleAddPullTag(tag)}
            >
              {tag}
            </button>
          ))}
        </div>
        <div className="stats">
          {pullTags.length === 0 ? (
            <span className="muted">No pull tags yet</span>
          ) : (
            pullTags.map((tag) => (
              <span key={tag} className="chip">
                {tag}
                <button
                  type="button"
                  onClick={() => setPullTags((prev) => prev.filter((t) => t !== tag))}
                  aria-label={`Remove pull tag ${tag}`}
                  disabled={loading}
                >
                  ×
                </button>
              </span>
            ))
          )}
        </div>
        <div className="actions">
          <button
            type="button"
            disabled={loading || pullTags.length === 0}
            onClick={handleFetchEvaluate}
            aria-label="Fetch and evaluate SFW samples"
          >
            {loading ? "Fetching & evaluating…" : "Fetch & evaluate"}
          </button>
        </div>
      </section>

      {result && (
        <section className="panel debug-eval-results" aria-label="THR3SHR debug results">
          <h2>SFW results</h2>
          <div className="stats">
            <span>
              Query: <code>{result.query}</code>
            </span>
            <span>
              Evaluated: {result.count_evaluated}/{result.count_requested}
            </span>
          </div>
          {result.errors?.length > 0 && (
            <div className="error">{result.errors.join(" · ")}</div>
          )}
          <h3>Recall @ {Number(result.confidence_threshold).toFixed(2)}</h3>
          <div className="stats">
            {(result.recall || []).map((row) => (
              <div key={row.tag} className="metric">
                <span className="label">{row.tag}</span>
                <span className="value">
                  {row.hit_rate == null
                    ? "n/a"
                    : `${(row.hit_rate * 100).toFixed(0)}% (${row.hits_at_threshold}/${row.present_in_posts})`}
                </span>
              </div>
            ))}
          </div>
          <h3>Classify preview</h3>
          <div className="table-wrap">
            <table>
              <thead>
                <tr>
                  <th>Preview</th>
                  <th>Post</th>
                  <th>Pull scores</th>
                  <th>Primary</th>
                  <th>Review</th>
                </tr>
              </thead>
              <tbody>
                {(result.items || []).map((item) => {
                  const previewUrl = api.getSfwDebugPreviewUrl(item.source, item.file_name)
                  const pullText = Object.entries(item.pull_tag_scores || {})
                    .map(([tag, score]) =>
                      `${tag}:${score == null ? "—" : Number(score).toFixed(2)}`
                    )
                    .join(" ")
                  return (
                    <tr
                      key={`${item.source}-${item.post_id}`}
                      className={item.needs_review ? "needs-review" : ""}
                    >
                      <td>
                        {previewUrl ? (
                          <img
                            src={previewUrl}
                            alt={`Post ${item.post_id}`}
                            className="debug-thumb"
                          />
                        ) : (
                          <span className="muted">n/a</span>
                        )}
                      </td>
                      <td>
                        {item.post_id}
                        <div className="muted">{(item.known_tags || []).join(", ")}</div>
                      </td>
                      <td>{pullText}</td>
                      <td>
                        {item.primary_tag
                          ? `${item.primary_tag} (${Number(item.primary_score).toFixed(3)})`
                          : "—"}
                      </td>
                      <td>{item.needs_review ? item.review_reason || "needs review" : "ok"}</td>
                    </tr>
                  )
                })}
              </tbody>
            </table>
          </div>
        </section>
      )}

      <footer className="app-footer" aria-label="Attribution">
        <p>
          THR3SHR © 2026 Dinamush · code MIT · creative{" "}
          <a
            href="https://creativecommons.org/licenses/by/4.0/"
            target="_blank"
            rel="noreferrer"
          >
            CC BY 4.0
          </a>
          {" · "}
          <a
            href="https://github.com/Dinamush/thr3shr/blob/main/ATTRIBUTION.md"
            target="_blank"
            rel="noreferrer"
          >
            model attributions
          </a>
        </p>
      </footer>
    </div>
  )
}
