window.__previewEnabled = false;
window.__dashboardPoll = null;
window.__cameraSourcesCache = null;
window.__runtimeMode = "manual";
window.__peoplePollCounter = 0;
window.__audioPromptLibrary = [];

const RESOLUTION_PRESETS = [
  [1920, 1080, 30],
  [1920, 1080, 15],
  [1280, 720, 30],
  [1280, 720, 15],
  [960, 540, 30],
  [960, 540, 15],
  [848, 480, 60],
  [848, 480, 30],
  [640, 480, 60],
  [640, 480, 30],
  [640, 480, 15],
  [424, 240, 60],
  [424, 240, 30],
];

async function apiGet(path) {
  const response = await fetch(path);
  const raw = await response.text();
  let data = null;
  try {
    data = raw ? JSON.parse(raw) : null;
  } catch (_e) {
    if (!response.ok) {
      throw new Error(raw || `HTTP ${response.status}`);
    }
    return raw;
  }
  if (!response.ok) {
    throw new Error((data && (data.error || data.result)) || raw || `HTTP ${response.status}`);
  }
  return data;
}

async function apiPostFormData(path, formData) {
  const response = await fetch(path, { method: "POST", body: formData });
  const raw = await response.text();
  let data = null;
  try {
    data = raw ? JSON.parse(raw) : null;
  } catch (_e) {
    if (!response.ok) {
      throw new Error(raw || `HTTP ${response.status}`);
    }
    return raw;
  }
  if (!response.ok) {
    throw new Error((data && (data.error || data.result)) || raw || `HTTP ${response.status}`);
  }
  return data;
}

async function apiPostJson(path, payload) {
  const response = await fetch(path, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload || {}),
  });
  const raw = await response.text();
  let data = null;
  try {
    data = raw ? JSON.parse(raw) : null;
  } catch (_e) {
    if (!response.ok) {
      throw new Error(raw || `HTTP ${response.status}`);
    }
    return raw;
  }
  if (!response.ok) {
    throw new Error((data && (data.error || data.result)) || raw || `HTTP ${response.status}`);
  }
  return data;
}

function el(tag, cls, html) {
  const node = document.createElement(tag);
  if (cls) node.className = cls;
  if (html !== undefined) node.innerHTML = html;
  return node;
}

