(() => {
  const PAGE_SIZE = 10;

  const ICONS = {
    listen: `<svg width="14" height="14" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M8 2a4 4 0 0 0-4 4v3a4 4 0 0 0 8 0V6a4 4 0 0 0-4-4zm0 12a5 5 0 0 0 5-5H3a5 5 0 0 0 5 5z"/></svg>`,
  };

  const el = (id) => document.getElementById(id);
  const t = (key, vars) => window.I18n?.t(key, vars) ?? key;

  const listEl = el("soundList");
  const emptyEl = el("soundListEmpty");
  const createBtnEl = el("soundCreateBtn");
  const filterInputEl = el("soundsFilterInput");
  const filterCountEl = el("soundsFilterCount");
  const pageLabelEl = el("soundsPageLabel");
  const dialogEl = el("soundEditDialog");
  const formEl = el("soundEditForm");
  const titleEl = el("soundEditTitle");
  const systemBadgeEl = el("soundEditSystemBadge");
  const nameEl = el("soundEditName");
  const descEl = el("soundEditDescription");
  const volumeEl = el("soundEditVolume");
  const volumeOutEl = el("soundEditVolumeOut");
  const enabledEl = el("soundEditEnabled");
  const fileMetaEl = el("soundEditFileMeta");
  const fileSectionEl = el("soundEditFileSection");
  const uploadInputEl = el("soundEditUploadInput");
  const uploadBtnEl = el("soundEditUploadBtn");
  const listenBtnEl = el("soundEditListenBtn");
  const playRobotBtnEl = el("soundEditPlayRobotBtn");
  const deleteBtnEl = el("soundEditDeleteBtn");
  const deleteConfirmDialogEl = el("soundDeleteConfirmDialog");
  const deleteConfirmTextEl = el("soundDeleteConfirmText");

  const store = {
    sounds: [],
    editingId: null,
    previewAudio: null,
    filter: "",
    page: 1,
  };

  function canWrite() {
    if (!window.AuthApp?.canWrite) return true;
    return window.AuthApp.canWrite("sounds") || window.AuthApp.canWrite("integrations");
  }

  function escapeHtml(str) {
    return String(str)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;");
  }

  async function apiJson(url, opts = {}) {
    if (window.AuthApp && !window.AuthApp.isReady()) {
      throw new Error("not authenticated");
    }
    const res = await fetch(url, { credentials: "include", ...opts });
    const text = await res.text();
    let data = null;
    try {
      data = text ? JSON.parse(text) : null;
    } catch {
      data = null;
    }
    if (!res.ok) {
      const msg = (data && data.error) || text || res.statusText;
      throw new Error(msg);
    }
    return data;
  }

  async function refreshSounds() {
    const data = await apiJson("/api/sounds");
    store.sounds = Array.isArray(data.sounds) ? data.sounds : [];
  }

  function formatDuration(ms) {
    if (!Number.isFinite(Number(ms))) return "—";
    const sec = Math.round(Number(ms) / 100) / 10;
    return `${sec}s`;
  }

  function currentEditingSound() {
    return store.editingId ? store.sounds.find((s) => s.id === store.editingId) : null;
  }

  function canListen(sound) {
    return !!(sound?.file_name);
  }

  function sortedSounds() {
    return [...store.sounds].sort((a, b) => {
      if (!!a.is_system !== !!b.is_system) return a.is_system ? -1 : 1;
      return (a.name || "").localeCompare(b.name || "");
    });
  }

  function filteredSounds() {
    const q = store.filter.trim().toLowerCase();
    let items = sortedSounds();
    if (q) {
      items = items.filter((sound) => {
        const name = (sound.name || "").toLowerCase();
        const file = (sound.file_name || "").toLowerCase();
        const desc = (sound.description || "").toLowerCase();
        return name.includes(q) || file.includes(q) || desc.includes(q);
      });
    }
    return items;
  }

  function pageCount(total) {
    return Math.max(1, Math.ceil(total / PAGE_SIZE));
  }

  function pagedItems(items) {
    const totalPages = pageCount(items.length);
    if (store.page > totalPages) store.page = totalPages;
    if (store.page < 1) store.page = 1;
    const start = (store.page - 1) * PAGE_SIZE;
    return items.slice(start, start + PAGE_SIZE);
  }

  function updatePagerUi(totalItems) {
    const totalPages = pageCount(totalItems);
    if (filterCountEl) filterCountEl.textContent = t("sounds.itemsFound", { n: totalItems });
    if (pageLabelEl) pageLabelEl.textContent = t("sounds.pageOf", { page: store.page, total: totalPages });
    const atStart = store.page <= 1;
    const atEnd = store.page >= totalPages;
    el("soundsPageFirst")?.toggleAttribute("disabled", atStart);
    el("soundsPagePrev")?.toggleAttribute("disabled", atStart);
    el("soundsPageNext")?.toggleAttribute("disabled", atEnd);
    el("soundsPageLast")?.toggleAttribute("disabled", atEnd);
  }

  function soundMetaLine(sound) {
    const hasFile = !!sound.file_name;
    const parts = [
      sound.enabled === false ? t("common.disabled") : t("common.enabled"),
      t("sounds.volumeShort", { volume: sound.volume != null ? sound.volume : 100 }),
      hasFile ? sound.file_name : t("sounds.noFile"),
    ];
    if (sound.duration_ms != null) parts.push(formatDuration(sound.duration_ms));
    if (sound.is_system) parts.push(t("sounds.systemShort"));
    return parts.map((p) => escapeHtml(p)).join(" · ");
  }

  function renderList() {
    if (!listEl) return;
    const items = filteredSounds();
    const pageItems = pagedItems(items);
    updatePagerUi(items.length);

    listEl.innerHTML = "";
    const showEmpty = items.length === 0;
    if (listEl) listEl.hidden = showEmpty;
    if (emptyEl) {
      emptyEl.hidden = !showEmpty;
      emptyEl.textContent = store.filter.trim() ? t("sounds.emptyFilter") : t("sounds.empty");
    }

    pageItems.forEach((sound) => {
      const row = document.createElement("article");
      row.className = "soundsMirRow";
      row.setAttribute("role", "listitem");
      row.dataset.id = sound.id;

      const listenDisabled = !canListen(sound);
      const editDisabled = !canWrite();

      row.innerHTML = `
        <div class="soundsMirRowMain">
          <button type="button" class="soundsMirRowTitle soundEditBtn" data-id="${escapeHtml(sound.id)}" ${editDisabled ? "disabled" : ""}>
            ${escapeHtml(sound.name || sound.id)}
          </button>
          <div class="soundsMirRowMeta">${soundMetaLine(sound)}</div>
        </div>
        <div class="soundsMirRowActions">
          <button type="button" class="mapsMirIconBtn soundListenBtn" data-id="${escapeHtml(sound.id)}" ${listenDisabled ? "disabled" : ""} title="${escapeHtml(t("sounds.listen"))}">
            ${ICONS.listen}
          </button>
          <button type="button" class="mapsMirBtn mapsMirBtn--outline soundEditBtn" data-id="${escapeHtml(sound.id)}" ${editDisabled ? "disabled" : ""}>
            ${escapeHtml(t("common.edit"))}
          </button>
        </div>`;

      row.querySelectorAll(".soundEditBtn").forEach((btn) => {
        btn.addEventListener("click", () => {
          if (!canWrite()) return;
          openDialog(btn.dataset.id);
        });
      });
      row.querySelector(".soundListenBtn")?.addEventListener("click", () => listenSound(sound.id));
      row.addEventListener("dblclick", () => {
        if (canWrite()) openDialog(sound.id);
      });
      listEl.appendChild(row);
    });

    if (createBtnEl) createBtnEl.hidden = !canWrite();
  }

  function stopPreview() {
    if (store.previewAudio) {
      store.previewAudio.pause();
      store.previewAudio = null;
    }
  }

  function listenSound(id) {
    const sound = store.sounds.find((s) => s.id === id);
    if (!canListen(sound)) return;
    stopPreview();
    const audio = new Audio(`/api/sounds/${encodeURIComponent(id)}/file`);
    store.previewAudio = audio;
    audio.play().catch(() => alert(t("sounds.playFailed")));
  }

  function readVolume() {
    return Number(volumeEl?.value) || 0;
  }

  function syncVolumeOut() {
    if (volumeOutEl) volumeOutEl.textContent = String(readVolume());
  }

  function updateFileMeta(sound) {
    if (!fileMetaEl) return;
    if (sound?.file_name) {
      fileMetaEl.textContent = t("sounds.fileMeta", {
        name: sound.file_name,
        duration: formatDuration(sound.duration_ms),
      });
    } else if (sound?.is_system) {
      fileMetaEl.textContent = t("sounds.systemNoFile");
    } else {
      fileMetaEl.textContent = t("sounds.noFile");
    }
    const listenOk = canListen(sound);
    if (listenBtnEl) listenBtnEl.disabled = !listenOk;
    if (playRobotBtnEl) playRobotBtnEl.disabled = !sound?.id || sound.enabled === false;
  }

  function applyDialogPermissions(sound) {
    const ro = !canWrite();
    const isSystem = !!sound?.is_system;
    if (nameEl) nameEl.readOnly = ro || isSystem;
    if (descEl) descEl.readOnly = ro;
    if (volumeEl) volumeEl.disabled = ro;
    if (enabledEl) enabledEl.disabled = ro;
    if (uploadBtnEl) uploadBtnEl.hidden = isSystem || ro;
    if (fileSectionEl) fileSectionEl.hidden = false;
    if (deleteBtnEl) deleteBtnEl.hidden = !sound || isSystem || ro;
    if (systemBadgeEl) systemBadgeEl.hidden = !isSystem;
  }

  function openDialog(id = null) {
    store.editingId = id;
    const existing = id ? store.sounds.find((s) => s.id === id) : null;
    if (titleEl) titleEl.textContent = existing ? t("sounds.editTitle") : t("sounds.createTitle");
    if (nameEl) nameEl.value = existing?.name || "";
    if (descEl) descEl.value = existing?.description || "";
    if (enabledEl) enabledEl.checked = existing?.enabled !== false;
    if (volumeEl) volumeEl.value = existing?.volume != null ? existing.volume : 100;
    syncVolumeOut();
    updateFileMeta(existing);
    applyDialogPermissions(existing);
    dialogEl?.showModal();
  }

  function readPayload() {
    return {
      name: nameEl?.value.trim() || "",
      description: descEl?.value.trim() || "",
      enabled: enabledEl?.checked !== false,
      volume: readVolume(),
    };
  }

  function isReservedName(name) {
    return ["beep", "horn", "chime"].includes(String(name || "").trim().toLowerCase());
  }

  function nameExists(name, exceptId = null) {
    const lower = String(name || "").trim().toLowerCase();
    return store.sounds.some(
      (s) => s.id !== exceptId && String(s.name || "").trim().toLowerCase() === lower,
    );
  }

  async function saveDialog() {
    if (!canWrite()) return;
    const soundId = store.editingId;
    const payload = readPayload();
    if (!payload.name) {
      alert(t("sounds.nameRequired"));
      return;
    }
    if (!soundId) {
      if (isReservedName(payload.name)) {
        alert(t("sounds.reservedName"));
        return;
      }
      if (nameExists(payload.name)) {
        alert(t("sounds.nameDuplicate"));
        return;
      }
    }
    try {
      if (soundId) {
        await apiJson(`/api/sounds/${encodeURIComponent(soundId)}`, {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(payload),
        });
      } else {
        const created = await apiJson("/api/sounds", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(payload),
        });
        store.editingId = created.id;
      }
      await refreshSounds();
      renderList();
      const updated = currentEditingSound();
      updateFileMeta(updated);
      applyDialogPermissions(updated);
      if (!uploadInputEl?.files?.length) {
        dialogEl?.close();
      }
    } catch (e) {
      alert(e.message);
    }
  }

  async function uploadFile() {
    if (!canWrite() || !store.editingId) return;
    const sound = currentEditingSound();
    if (sound?.is_system) return;
    const file = uploadInputEl?.files?.[0];
    if (!file) return;
    const form = new FormData();
    form.append("file", file);
    try {
      await apiJson(`/api/sounds/${encodeURIComponent(store.editingId)}/file`, {
        method: "POST",
        body: form,
      });
      uploadInputEl.value = "";
      await refreshSounds();
      renderList();
      updateFileMeta(currentEditingSound());
    } catch (e) {
      alert(e.message);
    }
  }

  function openDeleteConfirm() {
    const sound = currentEditingSound();
    if (!sound || sound.is_system || !canWrite()) return;
    if (deleteConfirmTextEl) {
      deleteConfirmTextEl.textContent = t("sounds.deleteConfirmText", { name: sound.name || sound.id });
    }
    deleteConfirmDialogEl?.showModal();
  }

  async function confirmDelete() {
    if (!canWrite() || !store.editingId) return;
    try {
      await apiJson(`/api/sounds/${encodeURIComponent(store.editingId)}`, { method: "DELETE" });
      deleteConfirmDialogEl?.close();
      dialogEl?.close();
      store.editingId = null;
      await refreshSounds();
      renderList();
    } catch (e) {
      alert(e.message);
    }
  }

  async function playOnRobot() {
    if (!store.editingId) return;
    try {
      await apiJson(`/api/sounds/${encodeURIComponent(store.editingId)}/play`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ volume: readVolume() }),
      });
    } catch (e) {
      alert(e.message);
    }
  }

  function clearFilters() {
    store.filter = "";
    store.page = 1;
    if (filterInputEl) filterInputEl.value = "";
    renderList();
  }

  function bindEvents() {
    createBtnEl?.addEventListener("click", () => {
      if (!canWrite()) return;
      openDialog(null);
    });
    formEl?.addEventListener("submit", (evt) => {
      evt.preventDefault();
      saveDialog();
    });
    el("soundEditCancelBtn")?.addEventListener("click", () => {
      stopPreview();
      store.editingId = null;
      dialogEl?.close();
    });
    dialogEl?.addEventListener("cancel", (evt) => {
      evt.preventDefault();
      stopPreview();
      store.editingId = null;
      dialogEl?.close();
    });
    volumeEl?.addEventListener("input", syncVolumeOut);
    uploadBtnEl?.addEventListener("click", () => uploadInputEl?.click());
    uploadInputEl?.addEventListener("change", () => {
      saveDialog().then(() => uploadFile());
    });
    listenBtnEl?.addEventListener("click", () => {
      if (store.editingId) listenSound(store.editingId);
    });
    playRobotBtnEl?.addEventListener("click", () => playOnRobot());
    deleteBtnEl?.addEventListener("click", () => openDeleteConfirm());
    el("soundDeleteCancelBtn")?.addEventListener("click", () => deleteConfirmDialogEl?.close());
    el("soundDeleteYesBtn")?.addEventListener("click", () => confirmDelete());
    deleteConfirmDialogEl?.addEventListener("cancel", (evt) => {
      evt.preventDefault();
      deleteConfirmDialogEl?.close();
    });

    filterInputEl?.addEventListener("input", () => {
      store.filter = filterInputEl.value;
      store.page = 1;
      renderList();
    });
    el("soundsClearFiltersBtn")?.addEventListener("click", clearFilters);
    el("soundsPageFirst")?.addEventListener("click", () => {
      store.page = 1;
      renderList();
    });
    el("soundsPagePrev")?.addEventListener("click", () => {
      store.page = Math.max(1, store.page - 1);
      renderList();
    });
    el("soundsPageNext")?.addEventListener("click", () => {
      store.page += 1;
      renderList();
    });
    el("soundsPageLast")?.addEventListener("click", () => {
      store.page = pageCount(filteredSounds().length);
      renderList();
    });
    el("soundsHelpBtn")?.addEventListener("click", () => {
      alert(t("sounds.helpBody"));
    });

    window.addEventListener("lm:locale-change", () => renderList());
  }

  async function onPageShow() {
    stopPreview();
    if (createBtnEl) createBtnEl.hidden = !canWrite();
    document.body.classList.toggle("auth-readonly-sounds", !canWrite());
    try {
      await refreshSounds();
      renderList();
    } catch (e) {
      if (emptyEl) {
        emptyEl.hidden = false;
        emptyEl.textContent = e.message;
      }
      if (listEl) listEl.hidden = true;
    }
  }

  function onPageHide() {
    stopPreview();
    dialogEl?.close();
    deleteConfirmDialogEl?.close();
  }

  function getSounds() {
    return JSON.parse(JSON.stringify(store.sounds));
  }

  bindEvents();

  window.SoundsApp = {
    onPageShow,
    onPageHide,
    getSounds,
    refreshSounds,
  };
})();
