use axum::{
    http::header,
    response::{Html, IntoResponse, Response},
};

pub(crate) fn index_response() -> Response {
    (
        [
            (
                header::CACHE_CONTROL,
                "no-store, no-cache, must-revalidate, max-age=0",
            ),
            (header::PRAGMA, "no-cache"),
            (header::EXPIRES, "0"),
        ],
        Html(INDEX_HTML),
    )
        .into_response()
}

const INDEX_HTML: &str = r##"<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>aster-webui</title>
  <style>
    :root {
      --bg: #090d12;
      --surface: #10161d;
      --surface-2: #141b24;
      --surface-3: #0c1117;
      --surface-4: #172230;
      --line: #223042;
      --line-soft: #182333;
      --text: #eef3f8;
      --muted: #95a7ba;
      --faint: #667789;
      --accent: #6ee7b7;
      --accent-2: #b9f66a;
      --accent-3: #7ec8ff;
      --warn: #f6c65b;
      --danger: #f18479;
      --shadow: 0 18px 60px rgba(0, 0, 0, .28);
    }
    * { box-sizing: border-box; }
    body {
      margin: 0;
      background: linear-gradient(180deg, #0a1112 0, var(--bg) 210px);
      color: var(--text);
      font-family: Inter, "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      letter-spacing: 0;
    }
    main {
      width: min(1320px, calc(100% - 32px));
      margin: 0 auto;
      padding: 18px 0 32px;
    }
    .topbar {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 16px;
      margin-bottom: 14px;
    }
    .brand {
      display: flex;
      align-items: center;
      gap: 12px;
      min-width: 0;
    }
    .brand-mark {
      width: 38px;
      height: 38px;
      border: 1px solid rgba(110, 231, 183, .36);
      border-radius: 8px;
      display: grid;
      place-items: center;
      background: #0d1719;
      color: var(--accent);
      font-weight: 800;
    }
    .brand h1 {
      margin: 0;
      font-size: 1.55rem;
      line-height: 1.05;
      letter-spacing: 0;
    }
    .brand p { margin: 3px 0 0; color: var(--muted); font-size: .9rem; }
    .layout { display: grid; grid-template-columns: minmax(0, 1.45fr) 520px; gap: 14px; }
    .stack { display: grid; gap: 16px; }
    .tabbar {
      display: inline-flex;
      gap: 6px;
      margin-bottom: 14px;
      padding: 4px;
      border: 1px solid var(--line);
      border-radius: 8px;
      background: rgba(10, 15, 20, .72);
    }
    .tab-button {
      min-height: 38px;
      padding: 8px 14px;
      border: 0;
      border-radius: 6px;
      background: transparent;
      color: var(--muted);
      font-weight: 700;
    }
    .tab-button:hover {
      transform: none;
      background: rgba(20, 29, 40, .8);
      border-color: transparent;
    }
    .tab-button.active {
      background: linear-gradient(135deg, rgba(110, 231, 183, .16), rgba(185, 246, 106, .16));
      color: var(--text);
      box-shadow: inset 0 0 0 1px rgba(110, 231, 183, .18);
    }
    .tab-panel[hidden] { display: none; }
    .panel, .card {
      background: linear-gradient(180deg, rgba(18, 25, 34, .98), rgba(13, 18, 25, .98));
      border: 1px solid var(--line);
      border-radius: 8px;
      box-shadow: var(--shadow);
    }
    .panel { padding: 16px; }
    .card { padding: 14px; }
    .section-head {
      display: flex;
      align-items: baseline;
      justify-content: space-between;
      gap: 12px;
      margin-bottom: 12px;
      padding-bottom: 10px;
      border-bottom: 1px solid rgba(34, 48, 66, .72);
    }
    h2 {
      margin: 0;
      font-size: 1.05rem;
      line-height: 1.2;
      letter-spacing: 0;
    }
    .muted { color: var(--muted); }
    .subtle { color: var(--faint); font-size: .82rem; }
    .controls {
      display: grid;
      grid-template-columns: 132px minmax(220px, 1fr) auto;
      gap: 10px;
      align-items: end;
    }
    label { display: grid; gap: 7px; color: var(--muted); font-size: .82rem; }
    input, button {
      border-radius: 8px;
      border: 1px solid var(--line);
      padding: 10px 12px;
      font: inherit;
      min-height: 42px;
    }
    input {
      background: var(--surface-3);
      color: var(--text);
      min-width: 0;
    }
    input:focus-visible, button:focus-visible {
      outline: 2px solid rgba(110, 231, 183, .65);
      outline-offset: 2px;
    }
    button {
      cursor: pointer;
      color: var(--text);
      background: var(--surface-2);
      transition: transform .12s ease, border-color .12s ease, background .12s ease;
    }
    button:hover {
      transform: translateY(-1px);
      border-color: #38516c;
      background: #182231;
    }
    .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
    .primary {
      background: linear-gradient(135deg, var(--accent), var(--accent-2));
      color: #071016;
      border-color: transparent;
      font-weight: 800;
    }
    .ghost { background: var(--surface-3); color: var(--text); }
    .danger {
      background: rgba(241, 132, 121, .14);
      color: #ffd2cd;
      border-color: rgba(241, 132, 121, .24);
    }
    .toggle-row {
      display: flex;
      gap: 8px;
      flex-wrap: wrap;
      margin-top: 12px;
    }
    .select-wrap select {
      min-height: 42px;
      border-radius: 8px;
      border: 1px solid var(--line);
      background: var(--surface-3);
      color: var(--text);
      padding: 10px 12px;
      font: inherit;
    }
    .toggle-pill {
      display: inline-flex;
      align-items: center;
      gap: 9px;
      min-height: 38px;
      padding: 8px 12px;
      border: 1px solid var(--line);
      border-radius: 8px;
      background: rgba(9, 14, 20, .72);
      color: var(--text);
      font-size: .88rem;
    }
    .toggle-pill input {
      margin: 0;
      min-height: auto;
      width: 16px;
      height: 16px;
      accent-color: var(--accent);
    }
    .status-grid {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 10px;
    }
    .status {
      min-height: 84px;
      display: grid;
      align-content: space-between;
      background: rgba(16, 22, 30, .86);
      position: relative;
      overflow: hidden;
    }
    .status::after {
      content: "";
      position: absolute;
      inset: 0 auto 0 0;
      width: 3px;
      background: linear-gradient(180deg, var(--accent), var(--accent-3));
      opacity: .9;
    }
    .status span, .metric-label {
      color: var(--muted);
      font-size: .78rem;
      letter-spacing: .02em;
      text-transform: uppercase;
    }
    .status strong {
      display: block;
      margin-top: 6px;
      font-size: 1.1rem;
      line-height: 1.18;
      overflow-wrap: anywhere;
    }
    .status small { display: block; margin-top: 5px; color: var(--muted); }
    .metrics-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
      gap: 10px;
    }
    .metric-card {
      min-height: 104px;
      border-left: 3px solid var(--accent);
      background: linear-gradient(180deg, rgba(17, 24, 32, .98), rgba(12, 17, 24, .98));
    }
    .metric-card:nth-child(2) { border-left-color: #78bdf7; }
    .metric-card:nth-child(3) { border-left-color: var(--warn); }
    .metric-card:nth-child(4) { border-left-color: #b99cff; }
    .metric-card:nth-child(5) { border-left-color: #f2a65a; }
    .metric-card:nth-child(6) { border-left-color: var(--danger); }
    .metric-card strong {
      display: block;
      margin-top: 8px;
      font-size: 1.25rem;
      line-height: 1.2;
    }
    .metric-card small {
      display: block;
      margin-top: 6px;
      color: var(--muted);
      line-height: 1.35;
    }
    .workbench {
      display: grid;
      grid-template-columns: minmax(0, 1.35fr) minmax(340px, .75fr);
      gap: 14px;
    }
    .system-tab {
      display: grid;
      gap: 14px;
    }
    .specs-summary {
      display: grid;
      grid-template-columns: repeat(3, minmax(0, 1fr));
      gap: 10px;
    }
    .gallery {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(176px, 1fr));
      gap: 10px;
    }
    .tile {
      border: 1px solid var(--line-soft);
      border-radius: 8px;
      overflow: hidden;
      background: #0b1016;
      min-width: 0;
    }
    .tile.selected {
      border-color: var(--accent-2);
      box-shadow: inset 0 0 0 1px rgba(185, 246, 106, .8);
    }
    .tile:hover { border-color: #32465d; }
    .tile img {
      width: 100%;
      aspect-ratio: 960 / 376;
      object-fit: cover;
      display: block;
      background: #020409;
    }
    .tile-body {
      padding: 10px;
      display: grid;
      gap: 9px;
    }
    .tile-title {
      min-height: 2.5em;
      line-height: 1.28;
      word-break: break-word;
      color: var(--text);
    }
    .tile-meta {
      color: var(--faint);
      font-size: .82rem;
      line-height: 1.25;
    }
    .mini { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }
    .mini button {
      min-height: 34px;
      padding: 7px 10px;
      font-size: .86rem;
    }
    .order {
      display: grid;
      gap: 8px;
    }
    .order-item {
      display: grid;
      grid-template-columns: 28px minmax(0, 1fr) auto;
      gap: 10px;
      align-items: center;
      border: 1px solid var(--line-soft);
      border-radius: 8px;
      padding: 10px;
      background: rgba(8, 12, 18, .78);
    }
    .order-item.current {
      border-color: rgba(110, 231, 183, .7);
      background: rgba(16, 36, 39, .72);
    }
    .index {
      width: 26px;
      height: 26px;
      display: grid;
      place-items: center;
      border-radius: 6px;
      background: rgba(185, 246, 106, .12);
      color: var(--accent-2);
      font-weight: 800;
    }
    .meta {
      display: grid;
      gap: 3px;
      min-width: 0;
    }
    .meta > div:first-child {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    .toast {
      position: fixed;
      bottom: 16px;
      right: 16px;
      min-width: 260px;
      max-width: 420px;
      padding: 12px 14px;
      border-radius: 8px;
      border: 1px solid var(--line);
      background: rgba(10, 14, 20, .96);
      display: none;
      box-shadow: var(--shadow);
      z-index: 50;
    }
    .nyan-wrap {
      position: fixed;
      right: 18px;
      bottom: 18px;
      width: 184px;
      pointer-events: none;
      z-index: 30;
      opacity: .92;
      padding: 10px 12px 8px;
      border: 1px solid rgba(126, 200, 255, .22);
      border-radius: 8px;
      background:
        linear-gradient(180deg, rgba(15, 22, 30, .88), rgba(10, 15, 22, .72));
      box-shadow:
        0 16px 36px rgba(0, 0, 0, .28),
        inset 0 0 0 1px rgba(110, 231, 183, .08);
      filter: drop-shadow(0 10px 18px rgba(0, 0, 0, .22));
    }
    .nyan-wrap::before {
      content: "UI mascot";
      display: block;
      margin-bottom: 6px;
      color: var(--muted);
      font-size: .68rem;
      font-weight: 700;
      letter-spacing: .08em;
      text-transform: uppercase;
    }
    .nyan-wrap img {
      display: block;
      width: 100%;
      height: auto;
    }
    @media (max-width: 1180px) {
      .layout, .workbench, .specs-summary { grid-template-columns: 1fr; }
      .status-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    }
    @media (max-width: 1024px) {
      .controls, .metrics-grid { grid-template-columns: 1fr; }
      .status-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
      .actions { justify-content: flex-start; }
    }
    @media (max-width: 720px) {
      main { width: min(100% - 20px, 1320px); padding-top: 12px; }
      .topbar { align-items: flex-start; }
      .brand h1 { font-size: 1.28rem; }
      .panel, .card { padding: 12px; }
      .gallery { grid-template-columns: 1fr; }
      .order-item { grid-template-columns: 28px minmax(0, 1fr); }
      .order-item .mini { grid-column: 2; }
      .nyan-wrap { width: 128px; right: 10px; bottom: 10px; opacity: .78; padding: 8px; }
      .nyan-wrap::before { margin-bottom: 4px; font-size: .62rem; }
    }
</style>
</head>
<body>
  <main>
    <header class="topbar">
      <div class="brand">
        <div class="brand-mark">A</div>
        <div>
          <h1>aster-webui</h1>
          <p>AOOSTAR display control</p>
        </div>
      </div>
      <div class="actions">
        <button id="apply" class="primary" type="button">Apply Rotation</button>
        <button id="refresh" class="ghost" type="button">Refresh</button>
      </div>
    </header>

    <nav class="tabbar" aria-label="Views">
      <button class="tab-button active" type="button" data-tab-target="screen-control">Screen Control</button>
      <button class="tab-button" type="button" data-tab-target="system-specs">System Specs</button>
    </nav>

    <section class="tab-panel" data-tab-panel="screen-control">
      <section class="layout">
        <div class="stack">
          <div class="panel">
            <div class="section-head">
              <h2>Rotation Setup</h2>
              <span class="subtle">/config/Monitor3.json</span>
            </div>
            <div class="controls">
              <label>
                <span>Switch Time</span>
                <input id="switchTime" type="number" min="1" max="600" value="10">
              </label>
              <label>
                <span>Upload Source</span>
                <input id="upload" type="file" accept="image/*">
              </label>
              <div class="actions">
                <button id="disable" class="danger" type="button">Disable</button>
              </div>
            </div>
          <div class="toggle-row">
            <label class="toggle-pill"><input id="specsEnabled" type="checkbox"> System Specs</label>
            <label class="toggle-pill"><input id="memesEnabled" type="checkbox"> Memes</label>
            <label class="toggle-pill"><input id="gifsEnabled" type="checkbox"> GIFs</label>
          </div>
          <div class="toggle-row">
            <label class="select-wrap">
              <span>Screen Layout</span>
              <select id="specsMode">
                <option value="cards">Cards</option>
                <option value="meters">Meters</option>
              </select>
            </label>
          </div>
        </div>
        </div>

        <aside class="status-grid">
          <div class="card status"><span>Panel</span><strong id="customPanelValue">-</strong></div>
          <div class="card status"><span>Images</span><strong id="imageCountValue">-</strong></div>
          <div class="card status"><span>Display</span><strong id="displayStatusValue">-</strong></div>
          <div class="card status"><span>Frame</span><strong id="currentImageValue">-</strong></div>
          <div class="card status"><span>Target</span><strong id="displayTargetValue">-</strong></div>
          <div class="card status"><span>Config</span><strong id="configPathValue">-</strong></div>
        </aside>
      </section>

      <section class="workbench">
        <div class="panel">
          <div class="section-head">
            <h2>Available Images</h2>
            <span class="subtle">960 x 376</span>
          </div>
          <div id="gallery" class="gallery"></div>
        </div>
        <div class="panel">
          <div class="section-head">
            <h2>Rotation Order</h2>
            <span class="subtle">Live queue</span>
          </div>
          <div id="order" class="order"></div>
          <p id="displayErrorValue" class="muted"></p>
        </div>
      </section>
    </section>

    <section class="tab-panel system-tab" data-tab-panel="system-specs" hidden>
      <section class="specs-summary">
        <div class="card status"><span>Display</span><strong id="displayStatusMirror">-</strong><small id="displayTargetMirror">-</small></div>
        <div class="card status"><span>Panel State</span><strong id="customPanelMirror">-</strong><small id="currentImageMirror">-</small></div>
        <div class="card status"><span>Config</span><strong id="configPathMirror">-</strong><small id="imageCountMirror">-</small></div>
      </section>

      <section class="panel">
        <div class="section-head">
          <h2>System Specs</h2>
          <span class="subtle">Live host metrics</span>
        </div>
        <section class="metrics-grid">
          <div class="card metric-card"><span class="metric-label">CPU</span><strong id="cpuUsageValue">-</strong><small id="cpuMetaValue">-</small></div>
          <div class="card metric-card"><span class="metric-label">Memory</span><strong id="memUsageValue">-</strong><small id="memMetaValue">-</small></div>
          <div class="card metric-card"><span class="metric-label">Storage</span><strong id="diskUsageValue">-</strong><small id="diskMetaValue">-</small></div>
          <div class="card metric-card"><span class="metric-label">Swap</span><strong id="swapUsageValue">-</strong><small id="swapMetaValue">-</small></div>
          <div class="card metric-card"><span class="metric-label">Load Avg</span><strong id="loadValue">-</strong><small id="uptimeValue">-</small></div>
          <div class="card metric-card"><span class="metric-label">Temperatures</span><strong id="tempCpuValue">-</strong><small id="tempGpuValue">-</small></div>
        </section>
      </section>
    </section>
  </main>
  <div id="toast" class="toast"></div>
  <div class="nyan-wrap" aria-hidden="true">
    <img src="/api/assets/nyan-cat.gif" alt="Nyancat">
  </div>

  <script>
    const state = {
      images: [],
      activeImages: [],
      setup: { switch_time: "10", custom_panel: false, specs_enabled: false, specs_mode: "cards", memes_enabled: false, gifs_enabled: true },
      display: {},
      system: {},
      activeTab: "screen-control",
      dirty: false,
    };
    const el = {
      gallery: document.getElementById("gallery"),
      order: document.getElementById("order"),
      tabButtons: Array.from(document.querySelectorAll("[data-tab-target]")),
      tabPanels: Array.from(document.querySelectorAll("[data-tab-panel]")),
      switchTime: document.getElementById("switchTime"),
      specsEnabled: document.getElementById("specsEnabled"),
      specsMode: document.getElementById("specsMode"),
      memesEnabled: document.getElementById("memesEnabled"),
      gifsEnabled: document.getElementById("gifsEnabled"),
      customPanelValue: document.getElementById("customPanelValue"),
      imageCountValue: document.getElementById("imageCountValue"),
      displayStatusValue: document.getElementById("displayStatusValue"),
      currentImageValue: document.getElementById("currentImageValue"),
      displayTargetValue: document.getElementById("displayTargetValue"),
      displayErrorValue: document.getElementById("displayErrorValue"),
      configPathValue: document.getElementById("configPathValue"),
      displayStatusMirror: document.getElementById("displayStatusMirror"),
      displayTargetMirror: document.getElementById("displayTargetMirror"),
      customPanelMirror: document.getElementById("customPanelMirror"),
      currentImageMirror: document.getElementById("currentImageMirror"),
      configPathMirror: document.getElementById("configPathMirror"),
      imageCountMirror: document.getElementById("imageCountMirror"),
      cpuUsageValue: document.getElementById("cpuUsageValue"),
      cpuMetaValue: document.getElementById("cpuMetaValue"),
      memUsageValue: document.getElementById("memUsageValue"),
      memMetaValue: document.getElementById("memMetaValue"),
      diskUsageValue: document.getElementById("diskUsageValue"),
      diskMetaValue: document.getElementById("diskMetaValue"),
      swapUsageValue: document.getElementById("swapUsageValue"),
      swapMetaValue: document.getElementById("swapMetaValue"),
      loadValue: document.getElementById("loadValue"),
      uptimeValue: document.getElementById("uptimeValue"),
      tempCpuValue: document.getElementById("tempCpuValue"),
      tempGpuValue: document.getElementById("tempGpuValue"),
      upload: document.getElementById("upload"),
      apply: document.getElementById("apply"),
      disable: document.getElementById("disable"),
      refresh: document.getElementById("refresh"),
      toast: document.getElementById("toast"),
    };

    function toast(msg) {
      el.toast.textContent = msg;
      el.toast.style.display = "block";
      clearTimeout(toast.timer);
      toast.timer = setTimeout(() => el.toast.style.display = "none", 2600);
    }

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

    function escapeHtml(value) {
      return String(value)
        .replaceAll("&", "&amp;")
        .replaceAll("<", "&lt;")
        .replaceAll(">", "&gt;")
        .replaceAll('"', "&quot;")
        .replaceAll("'", "&#39;");
    }

    function gifFrameLabel(image) {
      if (!image || !image.animated) return "";
      if (image.display_frame_count && image.display_frame_count !== image.frame_count) {
        return ` • GIF • ${image.frame_count} source • ${image.display_frame_count} display`;
      }
      return ` • GIF • ${image.frame_count} Frames`;
    }

    function orderFrameLabel(image) {
      if (!image || !image.animated) return "";
      if (image.display_frame_count && image.display_frame_count !== image.frame_count) {
        return `${image.frame_count} source • ${image.display_frame_count} display`;
      }
      return `${image.frame_count} Frames`;
    }

    function setActiveTab(name) {
      state.activeTab = name;
      for (const button of el.tabButtons) {
        const isActive = button.dataset.tabTarget === name;
        button.classList.toggle("active", isActive);
        button.setAttribute("aria-selected", isActive ? "true" : "false");
      }
      for (const panel of el.tabPanels) {
        panel.hidden = panel.dataset.tabPanel !== name;
      }
    }

    function move(index, delta) {
      const target = index + delta;
      if (target < 0 || target >= state.activeImages.length) return;
      const next = [...state.activeImages];
      const [item] = next.splice(index, 1);
      next.splice(target, 0, item);
      state.activeImages = next;
      markDirty();
      render();
    }

    function toggle(name) {
      if (state.activeImages.includes(name)) {
        state.activeImages = state.activeImages.filter((item) => item !== name);
      } else {
        state.activeImages = [...state.activeImages, name];
      }
      markDirty();
      render();
    }

    function markDirty() {
      state.dirty = true;
      updateDirtyUi();
    }

    function clearDirty() {
      state.dirty = false;
      updateDirtyUi();
    }

    function updateDirtyUi() {
      el.apply.textContent = state.dirty ? "Apply Rotation *" : "Apply Rotation";
      el.imageCountValue.textContent = `${state.activeImages.length}${state.dirty ? " *" : ""}`;
    }

    async function request(url, options = {}) {
      const res = await fetch(url, options);
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error || "request failed");
      return data;
    }

    async function load(options = {}) {
      const force = !!options.force;
      const preserveDraft = !!options.preserveDraft;
      if (state.dirty && !force) return;

      const draftActiveImages = [...state.activeImages];
      const data = await request("/api/state");
      state.images = data.images;
      state.activeImages = preserveDraft
        ? draftActiveImages.filter((name) => data.images.some((image) => image.name === name))
        : data.active_images;
      state.setup = preserveDraft
        ? {
            ...data.setup,
            switch_time: el.switchTime.value || data.setup.switch_time || "10",
            specs_enabled: el.specsEnabled.checked,
            specs_mode: el.specsMode.value || data.setup.specs_mode || "cards",
            memes_enabled: el.memesEnabled.checked,
            gifs_enabled: el.gifsEnabled.checked,
          }
        : data.setup;
      state.display = data.display || {};
      state.system = data.system || {};
      if (!preserveDraft) {
        el.switchTime.value = data.setup.switch_time || "10";
        el.specsEnabled.checked = !!data.setup.specs_enabled;
        el.specsMode.value = data.setup.specs_mode || "cards";
        el.memesEnabled.checked = !!data.setup.memes_enabled;
        el.gifsEnabled.checked = data.setup.gifs_enabled !== false;
      }
      const enabledModes = [
        state.setup.specs_enabled ? "Specs" : "",
        state.setup.memes_enabled ? "Memes" : "",
        state.setup.gifs_enabled ? "GIFs" : "",
      ].filter(Boolean);
      el.customPanelValue.textContent = state.setup.custom_panel
        ? (enabledModes.join(" + ") || "Active")
        : "Disabled";
      el.displayStatusValue.textContent = state.display.connected
        ? `Connected (${state.display.mode || "native"})`
        : (state.display.native_enabled ? "Disconnected" : "Disabled");
      el.currentImageValue.textContent = state.display.current_image || "-";
      el.displayTargetValue.textContent = state.display.device || "-";
      el.displayErrorValue.textContent = state.display.last_error || "";
      el.configPathValue.textContent = data.monitor_path;
      el.cpuUsageValue.textContent = `${state.system.cpu_usage_percent || "-"} %`;
      el.cpuMetaValue.textContent = `Load ${state.system.load_avg_one || "-"} / ${state.system.load_avg_five || "-"} / ${state.system.load_avg_fifteen || "-"}`;
      el.memUsageValue.textContent = `${state.system.mem_usage_percent || "-"} %`;
      el.memMetaValue.textContent = `${state.system.mem_used || "-"} / ${state.system.mem_total || "-"}`;
      el.diskUsageValue.innerHTML = `/mnt/cache ${state.system.cache_usage_percent || "-"}%<br>/mnt/user ${state.system.user_usage_percent || "-"}%`;
      el.diskMetaValue.textContent = `${state.system.cache_used || "-"} / ${state.system.cache_total || "-"} | ${state.system.user_used || "-"} / ${state.system.user_total || "-"}`;
      el.swapUsageValue.textContent = `${state.system.swap_usage_percent || "-"} %`;
      el.swapMetaValue.textContent = `${state.system.swap_used || "-"} / ${state.system.swap_total || "-"}`;
      el.loadValue.textContent = `${state.system.load_avg_one || "-"} / ${state.system.load_avg_five || "-"} / ${state.system.load_avg_fifteen || "-"}`;
      el.uptimeValue.textContent = `${state.system.cpu_count || "-"} cores • ${state.system.process_count || "-"} procs • Uptime ${state.system.uptime || "-"}`;
      el.tempCpuValue.textContent = `CPU ${state.system.temperature_cpu || "-"}`;
      el.tempGpuValue.textContent = `GPU ${state.system.temperature_gpu || "-"}`;
      el.displayStatusMirror.textContent = el.displayStatusValue.textContent;
      el.displayTargetMirror.textContent = state.display.device || "-";
      el.customPanelMirror.textContent = el.customPanelValue.textContent;
      el.currentImageMirror.textContent = state.display.current_image || "No active frame";
      el.configPathMirror.textContent = data.monitor_path;
      el.imageCountMirror.textContent = `${state.activeImages.length} selected`;
      updateDirtyUi();
      render();
    }

    async function uploadFile(event) {
      const file = event.target.files[0];
      if (!file) return;
      const form = new FormData();
      form.append("file", file);
      await request("/api/upload", { method: "POST", body: form });
      event.target.value = "";
      toast("Upload complete");
      await load({ force: true, preserveDraft: state.dirty });
    }

    async function apply() {
      await request("/api/panels/activate", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          images: state.activeImages,
          switch_time: Number.parseInt(el.switchTime.value || "10", 10),
          specs_enabled: el.specsEnabled.checked,
          specs_mode: el.specsMode.value,
          memes_enabled: el.memesEnabled.checked,
          gifs_enabled: el.gifsEnabled.checked,
        }),
      });
      clearDirty();
      toast("Rotation updated");
      await load({ force: true });
    }

    async function disablePanels() {
      await request("/api/panels/disable", { method: "POST" });
      clearDirty();
      toast("Custom panels disabled");
      await load({ force: true });
    }

    async function removeImage(name) {
      await request("/api/images/delete", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name }),
      });
      state.activeImages = state.activeImages.filter((item) => item !== name);
      clearDirty();
      toast("Image deleted");
      await load({ force: true });
    }

    function render() {
      const imageMap = new Map(state.images.map((image) => [image.name, image]));
      el.gallery.innerHTML = "";
      for (const image of state.images) {
        const selected = state.activeImages.includes(image.name);
        const tile = document.createElement("article");
        const safeLabel = escapeHtml(image.label);
        const safeName = escapeHtml(image.name);
        const safeUrl = escapeHtml(image.url);
        tile.className = "tile" + (selected ? " selected" : "");
        tile.innerHTML = `
          <img src="${safeUrl}" alt="${safeName}">
          <div class="tile-body">
            <div class="tile-title">${safeLabel}</div>
            <div class="tile-meta">${bytes(image.size)}${gifFrameLabel(image)}</div>
            <div class="mini">
              <button class="ghost" type="button" title="${selected ? "Remove" : "Add"}">${selected ? "Remove" : "Add"}</button>
              <button class="danger" type="button">Delete</button>
            </div>
          </div>
        `;
        const [toggleBtn, deleteBtn] = tile.querySelectorAll("button");
        toggleBtn.addEventListener("click", () => toggle(image.name));
        deleteBtn.addEventListener("click", () => removeImage(image.name));
        el.gallery.appendChild(tile);
      }

      el.order.innerHTML = "";
      if (state.setup.specs_enabled && !state.activeImages.length) {
        const row = document.createElement("div");
        row.className = "order-item" + (state.display.current_image === "System Specs" ? " current" : "");
        row.innerHTML = `
          <div class="index">S</div>
          <div class="meta">
            <div>System Specs</div>
            <div class="muted">${state.display.current_image === "System Specs" ? "Currently shown" : "Enabled"}</div>
          </div>
          <div class="mini"><span class="muted">Specs</span></div>
        `;
        el.order.appendChild(row);
      }

      if (state.setup.specs_enabled && state.activeImages.length) {
        const row = document.createElement("div");
        row.className = "order-item";
        row.innerHTML = `
          <div class="index">S</div>
          <div class="meta">
            <div>System Specs Overlay</div>
            <div class="muted">Overlay active</div>
          </div>
          <div class="mini"><span class="muted">Specs</span></div>
        `;
        el.order.appendChild(row);
      }

      if (!state.activeImages.length && !state.setup.specs_enabled) {
        el.order.innerHTML = `<p class="muted">No active images.</p>`;
        return;
      }
      state.activeImages.forEach((name, index) => {
        const image = imageMap.get(name);
        const label = image ? image.label : name;
        const safeLabel = escapeHtml(label);
        const row = document.createElement("div");
        const isCurrent = state.display.current_image === name || state.display.current_image === label;
        row.className = "order-item" + (isCurrent ? " current" : "");
        row.innerHTML = `
          <div class="index">${index + 1}</div>
          <div class="meta">
            <div>${safeLabel}</div>
            <div class="muted">${isCurrent ? "Currently shown" : (image && image.animated ? `Animated • ${orderFrameLabel(image)}` : "")}</div>
          </div>
          <div class="mini">
            <button class="ghost" type="button" title="Move up" aria-label="Move ${safeLabel} up">↑</button>
            <button class="ghost" type="button" title="Move down" aria-label="Move ${safeLabel} down">↓</button>
            <button class="danger" type="button" title="Remove" aria-label="Remove ${safeLabel}">Remove</button>
          </div>
        `;
        const [up, down, remove] = row.querySelectorAll("button");
        up.addEventListener("click", () => move(index, -1));
        down.addEventListener("click", () => move(index, 1));
        remove.addEventListener("click", () => toggle(name));
        el.order.appendChild(row);
      });
    }

    el.upload.addEventListener("change", (event) => uploadFile(event).catch((err) => toast(err.message)));
    el.apply.addEventListener("click", () => apply().catch((err) => toast(err.message)));
    el.disable.addEventListener("click", () => disablePanels().catch((err) => toast(err.message)));
    el.refresh.addEventListener("click", () => {
      clearDirty();
      load({ force: true }).catch((err) => toast(err.message));
    });
    el.switchTime.addEventListener("change", markDirty);
    el.specsEnabled.addEventListener("change", markDirty);
    el.specsMode.addEventListener("change", markDirty);
    el.memesEnabled.addEventListener("change", markDirty);
    el.gifsEnabled.addEventListener("change", markDirty);
    el.tabButtons.forEach((button) => {
      button.addEventListener("click", () => setActiveTab(button.dataset.tabTarget));
    });

    setActiveTab("screen-control");
    load({ force: true }).catch((err) => toast(err.message));
    setInterval(() => load().catch(() => {}), 5000);
  </script>
</body>
</html>
"##;