function escapeHtml(text) {
  return String(text || "")
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

function formatSize(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 fmtNum(v, digits = 2) {
  const n = Number(v);
  if (Number.isNaN(n)) return "-";
  return n.toFixed(digits);
}

function formatTime(ts) {
  const n = Number(ts);
  if (!Number.isFinite(n) || n <= 0) return "-";
  try {
    return new Date(n * 1000).toLocaleString();
  } catch (_e) {
    return "-";
  }
}

function cameraStatusLines(camera) {
  return [
    `Camera OK`,
    `Source: ${camera.source || "-"}`,
    `Backend: ${camera.backend || "-"}`,
    `Active: ${camera.profile || "-"}`,
    `Requested: ${camera.requested_profile || "-"}`,
    `Preferred RS Serial: ${camera.preferred_realsense_serial || "-"}`,
    `Active RS Serial: ${camera.realsense_serial || "-"}`,
  ].join("\n");
}

function setPreviewEnabled(enabled) {
  window.__previewEnabled = !!enabled;
  const btn = document.getElementById("toggle_preview");
  const img = document.getElementById("live_preview");
  const placeholder = document.getElementById("preview_placeholder");
  const hint = document.getElementById("preview_hint");
  if (btn) btn.textContent = window.__previewEnabled ? "Hide Live Camera" : "Show Live Camera";
  if (!img) return;
  if (window.__previewEnabled) {
    img.style.display = "block";
    img.src = `/preview.mjpg?t=${Date.now()}`;
    if (placeholder) placeholder.style.display = "none";
    if (hint) hint.textContent = "Live preview is running through the shared direct camera service.";
  } else {
    img.removeAttribute("src");
    img.src = "";
    img.style.display = "none";
    if (placeholder) placeholder.style.display = "grid";
    if (hint) hint.textContent = "Live preview is off. Turn it on only when needed.";
  }
}

function restartPreview() {
  if (!window.__previewEnabled) return;
  const img = document.getElementById("live_preview");
  if (!img) return;
  img.src = `/preview.mjpg?t=${Date.now()}`;
}

function setResolutionInputs(width, height, fps) {
  const widthInput = document.getElementById("width_input");
  const heightInput = document.getElementById("height_input");
  const fpsInput = document.getElementById("fps_input");
  const presetSelect = document.getElementById("resolution_select");
  if (widthInput) widthInput.value = String(width || "");
  if (heightInput) heightInput.value = String(height || "");
  if (fpsInput) fpsInput.value = String(fps || "");
  if (presetSelect) {
    const preset = `${width}x${height}@${fps}`;
    const hasPreset = Array.from(presetSelect.options).some((opt) => opt.value === preset);
    presetSelect.value = hasPreset ? preset : "";
  }
}

function renderResolutionOptions() {
  return [
    '<option value="">Preset</option>',
    ...RESOLUTION_PRESETS.map(([w, h, fps]) => `<option value="${w}x${h}@${fps}">${w}x${h} @ ${fps}</option>`),
  ].join("");
}

function renderSourceOptions(cameraSources) {
  const options = (cameraSources && cameraSources.options) || [];
  if (!options.length) {
    return '<option value="realsense">Preferred RealSense (default)</option>';
  }
  return options
    .map((item) => `<option value="${escapeHtml(item.value)}">${escapeHtml(item.label || item.value)}</option>`)
    .join("");
}

function renderCameraInventory(cameraSources, camera) {
  const lines = [];
  const preferred = String((cameraSources && cameraSources.preferred_realsense_serial) || (camera && camera.preferred_realsense_serial) || "").trim();
  const active = String((camera && camera.realsense_serial) || "").trim();
  const activeSource = String((camera && camera.source) || "").trim();

  if (preferred) {
    lines.push(`Preferred RealSense: ${preferred}`);
  }
  if (active) {
    lines.push(`Active RealSense: ${active}`);
  }

  const rsDevices = (cameraSources && cameraSources.realsense_devices) || [];
  if (rsDevices.length) {
    lines.push("RealSense devices:");
    for (const item of rsDevices) {
      const serial = String(item.serial || "").trim() || "-";
      const name = String(item.name || "Intel RealSense").trim();
      const tags = [];
      if (item.is_preferred) tags.push("default");
      if (item.is_active) tags.push("live");
      lines.push(`- ${name} [${serial}]${tags.length ? ` (${tags.join(", ")})` : ""}`);
    }
  }

  const videoDevices = (cameraSources && cameraSources.video_devices) || [];
  if (videoDevices.length) {
    lines.push("Video nodes:");
    for (const item of videoDevices) {
      const name = String(item.name || "").trim();
      const label = String(item.value || item.label || "").trim();
      const suffix = label === activeSource ? " (live)" : "";
      lines.push(`- ${label}${name ? ` :: ${name}` : ""}${suffix}`);
    }
  }

  return lines.join("\n") || "No camera inventory available.";
}

function personImageUrl(personId, kind) {
  return `/api/person_image?id=${encodeURIComponent(personId)}&kind=${encodeURIComponent(kind)}&t=${Date.now()}`;
}

function renderPeopleCards(items) {
  if (!items.length) {
    return '<div class="status-box"><i>No people enrolled yet.</i></div>';
  }
  return items.map((person) => {
    const personId = String(person.person_id || "");
    const title = escapeHtml(person.display_name || person.short_id || personId || "guest");
    const meta = [
      `ID: ${escapeHtml(person.short_id || personId || "-")}`,
      `Created: ${escapeHtml(person.created_date || "-")}`,
      `Seen: ${person.times_seen ?? 0}`,
      `Samples: ${person.sample_count ?? 0}`,
      `Last Seen: ${escapeHtml(formatTime(person.last_seen_at || 0))}`,
    ].join("\n");
    return `
      <article class="person-card">
        <div class="person-card-head">
          <img src="${personImageUrl(personId, "face")}" alt="${title} face">
          <img src="${personImageUrl(personId, "scene")}" alt="${title} scene">
        </div>
        <div class="person-card-body">
          <div class="person-title">${title}</div>
          <div class="person-meta">${meta}</div>
          <div class="person-actions">
            <a class="btn ghost" href="/api/download_person?id=${encodeURIComponent(personId)}">Download</a>
            <button class="btn ghost person-add-photo" data-id="${escapeHtml(personId)}">Add Photo</button>
            <button class="btn ghost danger person-delete" data-id="${escapeHtml(personId)}">Delete</button>
          </div>
        </div>
      </article>
    `;
  }).join("");
}

function selectedCameraSerial(source, cameraSources, camera) {
  const src = String(source || "").trim();
  if (src.startsWith("realsense:")) {
    return src.split(":", 2)[1] || "";
  }
  if (src === "realsense") {
    return String((camera && camera.realsense_serial) || (cameraSources && cameraSources.preferred_realsense_serial) || "").trim();
  }
  const devices = (cameraSources && cameraSources.realsense_devices) || [];
  const exact = devices.find((item) => item.value === src);
  return String((exact && exact.serial) || "").trim();
}

function formatPromptKeyLabel(key) {
  return String(key || "")
    .split("_")
    .filter(Boolean)
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
    .join(" ");
}

function promptFilenameForKey(key) {
  const item = (window.__audioPromptLibrary || []).find((entry) => entry.key === key);
  return item ? String(item.filename || `${key}.wav`) : `${key}.wav`;
}

function promptTextForKey(key) {
  const item = (window.__audioPromptLibrary || []).find((entry) => entry.key === key);
  return item ? String(item.text || "") : "";
}

function populateAudioPromptRecorder(prompts) {
  window.__audioPromptLibrary = Array.isArray(prompts) ? prompts.slice() : [];
  const keySelect = document.getElementById("audio_prompt_record_key");
  const filenameInput = document.getElementById("audio_prompt_record_filename");
  const textArea = document.getElementById("audio_prompt_record_text");
  if (!keySelect || !filenameInput || !textArea) return;

  const previousKey = keySelect.value;
  keySelect.innerHTML = (window.__audioPromptLibrary || [])
    .map((item) => `<option value="${escapeHtml(item.key)}">${escapeHtml(formatPromptKeyLabel(item.key))}</option>`)
    .join("");
  if (previousKey && (window.__audioPromptLibrary || []).some((item) => item.key === previousKey)) {
    keySelect.value = previousKey;
  }
  const activeKey = keySelect.value || ((window.__audioPromptLibrary[0] && window.__audioPromptLibrary[0].key) || "");
  if (activeKey) {
    keySelect.value = activeKey;
    if (!filenameInput.value || filenameInput.dataset.autofill !== "0") {
      filenameInput.value = promptFilenameForKey(activeKey);
      filenameInput.dataset.autofill = "1";
    }
    if (!textArea.value || textArea.dataset.autofill !== "0") {
      textArea.value = promptTextForKey(activeKey);
      textArea.dataset.autofill = "1";
    }
  }
}

async function refreshAudioPromptRecordStatus() {
  const status = document.getElementById("audio_prompt_record_status_box");
  if (!status) return;
  try {
    const res = await apiGet("/api/audio_prompt_record_status");
    const s = res.status || {};
    if (s.running) {
      status.textContent = [
        `Recording prompt: ${s.key || "-"}`,
        `Filename: ${s.filename || "-"}`,
        `Started: ${formatTime(s.started_at || 0)}`,
        "Status: generating Gemini audio and recording speaker output...",
      ].join("\n");
      return;
    }
    if (s.finished_at) {
      if (s.ok) {
        const result = s.result || {};
        status.textContent = [
          `Recorded prompt: ${result.key || s.key || "-"}`,
          `Saved speaker file: ${result.filename || s.filename || "-"}`,
          `Saved raw file: ${result.raw_filename || "-"}`,
          `Finished: ${formatTime(s.finished_at || 0)}`,
        ].join("\n");
      } else {
        status.textContent = [
          `Prompt recording failed for ${s.key || "-"}`,
          `Finished: ${formatTime(s.finished_at || 0)}`,
          `${s.error || "Unknown error"}`,
        ].join("\n");
      }
      return;
    }
    status.textContent = "Prompt recorder idle.";
  } catch (e) {
    status.textContent = `Prompt recorder unavailable\n${e.message}`;
  }
}

function renderAudioPromptCards(items) {
  if (!items.length) {
    return '<div class="status-box"><i>No audio prompt keys available.</i></div>';
  }
  return items.map((item) => {
    const exists = !!item.exists;
    const key = String(item.key || "");
    const filename = String(item.filename || "");
    return `
      <article class="audio-prompt-card ${exists ? "ready" : "missing"}">
        <div class="audio-prompt-head">
          <div>
            <div class="audio-prompt-title">${escapeHtml(formatPromptKeyLabel(key))}</div>
            <div class="audio-prompt-key">${escapeHtml(key)}</div>
          </div>
          <div class="replay-tags">
            <span class="replay-tag ${exists ? "active" : "warn"}">${exists ? "uploaded" : "missing"}</span>
          </div>
        </div>
        <div class="audio-prompt-text">${escapeHtml(item.text || "")}</div>
        <div class="audio-prompt-meta">
          File: ${escapeHtml(filename || "-")}<br>
          Size: ${formatSize(item.size || 0)}<br>
          Raw: ${item.raw_exists ? escapeHtml(item.raw_filename || "-") : "missing"}<br>
          Updated: ${formatTime(item.mtime || 0)}
        </div>
        <div class="audio-prompt-actions">
          <button class="btn ghost audio-prompt-upload" data-key="${escapeHtml(key)}" data-filename="${escapeHtml(filename)}">${exists ? "Replace" : "Upload"}</button>
          <button class="btn ghost audio-prompt-record" data-key="${escapeHtml(key)}">Record</button>
          ${exists ? `<a class="btn ghost" href="/api/download_audio_prompt?key=${encodeURIComponent(key)}">Download</a>` : ""}
          <button class="btn ghost danger audio-prompt-delete" data-key="${escapeHtml(key)}" ${exists ? "" : "disabled"}>Delete</button>
        </div>
      </article>
    `;
  }).join("");
}

async function refreshAudioPromptPanel() {
  const list = document.getElementById("audio_prompt_list");
  const status = document.getElementById("audio_prompt_status_box");
  const modeToggle = document.getElementById("audio_prompt_mode_toggle");
  const toggle = document.getElementById("audio_prompt_fallback_toggle");
  const fileInput = document.getElementById("audio_prompt_upload_file");
  if (!list || !status || !toggle || !fileInput || !modeToggle) return;
  try {
    const res = await apiGet("/api/audio_prompts");
    const prompts = res.prompts || [];
    const promptMode = String(res.mode || "audio").trim().toLowerCase() === "gemini" ? "gemini" : "audio";
    const fallbackToGemini = !!res.fallback_to_gemini;
    const available = prompts.filter((item) => item.exists).length;
    modeToggle.dataset.mode = promptMode;
    modeToggle.textContent = promptMode === "gemini" ? "Situation Speech: GEMINI" : "Situation Speech: AUDIO";
    toggle.dataset.enabled = fallbackToGemini ? "1" : "0";
    toggle.textContent = fallbackToGemini ? "Gemini Fallback: ON" : "Gemini Fallback: OFF";
    populateAudioPromptRecorder(prompts);
    status.textContent = [
      `Prompt folder: ${res.dir || "AI_Photographer/Data/Audio"}`,
      `Available clips: ${available} / ${prompts.length}`,
      `Fixed AI situation speech: ${promptMode.toUpperCase()}`,
      `Missing-clip Gemini fallback: ${fallbackToGemini ? "ON" : "OFF"}`,
      promptMode === "audio"
        ? "Recorded audio is the default for AI situations such as greeting on detection, photo request, countdown, refusal, retake, and thank-you. After those fixed prompts finish, normal Gemini conversation continues."
        : "Gemini is currently speaking the fixed AI situation prompts directly. General Gemini conversation remains available before and after the photo flow."
    ].join("\n");
    list.innerHTML = renderAudioPromptCards(prompts);

    list.querySelectorAll(".audio-prompt-upload").forEach((btn) => {
      btn.addEventListener("click", () => {
        fileInput.dataset.key = btn.dataset.key || "";
        fileInput.dataset.filename = btn.dataset.filename || "";
        fileInput.click();
      });
    });
    list.querySelectorAll(".audio-prompt-record").forEach((btn) => {
      btn.addEventListener("click", () => {
        const key = btn.dataset.key || "";
        const keySelect = document.getElementById("audio_prompt_record_key");
        const filenameInput = document.getElementById("audio_prompt_record_filename");
        const textArea = document.getElementById("audio_prompt_record_text");
        if (keySelect) keySelect.value = key;
        if (filenameInput) {
          filenameInput.value = promptFilenameForKey(key);
          filenameInput.dataset.autofill = "1";
        }
        if (textArea) {
          textArea.value = promptTextForKey(key);
          textArea.dataset.autofill = "1";
          textArea.focus();
        }
      });
    });
    list.querySelectorAll(".audio-prompt-delete").forEach((btn) => {
      btn.addEventListener("click", async () => {
        const key = btn.dataset.key || "";
        if (!key) return;
        if (!confirm(`Delete audio prompt clip for ${key}?`)) return;
        await apiGet(`/api/delete_audio_prompt?key=${encodeURIComponent(key)}`);
        await refreshAudioPromptPanel();
        await refreshAudioPromptRecordStatus();
      });
    });
  } catch (e) {
    status.textContent = `Audio prompt library unavailable\n${e.message}`;
    list.innerHTML = "";
    toggle.textContent = "Gemini Fallback";
    delete toggle.dataset.enabled;
  }
}

function bindTopActions() {
  const topStatus = document.getElementById("top_status");
  const topCapture = document.getElementById("top_capture");
  const topTest = document.getElementById("top_test");
  const topFix = document.getElementById("top_fix");

  if (topStatus) {
    topStatus.onclick = () => window.open("/api/status", "_blank", "noopener");
  }
  if (topCapture) {
    topCapture.onclick = async () => {
      try {
        const res = await apiGet("/api/capture");
        alert(`Captured:\n${res.result || res.path || "OK"}`);
        await renderPhotos();
      } catch (e) {
        alert(`Capture failed: ${e.message}`);
      }
    };
  }
  if (topTest) {
    topTest.onclick = async () => {
      try {
        const res = await apiGet("/api/test");
        alert(JSON.stringify(res, null, 2));
      } catch (e) {
        alert(`Camera test failed: ${e.message}`);
      }
    };
  }
  if (topFix) {
    topFix.onclick = async () => {
      if (!confirm("Run the camera fix flow?")) return;
      try {
        const res = await apiGet("/api/fix");
        alert(JSON.stringify(res, null, 2));
      } catch (e) {
        alert(`Fix failed: ${e.message}`);
      }
    };
  }
}

async function refreshAutonomousPanel() {
  const panel = document.getElementById("auto_state_panel");
  if (!panel) return;
  try {
    const r = await apiGet("/api/autonomous_state");
    if (!r.ok) {
      panel.innerHTML = "<i>Autonomous state unavailable</i>";
      return;
    }
    const s = r.state || {};
    panel.innerHTML = `
      <div class="info-grid">
        <div class="stat-line"><span>State</span><b>${escapeHtml(s.state || "IDLE")}</b></div>
        <div class="stat-line"><span>Session</span><b>${s.session_id ?? 0}</b></div>
        <div class="stat-line"><span>Detector</span><b>${escapeHtml((s.detector_backend || "normal").toUpperCase())}</b></div>
        <div class="stat-line"><span>YOLO Runtime</span><b>${escapeHtml((s.yolo_runtime || "-").toUpperCase())}</b></div>
        <div class="stat-line"><span>AI Blocked</span><b>${s.ai_blocked ? "Yes" : "No"}</b></div>
        <div class="stat-line"><span>Block Reason</span><b>${escapeHtml(s.ai_block_reason || "-")}</b></div>
        <div class="stat-line"><span>People</span><b>${s.person_count ?? 0}</b></div>
        <div class="stat-line"><span>Faces</span><b>${s.face_count ?? 0}</b></div>
        <div class="stat-line"><span>Group</span><b>${s.group_detected ? `Yes (${s.group_size ?? 0})` : "No"}</b></div>
        <div class="stat-line"><span>Intent</span><b>${s.intent_detected ? "Yes" : "No"}</b></div>
        <div class="stat-line"><span>Depth</span><b>${s.depth_m == null ? "-" : fmtNum(s.depth_m, 2)}</b></div>
        <div class="stat-line"><span>Approach</span><b>${fmtNum(s.approach_speed_mps || 0, 2)} m/s</b></div>
        <div class="stat-line"><span>Guest</span><b>${escapeHtml(s.recognized_person_label || "-")}</b></div>
        <div class="stat-line"><span>Known Guest</span><b>${s.recognized_person_known ? "Yes" : "No"}</b></div>
        <div class="stat-line"><span>Match</span><b>${s.recognized_person_match_score ? fmtNum(s.recognized_person_match_score, 2) : "-"}</b></div>
      </div>
    `;
  } catch (_e) {
    panel.innerHTML = "<i>Autonomous state unavailable</i>";
  }
}

async function refreshAiReadinessPanel() {
  const panel = document.getElementById("ai_readiness_panel");
  if (!panel) return;
  try {
    const data = await apiGet("/api/ai_readiness");
    panel.innerHTML = `
      <div class="info-grid">
        <div class="stat-line"><span>Ready</span><b>${data.ok ? "Yes" : "No"}</b></div>
        <div class="stat-line"><span>Backend</span><b>${escapeHtml((data.backend || "normal").toUpperCase())}</b></div>
        <div class="stat-line"><span>Runtime</span><b>${escapeHtml((data.yolo_runtime || "-").toUpperCase())}</b></div>
        <div class="stat-line"><span>YOLO Loaded</span><b>${data.yolo_loaded ? "Yes" : "No"}</b></div>
        <div class="stat-line"><span>Person Model</span><b>${data.person_model_ok ? "Yes" : "No"}</b></div>
        <div class="stat-line"><span>Face Model</span><b>${data.face_model_ok ? "Yes" : "No"}</b></div>
        <div class="stat-line"><span>Strict Required</span><b>${data.strict_required ? "Yes" : "No"}</b></div>
        <div class="stat-line"><span>Block Reason</span><b>${escapeHtml(data.block_reason || "-")}</b></div>
      </div>
    `;
  } catch (_e) {
    panel.innerHTML = "<i>AI readiness unavailable</i>";
  }
}

async function refreshRuntimeHealthPanel() {
  const panel = document.getElementById("runtime_health_panel");
  if (!panel) return;
  try {
    const r = await apiGet("/api/runtime_health");
    if (!r.ok) {
      panel.innerHTML = "<i>Runtime health unavailable</i>";
      return;
    }
    const h = r.health || {};
    panel.innerHTML = `
      <div class="info-grid">
        <div class="stat-line"><span>WS</span><b>${h.ws_connected ? "Connected" : "Down"}</b></div>
        <div class="stat-line"><span>WS State</span><b>${escapeHtml(h.ws_state || "-")}</b></div>
        <div class="stat-line"><span>Mic Enabled</span><b>${h.mic_enabled ? "Yes" : "No"}</b></div>
        <div class="stat-line"><span>Mic</span><b>${escapeHtml(h.mic_state || "-")}</b></div>
        <div class="stat-line"><span>Speaker</span><b>${escapeHtml(h.speaker_state || "-")}</b></div>
        <div class="stat-line"><span>Gate</span><b>${h.audio_gate_open ? "Open" : "Closed"}</b></div>
        <div class="stat-line"><span>Mic Restarts</span><b>${h.mic_restarts ?? 0}</b></div>
        <div class="stat-line"><span>Speaker Restarts</span><b>${h.speaker_restarts ?? 0}</b></div>
        <div class="stat-line"><span>WS Restarts</span><b>${h.ws_restarts ?? 0}</b></div>
        <div class="stat-line"><span>Last WS Error</span><b>${escapeHtml(h.ws_last_error || "-")}</b></div>
      </div>
    `;
  } catch (_e) {
    panel.innerHTML = "<i>Runtime health unavailable</i>";
  }
}

async function renderPeople() {
  const list = document.getElementById("people_list");
  const status = document.getElementById("people_status_box");
  if (!list || !status) return;
  try {
    const res = await apiGet("/api/people");
    const people = res.people || [];
    status.textContent = people.length
      ? `Registered guests: ${people.length}\nReturning guests are recognized from saved face samples in photos/people/.`
      : "No people enrolled yet. AI mode will create a guest profile the first time a single face is confirmed.";
    list.innerHTML = renderPeopleCards(people);
    list.querySelectorAll(".person-delete").forEach((btn) => {
      btn.addEventListener("click", async () => {
        const personId = btn.dataset.id || "";
        if (!personId) return;
        if (!confirm(`Delete person ${personId}?`)) return;
        await apiGet(`/api/delete_person?id=${encodeURIComponent(personId)}`);
        await renderPeople();
      });
    });
    list.querySelectorAll(".person-add-photo").forEach((btn) => {
      btn.addEventListener("click", () => {
        const personId = btn.dataset.id || "";
        const input = document.getElementById("people_upload_file");
        if (!input) return;
        input.dataset.personId = personId;
        input.click();
      });
    });
  } catch (e) {
    status.textContent = `People registry unavailable\n${e.message}`;
    list.innerHTML = "";
  }
}

async function refreshCameraPanel() {
  const statusBox = document.getElementById("camera_status_box");
  const infoPanel = document.getElementById("camera_info_panel");
  const chipRow = document.getElementById("camera_chip_row");
  const selector = document.getElementById("camera_source_select");
  const sourceMeta = document.getElementById("camera_source_meta");
  const inventoryBox = document.getElementById("camera_inventory_box");
  if (!statusBox || !infoPanel || !chipRow) return;

  try {
    const [health, cameraSources] = await Promise.all([
      apiGet("/api/camera_health"),
      selector && !window.__cameraSourcesCache ? apiGet("/api/camera_sources") : Promise.resolve(window.__cameraSourcesCache || null),
    ]);

    if (cameraSources && cameraSources.ok) {
      window.__cameraSourcesCache = cameraSources;
      if (selector) {
        const currentValue = selector.dataset.initialized === "1"
          ? (selector.value || cameraSources.selected_source || "realsense")
          : (cameraSources.selected_source || "realsense");
        selector.innerHTML = renderSourceOptions(cameraSources);
        selector.value = currentValue;
        if (!selector.value) selector.value = cameraSources.selected_source || "realsense";
        selector.dataset.initialized = "1";
      }
      if (sourceMeta) {
        const rsCount = (cameraSources.realsense_devices || []).length;
        const vidCount = (cameraSources.video_devices || []).length;
        const activeLabel = (cameraSources.active_device && cameraSources.active_device.label) || cameraSources.active_source || "-";
        sourceMeta.textContent = `Preferred RS: ${cameraSources.preferred_realsense_serial || "-"} | Active: ${activeLabel} | RealSense devices: ${rsCount} | Video nodes: ${vidCount}`;
      }
    }

    const camera = (health && health.camera) || {};
    statusBox.textContent = camera.ok ? cameraStatusLines(camera) : `Camera not ready\nSource: ${camera.source || "-"}\nRequested: ${camera.requested_profile || "-"}\nError: ${camera.last_error || "-"}`;
    chipRow.innerHTML = `
      <div class="chip"><span>Requested</span><strong>${escapeHtml(camera.requested_source || camera.source || "-")}</strong></div>
      <div class="chip"><span>Backend</span><strong>${escapeHtml(camera.backend || "-")}</strong></div>
      <div class="chip"><span>Active Profile</span><strong>${escapeHtml(camera.profile || "-")}</strong></div>
      <div class="chip"><span>Frame Time</span><strong>${escapeHtml(formatTime(camera.frame_time))}</strong></div>
    `;
    infoPanel.textContent = JSON.stringify(health, null, 2);
    if (inventoryBox) {
      inventoryBox.textContent = renderCameraInventory(cameraSources, camera);
    }

    const widthInput = document.getElementById("width_input");
    if (widthInput && widthInput.dataset.initialized !== "1") {
      setResolutionInputs(camera.requested_width, camera.requested_height, camera.requested_fps);
      widthInput.dataset.initialized = "1";
    }
  } catch (e) {
    statusBox.textContent = `Camera service unavailable\n${e.message}`;
    infoPanel.textContent = JSON.stringify({ ok: false, error: e.message }, null, 2);
    chipRow.innerHTML = "";
    if (inventoryBox) inventoryBox.textContent = "Camera inventory unavailable.";
  }
}

function renderReplayList(items, opts = {}) {
  const manualMode = !!opts.manualMode;
  const testRunning = !!opts.testRunning;
  if (!items.length) {
    return '<div class="status-box"><i>No replay files found.</i></div>';
  }
  return items.map((item) => {
    const tags = [];
    if (item.active) tags.push('<span class="replay-tag active">active</span>');
    if (item.greeting) tags.push('<span class="replay-tag">greeting</span>');
    if (!item.trigger_ok) tags.push('<span class="replay-tag warn">triggerless</span>');
    return `
      <article class="replay-row ${item.active ? "active" : ""}">
        <div class="replay-main">
          <div class="replay-title">${escapeHtml(item.name)}</div>
          <div class="replay-meta">${formatSize(item.size || 0)} · ${formatTime(item.mtime || 0)}</div>
        </div>
        <div class="replay-tags">${tags.join("")}</div>
        <div class="replay-row-actions">
          <button class="btn ghost replay-test" data-name="${escapeHtml(item.name)}" ${manualMode && !testRunning ? "" : "disabled"}>Test</button>
          <button class="btn ghost replay-use" data-name="${escapeHtml(item.name)}">Use</button>
          <button class="btn ghost replay-rename" data-name="${escapeHtml(item.name)}">Rename</button>
          <a class="btn ghost" href="/api/download_replay?name=${encodeURIComponent(item.name)}">Download</a>
          <button class="btn ghost danger replay-delete" data-name="${escapeHtml(item.name)}" ${(item.active || item.greeting) ? "disabled" : ""}>Delete</button>
        </div>
      </article>
    `;
  }).join("");
}

function renderReplayRecordStatus(status) {
  if (!status || !status.started_at) {
    return "Recorder idle.";
  }
  if (status.running) {
    return `Recording ${status.name || "-"} for ${status.seconds || 0}s...\nOutput: ${status.output || "-"}`;
  }
  if (status.ok) {
    return `Recording saved\n${status.output || "-"}\nFinished: ${formatTime(status.finished_at || 0)}`;
  }
  return `Recorder error\n${status.error || "-"}${(status.output ? `\nOutput: ${status.output}` : "")}`;
}

function renderReplayTestStatus(status) {
  if (!status || !status.started_at) {
    return "Replay test idle.";
  }
  if (status.running) {
    return `Testing replay\n${status.name || "-"}`;
  }
  if (status.ok) {
    return `Replay test complete\n${status.result || status.name || "-"}`;
  }
  return `Replay test error\n${status.error || "-"}`;
}

async function refreshReplayPanel() {
  const selector = document.getElementById("replay_select");
  const activeNote = document.getElementById("replay_active_note");
  const list = document.getElementById("replay_manager_list");
  const status = document.getElementById("replay_record_status");
  const testStatus = document.getElementById("replay_test_status");
  const greetingSelect = document.getElementById("opt_greeting_replay_file");
  try {
    const [replayResp, activeResp, recordResp, testResp] = await Promise.all([
      apiGet("/api/replays"),
      apiGet("/api/get_replay"),
      apiGet("/api/replay_record_status").catch(() => ({ ok: false, status: {} })),
      apiGet("/api/replay_test_status").catch(() => ({ ok: false, status: {} })),
    ]);
    const items = replayResp.items || [];
    const activeReplay = activeResp.replay || "";
    const replayTest = testResp.status || {};
    const manualMode = window.__runtimeMode === "manual";

    if (selector) {
      selector.innerHTML = items.map((item) => `<option value="${escapeHtml(item.name)}">${escapeHtml(item.name)}</option>`).join("");
      selector.value = activeReplay || (items[0] && items[0].name) || "";
    }
    if (activeNote) {
      activeNote.textContent = activeReplay
        ? `Active replay: ${activeReplay}. Manual R2+X and AI photo capture use this replay when AI capture replay is enabled. Replay recording and replay test are manual-mode only.`
        : "No active replay selected.";
    }
    if (list) {
      list.innerHTML = renderReplayList(items, { manualMode, testRunning: !!replayTest.running });
      list.querySelectorAll(".replay-test").forEach((btn) => {
        btn.addEventListener("click", async () => {
          const name = btn.dataset.name || "";
          await apiGet(`/api/test_replay?name=${encodeURIComponent(name)}`);
          await refreshReplayPanel();
        });
      });
      list.querySelectorAll(".replay-use").forEach((btn) => {
        btn.addEventListener("click", async () => {
          await apiGet(`/api/set_replay?name=${encodeURIComponent(btn.dataset.name || "")}`);
          await refreshReplayPanel();
        });
      });
      list.querySelectorAll(".replay-rename").forEach((btn) => {
        btn.addEventListener("click", async () => {
          const oldName = btn.dataset.name || "";
          const currentLeaf = oldName.split("/").pop() || oldName;
          const nextName = window.prompt("Rename replay", currentLeaf);
          if (!nextName || !nextName.trim()) return;
          await apiGet(
            `/api/rename_replay?old=${encodeURIComponent(oldName)}&new=${encodeURIComponent(nextName.trim())}`
          );
          await refreshReplayPanel();
        });
      });
      list.querySelectorAll(".replay-delete").forEach((btn) => {
        btn.addEventListener("click", async () => {
          const name = btn.dataset.name || "";
          if (!confirm(`Delete replay ${name}?`)) return;
          await apiGet(`/api/delete_replay?name=${encodeURIComponent(name)}`);
          await refreshReplayPanel();
        });
      });
    }
    if (status) {
      status.textContent = renderReplayRecordStatus(recordResp.status || {});
    }
    if (testStatus) {
      testStatus.textContent = renderReplayTestStatus(replayTest);
    }
    if (greetingSelect) {
      const current = greetingSelect.dataset.current || greetingSelect.value;
      greetingSelect.innerHTML = items.map((item) => `<option value="${escapeHtml(item.name)}">${escapeHtml(item.name)}</option>`).join("");
      if (current) greetingSelect.value = current;
      if (!greetingSelect.value && items.length) greetingSelect.value = items[0].name;
      delete greetingSelect.dataset.current;
    }
  } catch (e) {
    if (activeNote) activeNote.textContent = `Replay inventory unavailable: ${e.message}`;
    if (list) list.innerHTML = "";
    if (status) status.textContent = `Recorder unavailable\n${e.message}`;
    if (testStatus) testStatus.textContent = `Replay test unavailable\n${e.message}`;
  }
}

async function renderControls() {
  const ctrl = document.getElementById("controls");
  const [modeResp, micResp, cameraSources] = await Promise.all([
    apiGet("/api/mode"),
    apiGet("/api/mic"),
    apiGet("/api/camera_sources").catch(() => ({ ok: false, options: [] })),
  ]);

  window.__cameraSourcesCache = cameraSources && cameraSources.ok ? cameraSources : null;

  const mode = modeResp.mode || "manual";
  window.__runtimeMode = mode;
  const modePolicy = modeResp.policy || {};
  const micEnabled = !!(micResp.options && micResp.options.mic_enabled);

  let backend = "normal";
  let strictLocked = false;
  let aiOptions = {
    hard_target_lock_enabled: true,
    retake_prompt_enabled: true,
    autonomous_greeting_replay_enabled: true,
    autonomous_greeting_replay_file: "right_hand_up.jsonl",
    autonomous_capture_replay_enabled: true,
    face_recognition_enabled: true,
    face_recognition_threshold: 0.88,
    active_replay: "photo_G3.jsonl",
  };
  let aiReadinessMarkup = "";
  let aiOptionsMarkup = "";
  if (mode === "ai") {
    try {
      const bj = await apiGet("/api/detector_backend");
      backend = bj.backend || "normal";
      strictLocked = !!bj.locked;
    } catch (_e) {}

    try {
      const oj = await apiGet("/api/ai_options");
      if (oj && oj.ok && oj.options) aiOptions = oj.options;
    } catch (_e) {}

    aiReadinessMarkup = `
      <section class="panel strong">
        <div class="panel-header">
          <div>
            <div class="panel-eyebrow">AI Vision</div>
            <h3>Detection Backend</h3>
          </div>
        </div>
        <div class="selector-row">
          <select id="det_backend_select">
            <option value="normal">Normal</option>
            <option value="yolo">YOLO</option>
          </select>
          <button id="det_backend_set" class="btn ghost">Apply Backend</button>
        </div>
        ${strictLocked ? '<div class="meta" style="margin-top:10px;color:#8b3120;"><b>Strict AI profile active:</b> backend is locked to YOLO in AI mode.</div>' : ""}
        <div id="ai_readiness_panel" class="info-box"><i>Loading AI readiness...</i></div>
      </section>
    `;

    aiOptionsMarkup = `
      <section class="panel">
        <div class="panel-header">
          <div>
            <div class="panel-eyebrow">AI Session</div>
            <h3>Session Options</h3>
          </div>
        </div>
        <div class="inline-actions">
          <label><input type="checkbox" id="opt_hard_lock" ${aiOptions.hard_target_lock_enabled ? "checked" : ""}> Hard Target Lock</label>
          <label><input type="checkbox" id="opt_retake_prompt" ${aiOptions.retake_prompt_enabled ? "checked" : ""}> Retake Prompt</label>
          <label><input type="checkbox" id="opt_capture_replay" ${aiOptions.autonomous_capture_replay_enabled ? "checked" : ""}> Use Active Replay During AI Photo</label>
          <label><input type="checkbox" id="opt_greeting_replay" ${aiOptions.autonomous_greeting_replay_enabled ? "checked" : ""}> Greeting Replay</label>
          <label><input type="checkbox" id="opt_face_recognition" ${aiOptions.face_recognition_enabled ? "checked" : ""}> Returning Guest Recognition</label>
        </div>
        <div class="selector-row" style="margin-top:12px;">
          <select id="opt_greeting_replay_file"></select>
          <input id="opt_face_threshold" type="number" min="0.50" max="0.995" step="0.01" value="${Number(aiOptions.face_recognition_threshold || 0.88).toFixed(2)}" placeholder="face threshold">
          <button id="opt_ai_save" class="btn ghost">Save AI Options</button>
        </div>
        <div class="status-box">AI welcomes approaching guests, asks if they want a photo, guides them into frame, and captures using the active replay when enabled. Returning-guest recognition stores samples in <code>photos/people/</code>. Active replay: ${escapeHtml(aiOptions.active_replay || "-")}.</div>
      </section>
    `;
  }

  const recorderMarkup = mode === "manual"
    ? `
        <section class="panel">
          <div class="panel-header">
            <div>
              <div class="panel-eyebrow">Recorder</div>
              <h3>Record New Replay</h3>
            </div>
          </div>
          <div class="selector-row">
            <input id="replay_record_name" type="text" placeholder="photo_wave">
            <input id="replay_record_seconds" type="number" min="1" step="1" value="15" placeholder="seconds">
            <button id="replay_record_start" class="btn primary">Start Recording</button>
          </div>
          <div class="meta" style="margin-top:10px;">Recorded files are saved directly in <code>Data/G1</code> and become available in the active replay selector.</div>
          <div id="replay_record_status" class="status-box">Recorder idle.</div>
        </section>
      `
    : `
        <section class="panel">
          <div class="panel-header">
            <div>
              <div class="panel-eyebrow">Recorder</div>
              <h3>Manual Mode Only</h3>
            </div>
          </div>
          <div class="status-box">Replay recording and replay test are disabled outside manual mode. Replay selection, rename, upload, download, and delete remain available.</div>
          <div id="replay_record_status" class="status-box">Recorder idle.</div>
        </section>
      `;

  ctrl.innerHTML = `
    <div class="dashboard-stack">
      <div class="control-grid">
        <section class="panel strong">
          <div class="panel-header">
            <div>
              <div class="panel-eyebrow">Runtime Mode</div>
              <h3>${escapeHtml(mode.toUpperCase())}</h3>
            </div>
          </div>
          <div class="selector-row">
            <select id="mode_select">
              <option value="manual">Manual</option>
              <option value="ai">AI</option>
            </select>
            <button id="mode_set" class="btn primary">Set Mode</button>
          </div>
          <div class="status-box">${escapeHtml(modePolicy.description || "")}</div>
        </section>

        <section class="panel">
          <div class="panel-header">
            <div>
              <div class="panel-eyebrow">Gemini Mic</div>
              <h3>${micEnabled ? "Microphone ON" : "Microphone OFF"}</h3>
            </div>
          </div>
          <div class="action-row">
            <button id="mic_toggle" class="btn ${micEnabled ? "ghost danger" : "primary"}">${micEnabled ? "Turn Mic Off" : "Turn Mic On"}</button>
          </div>
          <div class="status-box">Microphone audio can stay on in both modes. Manual mode still disables mapped photo commands and AI detection.</div>
        </section>
      </div>

      <section class="panel strong">
        <div class="panel-header">
          <div>
            <div class="panel-eyebrow">Camera Console</div>
            <h2>Live Camera</h2>
          </div>
        </div>
        <div class="camera-grid">
          <div>
            <div class="preview-shell">
              <div id="preview_placeholder" class="preview-placeholder">Live preview is off.<br>Use the toggle below when you need a live camera feed.</div>
              <img id="live_preview" class="camera-preview" alt="live preview">
            </div>
            <div class="preview-toolbar" style="margin-top:12px;">
              <button id="toggle_preview" class="btn ghost">Show Live Camera</button>
              <button id="capture_now_btn" class="btn primary">Capture Now</button>
              <button id="camera_refresh_btn" class="btn ghost">Refresh Camera</button>
            </div>
            <div id="preview_hint" class="meta" style="margin-top:10px;">Live preview is off. Turn it on only when needed.</div>
            <div id="camera_chip_row" class="chip-row"></div>
            <div id="camera_status_box" class="status-box">Loading camera status...</div>
          </div>
          <div>
            <div class="selector-row">
              <select id="camera_source_select">${renderSourceOptions(cameraSources)}</select>
              <button id="camera_apply_btn" class="btn ghost">Switch Camera</button>
              <button id="camera_save_default_btn" class="btn ghost">Save As Default</button>
            </div>
            <div id="camera_source_meta" class="meta" style="margin-top:10px;">Loading available camera sources...</div>
            <pre id="camera_inventory_box" class="status-box" style="margin-top:10px;">Loading camera inventory...</pre>
            <div class="resolution-row" style="margin-top:14px;">
              <select id="resolution_select">${renderResolutionOptions()}</select>
              <input id="width_input" type="number" min="1" step="1" placeholder="width">
              <input id="height_input" type="number" min="1" step="1" placeholder="height">
              <input id="fps_input" type="number" min="1" step="1" placeholder="fps">
              <button id="apply_resolution_btn" class="btn ghost">Apply Resolution</button>
            </div>
            <div class="meta" style="margin-top:10px;">Use a preset or enter width, height, and fps manually. The direct camera service will reopen with the requested settings.</div>
            <pre id="camera_info_panel" class="raw-box">Checking camera...</pre>
          </div>
        </div>
      </section>

      <div class="control-grid">
        <section class="panel">
          <div class="panel-header">
            <div>
              <div class="panel-eyebrow">Replay</div>
              <h3>Capture Motion</h3>
            </div>
          </div>
          <div class="selector-row">
            <select id="replay_select"></select>
            <button id="replay_set" class="btn ghost">Set Active Replay</button>
          </div>
          <div id="replay_active_note" class="status-box">Loading replay status...</div>
        </section>

        ${recorderMarkup}
      </div>

      <section class="panel">
        <div class="panel-header">
          <div>
            <div class="panel-eyebrow">Replay Files</div>
            <h3>Replay Inventory</h3>
          </div>
        </div>
        <div class="action-row">
          <button id="replay_upload_btn" class="btn ghost">Upload Replay</button>
          <input id="replay_upload_file" type="file" accept=".jsonl" style="display:none;">
        </div>
        <div id="replay_test_status" class="status-box">Replay test idle.</div>
        <div id="replay_manager_list" class="replay-list"></div>
      </section>

      <section class="panel">
        <div class="panel-header">
          <div>
            <div class="panel-eyebrow">AI Voice Assets</div>
            <h3>Audio Prompt Library</h3>
          </div>
          <div class="action-row">
            <button id="audio_prompt_mode_toggle" class="btn ghost">Situation Speech</button>
            <button id="audio_prompt_fallback_toggle" class="btn ghost">Gemini Fallback</button>
          </div>
        </div>
        <div class="status-box">Upload prerecorded WAV clips for fixed AI situation prompts. In audio mode, AI detection, confirmation, countdown, retake, and thank-you situations use these clips first. Gemini can be switched back on for those same situations from the dashboard.</div>
        <div id="audio_prompt_status_box" class="status-box">Loading audio prompt library...</div>
        <div class="audio-prompt-recorder">
          <div class="selector-row">
            <select id="audio_prompt_record_key"></select>
            <input id="audio_prompt_record_filename" type="text" placeholder="filename.wav">
            <button id="audio_prompt_record_start" class="btn primary">Record From Text</button>
          </div>
          <textarea id="audio_prompt_record_text" class="prompt-editor" rows="4" placeholder="Prompt text to speak exactly..."></textarea>
          <div id="audio_prompt_record_status_box" class="status-box">Prompt recorder idle.</div>
        </div>
        <input id="audio_prompt_upload_file" type="file" accept=".wav" style="display:none;">
        <div id="audio_prompt_list" class="audio-prompt-grid"></div>
      </section>

      <section class="panel">
        <div class="panel-header">
          <div>
            <div class="panel-eyebrow">Actions</div>
            <h3>Operator Actions</h3>
          </div>
        </div>
        <div class="action-row">
          <button id="req_photo" class="btn ghost">Request Photo</button>
          <button id="clear_photos" class="btn ghost danger">Clear Photos</button>
          <button id="download_zip" class="btn ghost">Download All</button>
          <button id="upload_now" class="btn ghost">Upload Now</button>
          <button id="scripts_fix_check" class="btn ghost">Check RealSense</button>
          <button id="scripts_fix_run" class="btn ghost danger">Fix RealSense</button>
        </div>
      </section>

      ${aiReadinessMarkup}
      ${aiOptionsMarkup}

      <div class="status-grid">
        <section class="panel">
          <div class="panel-header">
            <div>
              <div class="panel-eyebrow">AI State</div>
              <h3>Autonomous State</h3>
            </div>
          </div>
          <div id="auto_state_panel" class="info-box"><i>Loading...</i></div>
        </section>

        <section class="panel">
          <div class="panel-header">
            <div>
              <div class="panel-eyebrow">Runtime</div>
              <h3>Runtime Health</h3>
            </div>
          </div>
          <div id="runtime_health_panel" class="info-box"><i>Loading...</i></div>
        </section>
      </div>
    </div>
  `;

  if (mode === "ai") {
    setTimeout(() => {
      const bSel = document.getElementById("det_backend_select");
      const bBtn = document.getElementById("det_backend_set");
      if (bSel) {
        bSel.value = backend;
        if (strictLocked) {
          bSel.disabled = true;
          bSel.value = "yolo";
        }
      }
      if (bBtn && strictLocked) {
        bBtn.disabled = true;
      }
      const greetingSel = document.getElementById("opt_greeting_replay_file");
      if (greetingSel) {
        greetingSel.dataset.current = aiOptions.autonomous_greeting_replay_file || "";
      }
    }, 0);
  }

  document.getElementById("mode_select").value = mode;

  document.getElementById("mode_set").addEventListener("click", async () => {
    const sel = document.getElementById("mode_select").value;
    await apiGet(`/api/set_mode?mode=${encodeURIComponent(sel)}`);
    await renderControls();
    await renderPhotos();
  });

  document.getElementById("mic_toggle").addEventListener("click", async () => {
    const nextEnabled = micEnabled ? 0 : 1;
    await apiGet(`/api/set_mic?enabled=${nextEnabled}`);
    await renderControls();
  });

  const replaySet = document.getElementById("replay_set");
  if (replaySet) {
    replaySet.addEventListener("click", async () => {
      const name = encodeURIComponent(document.getElementById("replay_select").value);
      await apiGet(`/api/set_replay?name=${name}`);
      await refreshReplayPanel();
    });
  }

  const replayRecordStart = document.getElementById("replay_record_start");
  if (replayRecordStart) {
    replayRecordStart.addEventListener("click", async () => {
      const name = (document.getElementById("replay_record_name").value || "").trim();
      const seconds = Number(document.getElementById("replay_record_seconds").value || "15");
      if (!name) {
        alert("Enter a replay file name first.");
        return;
      }
      await apiGet(`/api/replay_record_start?name=${encodeURIComponent(name)}&seconds=${encodeURIComponent(seconds)}`);
      await refreshReplayPanel();
    });
  }

  const replayUploadBtn = document.getElementById("replay_upload_btn");
  const replayUploadFile = document.getElementById("replay_upload_file");
  if (replayUploadBtn && replayUploadFile) {
    replayUploadBtn.addEventListener("click", () => replayUploadFile.click());
    replayUploadFile.addEventListener("change", async () => {
      const file = replayUploadFile.files && replayUploadFile.files[0];
      if (!file) return;
      const requestedName = window.prompt("Replay name", file.name) || file.name;
      const form = new FormData();
      form.append("file", file, file.name);
      form.append("name", requestedName);
      try {
        await apiPostFormData("/api/upload_replay", form);
        await refreshReplayPanel();
      } catch (e) {
        alert(`Replay upload failed: ${e.message}`);
      } finally {
        replayUploadFile.value = "";
      }
    });
  }

  const audioPromptUploadFile = document.getElementById("audio_prompt_upload_file");
  const audioPromptModeToggle = document.getElementById("audio_prompt_mode_toggle");
  const audioPromptFallbackToggle = document.getElementById("audio_prompt_fallback_toggle");
  const audioPromptRecordKey = document.getElementById("audio_prompt_record_key");
  const audioPromptRecordFilename = document.getElementById("audio_prompt_record_filename");
  const audioPromptRecordText = document.getElementById("audio_prompt_record_text");
  const audioPromptRecordStart = document.getElementById("audio_prompt_record_start");
  if (audioPromptUploadFile) {
    audioPromptUploadFile.addEventListener("change", async () => {
      const file = audioPromptUploadFile.files && audioPromptUploadFile.files[0];
      const key = audioPromptUploadFile.dataset.key || "";
      if (!file || !key) return;
      const defaultName = audioPromptUploadFile.dataset.filename || `${key}.wav`;
      const requestedName = window.prompt("Audio filename", defaultName) || defaultName;
      const form = new FormData();
      form.append("key", key);
      form.append("file", file, file.name);
      form.append("filename", requestedName);
      try {
        await apiPostFormData("/api/upload_audio_prompt", form);
        await refreshAudioPromptPanel();
      } catch (e) {
        alert(`Audio prompt upload failed: ${e.message}`);
      } finally {
        audioPromptUploadFile.value = "";
        delete audioPromptUploadFile.dataset.key;
        delete audioPromptUploadFile.dataset.filename;
      }
    });
  }
  if (audioPromptModeToggle) {
    audioPromptModeToggle.addEventListener("click", async () => {
      const current = String(audioPromptModeToggle.dataset.mode || "audio").trim().toLowerCase();
      const next = current === "gemini" ? "audio" : "gemini";
      await apiGet(`/api/set_audio_prompt_mode?mode=${encodeURIComponent(next)}`);
      await refreshAudioPromptPanel();
    });
  }
  if (audioPromptFallbackToggle) {
    audioPromptFallbackToggle.addEventListener("click", async () => {
      const current = audioPromptFallbackToggle.dataset.enabled === "1";
      await apiGet(`/api/set_audio_prompt_fallback?enabled=${current ? 0 : 1}`);
      await refreshAudioPromptPanel();
    });
  }
  if (audioPromptRecordKey && audioPromptRecordFilename && audioPromptRecordText) {
    audioPromptRecordKey.addEventListener("change", () => {
      const key = audioPromptRecordKey.value || "";
      audioPromptRecordFilename.value = promptFilenameForKey(key);
      audioPromptRecordFilename.dataset.autofill = "1";
      audioPromptRecordText.value = promptTextForKey(key);
      audioPromptRecordText.dataset.autofill = "1";
    });
    audioPromptRecordFilename.addEventListener("input", () => {
      audioPromptRecordFilename.dataset.autofill = "0";
    });
    audioPromptRecordText.addEventListener("input", () => {
      audioPromptRecordText.dataset.autofill = "0";
    });
  }
  if (audioPromptRecordStart && audioPromptRecordKey && audioPromptRecordFilename && audioPromptRecordText) {
    audioPromptRecordStart.addEventListener("click", async () => {
      const key = (audioPromptRecordKey.value || "").trim();
      const filename = (audioPromptRecordFilename.value || "").trim();
      const text = (audioPromptRecordText.value || "").trim();
      if (!key) {
        alert("Select a prompt key first.");
        return;
      }
      if (!text) {
        alert("Prompt text is required.");
        return;
      }
      try {
        await apiPostJson("/api/audio_prompt_record", { key, filename, text });
        await refreshAudioPromptRecordStatus();
      } catch (e) {
        alert(`Prompt recording failed: ${e.message}`);
      }
    });
  }

  const peopleUploadBtn = document.getElementById("people_upload_btn");
  const peopleUploadFile = document.getElementById("people_upload_file");
  const peopleResetBtn = document.getElementById("people_reset_btn");
  if (peopleUploadBtn && peopleUploadFile) {
    peopleUploadBtn.addEventListener("click", () => {
      peopleUploadFile.dataset.personId = "";
      peopleUploadFile.click();
    });
    peopleUploadFile.addEventListener("change", async () => {
      const file = peopleUploadFile.files && peopleUploadFile.files[0];
      if (!file) return;
      const form = new FormData();
      form.append("file", file, file.name);
      if (peopleUploadFile.dataset.personId) {
        form.append("person_id", peopleUploadFile.dataset.personId);
      }
      try {
        await apiPostFormData("/api/upload_person", form);
        await renderPeople();
      } catch (e) {
        alert(`People upload failed: ${e.message}`);
      } finally {
        peopleUploadFile.value = "";
        delete peopleUploadFile.dataset.personId;
      }
    });
  }
  if (peopleResetBtn) {
    peopleResetBtn.addEventListener("click", async () => {
      if (!confirm("Delete all saved people profiles?")) return;
      await apiGet("/api/reset_people");
      await renderPeople();
    });
  }

  document.getElementById("req_photo").addEventListener("click", async () => {
    await apiGet("/api/request_photo");
  });
  document.getElementById("clear_photos").addEventListener("click", async () => {
    if (!confirm("Delete ALL photos?")) return;
    await apiGet("/api/clear_photos");
    await renderPhotos();
  });
  document.getElementById("download_zip").addEventListener("click", async () => {
    const r = await apiGet("/api/download_zip");
    if (r.ok) window.location = `/${r.archive}`;
  });
  document.getElementById("upload_now").addEventListener("click", async () => {
    await apiGet("/api/upload_now");
  });
  document.getElementById("scripts_fix_check").addEventListener("click", async () => {
    const r = await apiGet("/api/run_scripts_fix?mode=--check");
    alert(`RealSense check:\n${(r.stdout || []).join("\n") || "true"}`);
  });
  document.getElementById("scripts_fix_run").addEventListener("click", async () => {
    if (!confirm("Run RealSense fix script?")) return;
    const r = await apiGet("/api/run_scripts_fix?mode=--fix");
    alert(`Fix output:\n${(r.stdout || []).join("\n")}`);
  });

  document.getElementById("toggle_preview").addEventListener("click", () => {
    setPreviewEnabled(!window.__previewEnabled);
  });
  document.getElementById("camera_refresh_btn").addEventListener("click", async () => {
    window.__cameraSourcesCache = null;
    await refreshCameraPanel();
    restartPreview();
  });
  document.getElementById("capture_now_btn").addEventListener("click", async () => {
    const statusBox = document.getElementById("camera_status_box");
    statusBox.textContent = "Capturing...";
    try {
      const resp = await apiGet("/api/capture");
      statusBox.textContent = `Saved\n${resp.result || "OK"}`;
      await renderPhotos();
      await refreshCameraPanel();
    } catch (e) {
      statusBox.textContent = `Capture failed\n${e.message}`;
    }
  });
  document.getElementById("camera_apply_btn").addEventListener("click", async () => {
    const selector = document.getElementById("camera_source_select");
    const statusBox = document.getElementById("camera_status_box");
    const source = selector.value;
    statusBox.textContent = `Switching camera to ${source}...`;
    try {
      await apiGet(`/api/set_camera_source?source=${encodeURIComponent(source)}`);
      restartPreview();
      await refreshCameraPanel();
    } catch (e) {
      statusBox.textContent = `Camera switch failed\n${e.message}`;
    }
  });
  document.getElementById("camera_save_default_btn").addEventListener("click", async () => {
    const selector = document.getElementById("camera_source_select");
    const statusBox = document.getElementById("camera_status_box");
    const camera = ((await apiGet("/api/camera_health")).camera) || {};
    const cameraSources = window.__cameraSourcesCache || await apiGet("/api/camera_sources");
    const serial = selectedCameraSerial(selector.value, cameraSources, camera);
    if (!serial) {
      statusBox.textContent = "Default camera save failed\nSelect a RealSense source first.";
      return;
    }
    statusBox.textContent = `Saving preferred camera ${serial}...`;
    try {
      await apiGet(`/api/set_preferred_camera?serial=${encodeURIComponent(serial)}`);
      window.__cameraSourcesCache = null;
      await refreshCameraPanel();
      statusBox.textContent = `Preferred camera saved\n${serial}`;
    } catch (e) {
      statusBox.textContent = `Default camera save failed\n${e.message}`;
    }
  });
  document.getElementById("resolution_select").addEventListener("change", (e) => {
    const value = e.target.value;
    const match = value.match(/^(\d+)x(\d+)@(\d+)$/);
    if (!match) return;
    setResolutionInputs(Number(match[1]), Number(match[2]), Number(match[3]));
  });
  document.getElementById("apply_resolution_btn").addEventListener("click", async () => {
    const width = Number(document.getElementById("width_input").value);
    const height = Number(document.getElementById("height_input").value);
    const fps = Number(document.getElementById("fps_input").value);
    const statusBox = document.getElementById("camera_status_box");
    statusBox.textContent = `Applying resolution ${width}x${height}@${fps}...`;
    try {
      const resp = await apiGet(`/api/set_camera_resolution?width=${encodeURIComponent(width)}&height=${encodeURIComponent(height)}&fps=${encodeURIComponent(fps)}`);
      const camera = resp.camera || {};
      setResolutionInputs(camera.requested_width, camera.requested_height, camera.requested_fps);
      restartPreview();
      await refreshCameraPanel();
    } catch (e) {
      statusBox.textContent = `Resolution change failed\n${e.message}`;
    }
  });

  if (mode === "ai") {
    const backendButton = document.getElementById("det_backend_set");
    if (backendButton) {
      backendButton.addEventListener("click", async () => {
        const v = document.getElementById("det_backend_select").value || "normal";
        try {
          await apiGet(`/api/set_detector_backend?backend=${encodeURIComponent(v)}`);
          await renderControls();
        } catch (e) {
          alert(e.message);
        }
      });
    }
    const aiSave = document.getElementById("opt_ai_save");
    if (aiSave) {
      aiSave.addEventListener("click", async () => {
        const hard = document.getElementById("opt_hard_lock").checked ? 1 : 0;
        const retake = document.getElementById("opt_retake_prompt").checked ? 1 : 0;
        const greetEnabled = document.getElementById("opt_greeting_replay").checked ? 1 : 0;
        const greetFile = document.getElementById("opt_greeting_replay_file").value || "";
        const captureReplay = document.getElementById("opt_capture_replay").checked ? 1 : 0;
        const faceRecognition = document.getElementById("opt_face_recognition").checked ? 1 : 0;
        const faceThreshold = document.getElementById("opt_face_threshold").value || "0.88";
        try {
          await apiGet(
            `/api/set_ai_options?hard_target_lock_enabled=${hard}&retake_prompt_enabled=${retake}` +
            `&autonomous_greeting_replay_enabled=${greetEnabled}` +
            `&autonomous_greeting_replay_file=${encodeURIComponent(greetFile)}` +
            `&autonomous_capture_replay_enabled=${captureReplay}` +
            `&face_recognition_enabled=${faceRecognition}` +
            `&face_recognition_threshold=${encodeURIComponent(faceThreshold)}`
          );
          await renderControls();
        } catch (e) {
          alert(e.message);
        }
      });
    }
  }

  setPreviewEnabled(window.__previewEnabled);
  await refreshCameraPanel();
  await refreshReplayPanel();
  await refreshAudioPromptPanel();
  await refreshAudioPromptRecordStatus();
  await refreshAutonomousPanel();
  await refreshAiReadinessPanel();
  await refreshRuntimeHealthPanel();
  await renderPeople();

  if (window.__dashboardPoll) clearInterval(window.__dashboardPoll);
  window.__peoplePollCounter = 0;
  window.__dashboardPoll = setInterval(async () => {
    await refreshCameraPanel().catch(() => {});
    await refreshReplayPanel().catch(() => {});
    await refreshAutonomousPanel().catch(() => {});
    await refreshAiReadinessPanel().catch(() => {});
    await refreshRuntimeHealthPanel().catch(() => {});
    window.__peoplePollCounter += 1;
    if (window.__peoplePollCounter % 4 === 0) {
      await renderPeople().catch(() => {});
    }
    if (window.__peoplePollCounter % 8 === 0) {
      await refreshAudioPromptPanel().catch(() => {});
    }
    await refreshAudioPromptRecordStatus().catch(() => {});
  }, 1500);
}

async function renderPhotos() {
  const list = document.getElementById("list");
  if (!list) return;
  list.innerHTML = "";
  const res = await apiGet("/api/photos");
  const photos = res.photos || [];
  if (!photos.length) {
    list.innerHTML = '<div class="panel"><i>No photos yet.</i></div>';
    return;
  }
  for (const photo of photos) {
    const card = el("article", "card");
    card.innerHTML = `
      <img class="thumb" src="/${encodeURIComponent(photo.name)}?t=${Date.now()}" alt="${escapeHtml(photo.name)}">
      <div class="card-body">
        <div class="name">${escapeHtml(photo.name)}</div>
        <div class="photo-meta">${formatSize(photo.size || 0)}<br>${formatTime(photo.mtime || 0)}</div>
        <div class="actions">
          <a href="/${encodeURIComponent(photo.name)}" target="_blank">Open</a>
          <a href="#" data-name="${encodeURIComponent(photo.name)}" class="del">Delete</a>
          <a href="/api/reupload?name=${encodeURIComponent(photo.name)}">Reupload</a>
        </div>
      </div>
    `;
    card.querySelector(".thumb").addEventListener("click", () => openPreview(photo.name));
    list.appendChild(card);
  }

  document.querySelectorAll("a.del").forEach((anchor) => {
    anchor.addEventListener("click", async (ev) => {
      ev.preventDefault();
      const name = anchor.getAttribute("data-name");
      if (!confirm("Delete this photo?")) return;
      await apiGet(`/api/delete?name=${name}`);
      await renderPhotos();
    });
  });
}

function openPreview(name) {
  const modal = document.getElementById("preview_modal");
  const img = document.getElementById("modal_img");
  const actions = document.getElementById("modal_actions");
  img.src = `/${encodeURIComponent(name)}`;
  actions.innerHTML = `
    <a href="/${encodeURIComponent(name)}" target="_blank">Open / Download</a>
    <a href="/api/reupload?name=${encodeURIComponent(name)}">Reupload</a>
    <a href="#" id="modal_delete">Delete</a>
  `;
  document.getElementById("modal_delete").addEventListener("click", async (ev) => {
    ev.preventDefault();
    if (!confirm("Delete this photo?")) return;
    await apiGet(`/api/delete?name=${encodeURIComponent(name)}`);
    closeModal();
    await renderPhotos();
  });
  modal.style.display = "flex";
}

function closeModal() {
  const modal = document.getElementById("preview_modal");
  if (modal) modal.style.display = "none";
}

window.addEventListener("load", () => {
  bindTopActions();
  const modalClose = document.getElementById("modal_close");
  if (modalClose) modalClose.addEventListener("click", closeModal);
  renderControls().catch((e) => {
    const ctrl = document.getElementById("controls");
    if (ctrl) ctrl.innerHTML = `<div class="panel"><div class="status-box">Dashboard load failed\n${escapeHtml(e.message)}</div></div>`;
  });
  renderPhotos().catch(() => {});
});

window.addEventListener("beforeunload", () => {
  if (window.__dashboardPoll) clearInterval(window.__dashboardPoll);
  if (window.__previewEnabled) setPreviewEnabled(false);
});

async function openScriptsModal() {
  const modal = document.getElementById("scripts_modal");
  const list = document.getElementById("scripts_list");
  const editor = document.getElementById("script_editor");
  const fname = document.getElementById("script_filename");
  list.innerHTML = "Loading...";
  try {
    const r = await apiGet("/api/scripts");
    if ((r.scripts || []).length === 0) {
      list.innerHTML = "<i>No scripts</i>";
    } else {
      list.innerHTML = "";
      r.scripts.forEach((s) => {
        const row = document.createElement("div");
        row.className = "script-row";
        row.innerHTML = `<b>${escapeHtml(s)}</b> &nbsp; <button class="btn-secondary btn-small" data-name="${escapeHtml(s)}" data-act="set">Set</button> <button class="btn-secondary btn-small" data-name="${escapeHtml(s)}" data-act="edit">Edit</button> <button class="btn-danger btn-small" data-name="${escapeHtml(s)}" data-act="del">Delete</button>`;
        list.appendChild(row);
      });
      list.querySelectorAll("button").forEach((btn) => {
        btn.addEventListener("click", async () => {
          const act = btn.getAttribute("data-act");
          const name = btn.getAttribute("data-name");
          if (act === "set") {
            await fetch("/api/set_sanad_script", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }) });
            alert(`Set as active: ${name}`);
          } else if (act === "del") {
            if (!confirm(`Delete script ${name}?`)) return;
            await fetch("/api/delete_script", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }) });
            await openScriptsModal();
          } else if (act === "edit") {
            const r = await apiGet(`/api/script_content?name=${encodeURIComponent(name)}`);
            if (r.ok) {
              editor.value = r.script || "";
              fname.value = name;
            }
          }
        });
      });
    }
  } catch (_e) {
    list.innerHTML = "<i>Error</i>";
  }

  try {
    const a = await apiGet("/api/sanad_script");
    if (a.ok) editor.value = a.script || "";
  } catch (_e) {}

  document.getElementById("scripts_modal_close").onclick = () => {
    modal.style.display = "none";
  };
  document.getElementById("script_upload").onclick = async () => {
    const name = fname.value.trim();
    const script = editor.value || "";
    if (!name) {
      alert("Enter filename");
      return;
    }
    await fetch("/api/upload_script", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ filename: name, script }) });
    await openScriptsModal();
  };
  document.getElementById("script_save").onclick = async () => {
    await fetch("/api/sanad_script", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ script: editor.value || "" }) });
    alert("Saved active SANAD script");
  };

  modal.style.display = "flex";
}
