// identity's registration page logic. Vanilla JS, no framework — see
// register.html's top comment for why this is the fallback path, not the primary
// (voice) one.
"use strict";

const params = new URLSearchParams(location.search);
const API = (params.get("api") || "").replace(/\/$/, "");
const TOKEN = params.get("token") || "";
const DEVICE_ID = params.get("device") || "unknown";

if (!API || !TOKEN) {
  document.body.innerHTML =
    '<p class="error" style="padding:24px">identity not configured — missing ' +
    "?api=&token= in the URL.</p>";
  throw new Error("identity: missing ?api=/&token= query params");
}

function api(path, options) {
  options = options || {};
  options.headers = Object.assign({ Authorization: `Bearer ${TOKEN}` }, options.headers || {});
  return fetch(`${API}${path}`, options).then((res) =>
    res.json().then((body) => {
      if (!res.ok && res.status !== 409) throw new Error(body.error || `${res.status} ${res.statusText}`);
      return body;
    })
  );
}

function escapeHtml(s) {
  return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
}

// --- Camera (best-effort — registration still works with a text name + voice-found
// candidate even if the camera fails; the photo is an audit artifact, never load-
// bearing for the actual identity decision, see ../README.md) -----------------
const video = document.getElementById("camera-preview");
const cameraError = document.getElementById("camera-error");
let stream = null;

// `navigator.mediaDevices` is undefined on a NON-SECURE ORIGIN — getUserMedia requires
// a secure context, so plain http://192.168.x.x has no camera API at all. That case is
// checked explicitly rather than left to `?.`: optional chaining short-circuits the
// WHOLE chain, so `navigator.mediaDevices?.getUserMedia(...).then(...).catch(...)`
// evaluates to undefined and neither handler ever runs — the camera silently doesn't
// start, no message appears, and registration proceeds photo-less with no explanation.
// See proxy/README.md; serving this page over HTTPS is what actually fixes it.
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
  cameraError.textContent = window.isSecureContext
    ? "No camera API in this browser (registration still works without it)."
    : "Camera needs HTTPS — this page is on a non-secure origin, so the browser blocks " +
      "camera access entirely. Registration still works without a photo. See proxy/README.md.";
  cameraError.hidden = false;
} else {
  navigator.mediaDevices
    .getUserMedia({ video: { facingMode: "user" }, audio: false })
    .then((s) => {
      stream = s;
      video.srcObject = s;
    })
    .catch((err) => {
      cameraError.textContent = `Camera unavailable: ${err.message} (registration still works without it)`;
      cameraError.hidden = false;
    });
}

function capturePhoto() {
  if (!stream) return Promise.resolve(null);
  const canvas = document.getElementById("captured-frame");
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  canvas.getContext("2d").drawImage(video, 0, 0);
  return new Promise((resolve) => {
    canvas.toBlob(
      (blob) => {
        if (!blob) return resolve(null);
        api("/register/photo", { method: "POST", body: blob, headers: { "Content-Type": "image/jpeg" } })
          .then((r) => resolve(r.photo_id || null))
          .catch(() => resolve(null));
      },
      "image/jpeg",
      0.85
    );
  });
}

// --- Registration ------------------------------------------------------------
const form = document.getElementById("register-form");
const status = document.getElementById("register-status");
const picker = document.getElementById("candidate-picker");
const candidateList = document.getElementById("candidate-list");

function attemptRegister(name, entityId, noDevice) {
  status.textContent = "Registering…";
  picker.hidden = true;
  return capturePhoto().then((photoId) =>
    api("/register", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        name,
        device_id: DEVICE_ID,
        photo_id: photoId,
        entity_id: entityId,
        no_device: !!noDevice,
      }),
    })
  ).then((result) => {
    if (result.ok) {
      status.textContent = result.message;
      form.reset();
      loadPeople();
      return;
    }
    if (result.reason === "ambiguous") {
      status.textContent = result.message;
      candidateList.innerHTML = result.candidates
        .map(
          (c) =>
            `<button type="button" class="card" style="width:100%;text-align:left;border:none;cursor:pointer" data-entity="${escapeHtml(c.entity_id)}">
              <span class="card-name">${escapeHtml(c.friendly_name)}</span>
            </button>`
        )
        .join("");
      picker.hidden = false;
      candidateList.querySelectorAll("[data-entity]").forEach((btn) => {
        btn.addEventListener("click", () => attemptRegister(name, btn.dataset.entity, false));
      });
      return;
    }
    status.textContent = result.message || "Could not register.";
  }).catch((err) => {
    status.textContent = `Error: ${err.message}`;
  });
}

form.addEventListener("submit", (event) => {
  event.preventDefault();
  const name = document.getElementById("f-name").value.trim();
  const noDevice = document.getElementById("f-no-device").checked;
  if (name) attemptRegister(name, null, noDevice);
});

// --- Guest (no name, no device — see identity/README.md) -----------------------
const guestBtn = document.getElementById("guest-btn");
const guestStatus = document.getElementById("guest-status");

guestBtn.addEventListener("click", () => {
  guestStatus.textContent = "Adding guest…";
  capturePhoto()
    .then((photoId) =>
      api("/register/guest", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ device_id: DEVICE_ID, photo_id: photoId }),
      })
    )
    .then((result) => {
      guestStatus.textContent = result.message;
      loadPeople();
    })
    .catch((err) => {
      guestStatus.textContent = `Error: ${err.message}`;
    });
});

// --- Profile pictures ----------------------------------------------------------
// Fetched via blob, not a plain <img src="...">, because every identity endpoint
// (including this one) requires an Authorization header — an <img> tag has no way
// to send one, so a bare src= URL would just 401. See identity/README.md for why
// every registration also updates the person's profile photo to whatever the
// kiosk's camera last captured.
function loadAvatar(imgContainer, personId) {
  fetch(`${API}/people/${personId}/photo`, { headers: { Authorization: `Bearer ${TOKEN}` } })
    .then((res) => (res.ok ? res.blob() : Promise.reject()))
    .then((blob) => {
      const img = document.createElement("img");
      img.src = URL.createObjectURL(blob);
      imgContainer.replaceChildren(img);
    })
    .catch(() => {
      /* no photo — leave the plain circle placeholder */
    });
}

// --- Already-registered list ---------------------------------------------------
function loadPeople() {
  const el = document.getElementById("people-list");
  api("/people")
    .then((data) => {
      const people = data.people || [];
      if (!people.length) {
        el.innerHTML = '<p class="hint">Nobody registered yet.</p>';
        return;
      }
      el.innerHTML = people
        .map(
          (p) =>
            `<div class="card">
              <span class="avatar" data-person="${p.id}">👤</span>
              <span class="card-name">${escapeHtml(p.name)}</span>
              <span class="card-meta">${p.identifiers.length} device${p.identifiers.length === 1 ? "" : "s"}</span>
            </div>`
        )
        .join("");
      people.forEach((p) => {
        if (p.has_photo) {
          const el2 = el.querySelector(`.avatar[data-person="${p.id}"]`);
          if (el2) loadAvatar(el2, p.id);
        }
      });
    })
    .catch((err) => {
      el.innerHTML = `<p class="error">Could not load: ${escapeHtml(err.message)}</p>`;
    });
}

loadPeople();
