pub(crate) const SPEAKER_DIRECTORY_HTML: &str = r#"<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Speaker Directory</title>
  <style>
    :root {
      color-scheme: light;
      font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      background: #f5f6f8;
      color: #171b22;
    }
    body {
      margin: 0;
    }
    header {
      background: #ffffff;
      border-bottom: 1px solid #d9dde5;
      padding: 24px 32px 18px;
    }
    main {
      max-width: 1120px;
      margin: 0 auto;
      padding: 24px 24px 40px;
    }
    h1, h2, h3 {
      margin: 0;
      letter-spacing: 0;
    }
    h1 {
      font-size: 28px;
      line-height: 1.2;
    }
    h2 {
      font-size: 18px;
      margin-bottom: 12px;
    }
    h3 {
      font-size: 15px;
      margin-bottom: 8px;
    }
    .summary {
      display: grid;
      grid-template-columns: repeat(3, minmax(0, 1fr));
      gap: 12px;
      margin-top: 18px;
    }
    .metric, .item {
      background: #ffffff;
      border: 1px solid #d9dde5;
      border-radius: 8px;
    }
    .metric {
      padding: 12px 14px;
      min-width: 0;
    }
    .label {
      color: #5b6472;
      font-size: 12px;
      text-transform: uppercase;
    }
    .value {
      font-size: 15px;
      margin-top: 5px;
      overflow-wrap: anywhere;
    }
    .grid {
      display: grid;
      grid-template-columns: minmax(280px, 0.9fr) minmax(320px, 1.1fr);
      gap: 16px;
      align-items: start;
    }
    .panel {
      margin-bottom: 22px;
    }
    .list {
      display: grid;
      gap: 10px;
    }
    .item {
      padding: 12px;
    }
    .row {
      display: flex;
      justify-content: space-between;
      gap: 12px;
      align-items: baseline;
    }
    .mono {
      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
      font-size: 13px;
      overflow-wrap: anywhere;
    }
    .muted {
      color: #5b6472;
    }
    .status {
      display: inline-flex;
      align-items: center;
      border-radius: 999px;
      padding: 3px 9px;
      background: #edf2f7;
      font-size: 12px;
      text-transform: capitalize;
    }
    .valid {
      background: #e7f6ed;
      color: #17663a;
    }
    .invalid {
      background: #ffe9e6;
      color: #9a2516;
    }
    .missing {
      background: #fff4db;
      color: #77510e;
    }
    .metadata, .span {
      margin-top: 8px;
      padding-top: 8px;
      border-top: 1px solid #eceff3;
    }
    .profile-form {
      display: grid;
      gap: 8px;
      margin-top: 10px;
    }
    .trace-controls {
      display: grid;
      gap: 8px;
      margin-bottom: 12px;
    }
    .report {
      display: grid;
      gap: 5px;
      margin-top: 8px;
      font-size: 13px;
    }
    input, textarea {
      width: 100%;
      box-sizing: border-box;
      border: 1px solid #cbd2dc;
      border-radius: 6px;
      padding: 8px 10px;
      font: inherit;
      background: #ffffff;
      color: #171b22;
    }
    textarea {
      min-height: 78px;
      resize: vertical;
      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
      font-size: 13px;
    }
    .actions {
      display: flex;
      gap: 8px;
      flex-wrap: wrap;
    }
    button {
      border: 1px solid #253043;
      border-radius: 6px;
      background: #253043;
      color: #ffffff;
      padding: 7px 10px;
      font: inherit;
      cursor: pointer;
    }
    button.secondary {
      background: #ffffff;
      color: #9a2516;
      border-color: #e0b4ae;
    }
    .error {
      color: #9a2516;
    }
    @media (max-width: 760px) {
      header {
        padding: 20px;
      }
      main {
        padding: 18px;
      }
      .summary, .grid {
        grid-template-columns: 1fr;
      }
      .row {
        display: block;
      }
    }
  </style>
</head>
<body>
  <header>
    <h1>Speaker Directory</h1>
    <div class="summary">
      <div class="metric"><div class="label">Scope</div><div id="scope" class="value">...</div></div>
      <div class="metric"><div class="label">Library</div><div id="library-status" class="value">...</div></div>
      <div class="metric"><div class="label">Trace</div><div id="trace-status" class="value">...</div></div>
    </div>
  </header>
  <main>
    <section class="panel">
      <h2>Backing Path</h2>
      <div id="path" class="mono"></div>
    </section>
    <div class="grid">
      <section class="panel">
        <h2>Profiles</h2>
        <div id="profiles" class="list"></div>
      </section>
      <section class="panel">
        <h2>Speaker Trace</h2>
        <form id="trace-rescan-form" class="trace-controls">
          <input id="trace-scan-root" type="text" placeholder="Scan root" aria-label="Speaker Trace scan root">
          <div class="actions">
            <button type="submit">Rescan</button>
          </div>
          <div id="trace-rescan-error" class="error"></div>
          <div id="trace-rescan-report" class="report muted"></div>
        </form>
        <div id="trace" class="list"></div>
      </section>
    </div>
  </main>
  <script>
    const sessionToken = "__SESSION_TOKEN__";
    const text = (value) => value === undefined || value === null || value === "" ? "none" : String(value);
    const el = (tag, className, value) => {
      const node = document.createElement(tag);
      if (className) node.className = className;
      if (value !== undefined) node.textContent = value;
      return node;
    };
    const status = (value) => {
      const node = el("span", `status ${value}`, value);
      return node;
    };
    const metadataText = (metadata) => {
      const entries = Object.entries(metadata || {});
      if (!entries.length) return "metadata: none";
      return entries.map(([key, value]) => `${key}: ${value}`).join(" | ");
    };
    const metadataLines = (metadata) => Object.entries(metadata || {})
      .map(([key, value]) => `${key}=${value}`)
      .join("\n");
    const parseMetadata = (value) => {
      const metadata = {};
      for (const rawLine of value.split(/\r?\n/)) {
        const line = rawLine.trim();
        if (!line) continue;
        const index = line.indexOf("=");
        if (index <= 0) throw new Error("metadata lines must use key=value");
        metadata[line.slice(0, index).trim()] = line.slice(index + 1).trim();
      }
      return metadata;
    };
    const renderRescanReport = (report) => {
      const node = document.getElementById("trace-rescan-report");
      node.replaceChildren();
      if (!report) return;
      const stats = report.stats || {};
      node.append(el("div", "", `Scanned files: ${stats.scannedFiles ?? 0}`));
      node.append(el("div", "", `Accepted entries: ${stats.acceptedEntries ?? 0}`));
      node.append(el("div", "", `Ignored non-JSON files: ${stats.ignoredNonJsonFiles ?? 0}`));
      node.append(el("div", "", `Malformed JSON errors: ${stats.malformedJsonErrors ?? 0}`));
    };
    const api = async (path, options = {}) => {
      const response = await fetch(path, {
        ...options,
        headers: {
          "Content-Type": "application/json",
          "X-Native-Whisperx-Session-Token": sessionToken,
          ...(options.headers || {})
        }
      });
      if (!response.ok) {
        throw new Error((await response.text()).trim() || response.statusText);
      }
      return response.json();
    };
    const renderProfile = (profiles, profile) => {
      const item = el("article", "item");
      const row = el("div", "row");
      row.append(el("strong", "", text(profile.label)));
      row.append(el("span", "mono muted", text(profile.id)));
      item.append(row);
      item.append(el("div", "metadata muted", metadataText(profile.metadata)));

      const form = el("form", "profile-form");
      const label = document.createElement("input");
      label.value = profile.label || "";
      label.setAttribute("aria-label", "Speaker label");
      const metadata = document.createElement("textarea");
      metadata.value = metadataLines(profile.metadata);
      metadata.setAttribute("aria-label", "Speaker metadata");
      const actions = el("div", "actions");
      const save = document.createElement("button");
      save.type = "submit";
      save.textContent = "Save";
      const remove = document.createElement("button");
      remove.type = "button";
      remove.className = "secondary";
      remove.textContent = "Delete";
      const error = el("div", "error");
      actions.append(save, remove);
      form.append(label, metadata, actions, error);
      form.addEventListener("submit", async (event) => {
        event.preventDefault();
        error.textContent = "";
        try {
          await api(`/api/profiles/${encodeURIComponent(profile.id)}`, {
            method: "PUT",
            body: JSON.stringify({
              id: profile.id,
              label: label.value,
              metadata: parseMetadata(metadata.value)
            })
          });
          await refresh();
        } catch (err) {
          error.textContent = err.message;
        }
      });
      remove.addEventListener("click", async () => {
        error.textContent = "";
        try {
          await api(`/api/profiles/${encodeURIComponent(profile.id)}`, { method: "DELETE" });
          await refresh();
        } catch (err) {
          error.textContent = err.message;
        }
      });
      item.append(form);
      profiles.append(item);
    };
    const render = (state) => {
      document.getElementById("scope").textContent = state.scope;
      document.getElementById("path").textContent = state.path;
      document.getElementById("library-status").replaceChildren(status(state.library.status));
      document.getElementById("trace-status").replaceChildren(status(state.trace.status));

      const profiles = document.getElementById("profiles");
      profiles.replaceChildren();
      if (!state.profiles.length) {
        profiles.append(el("div", "muted", "No enrolled profiles"));
      }
      for (const profile of state.profiles) {
        renderProfile(profiles, profile);
      }

      const trace = document.getElementById("trace");
      trace.replaceChildren();
      if (!state.trace.speakers.length) {
        trace.append(el("div", "muted", state.trace.message || "No trace groups"));
      }
      for (const speaker of state.trace.speakers) {
        const item = el("article", "item");
        item.append(el("h3", "", speaker.label || speaker.anonymousLabel || speaker.profileId));
        item.append(el("div", "mono muted", speaker.profileId || speaker.anonymousLabel));
        for (const file of speaker.files) {
          const fileNode = el("div", "metadata");
          fileNode.append(el("div", "mono", file.sourceFile));
          fileNode.append(el("div", "muted", `${file.segmentCount} segments | ${file.totalDuration.toFixed(3)}s`));
          for (const span of file.spans) {
            const spanNode = el("div", "span muted");
            spanNode.textContent = `${text(span.startSeconds)}-${text(span.endSeconds)}: ${span.snippet}`;
            fileNode.append(spanNode);
          }
          item.append(fileNode);
        }
        trace.append(item);
      }
      for (const traceError of state.trace.errors || []) {
        const item = el("article", "item error");
        item.append(el("strong", "", "Malformed JSON"));
        item.append(el("div", "mono", traceError.sourceFile));
        item.append(el("div", "", traceError.message));
        trace.append(item);
      }
    };
    const refresh = async () => {
      const response = await fetch("/api/state");
      render(await response.json());
    };
    document.getElementById("trace-rescan-form").addEventListener("submit", async (event) => {
      event.preventDefault();
      const error = document.getElementById("trace-rescan-error");
      const scanRoot = document.getElementById("trace-scan-root").value.trim();
      error.textContent = "";
      try {
        const response = await api("/api/trace/rebuild", {
          method: "POST",
          body: JSON.stringify(scanRoot ? { scanRoot } : {})
        });
        render(response.state);
        renderRescanReport(response.report);
      } catch (err) {
        error.textContent = err.message;
      }
    });
    refresh().catch((error) => {
      document.getElementById("trace").textContent = error.message;
    });
  </script>
</body>
</html>
"#;
