ui/dialogs.js

import { events } from "../core/events.js";
import { store } from "../core/store.js";

// ── Helpers ───────────────────────────────────────────────────────────────────

function getDialog(id) {
  return /** @type {HTMLDialogElement} */ (document.getElementById(id));
}

function closeOnBackdrop(dialog) {
  dialog.addEventListener("click", (e) => {
    if (e.target === dialog) dialog.close();
  });
}

function wireCloseButtons(dialog) {
  dialog.querySelectorAll("[data-close]").forEach((btn) => {
    btn.addEventListener("click", () => dialog.close());
  });
}

// ── Menu dialog ───────────────────────────────────────────────────────────────

/**
 * Show the main menu. Returns a Promise that resolves with the chosen action.
 * Possible resolved values: "new-game" | "load-game" | "help" | null (dismissed)
 * @returns {Promise<string|null>}
 */
export async function showMenu() {
  const hasSave = await store.hasSave();
  return new Promise((resolve) => {
    const dialog = getDialog("menu-dialog");

    // Dim "Load Game" when there's nothing to load
    const loadItem = dialog.querySelector('[data-action="load-game"]');
    if (loadItem) {
      loadItem.style.opacity = hasSave ? "" : "0.4";
      loadItem.style.pointerEvents = hasSave ? "" : "none";
    }

    const onAction = (e) => {
      const li = e.target.closest("[data-action]");
      if (!li) return;
      dialog.close();
      dialog.removeEventListener("click", onAction);
      resolve(li.dataset.action);
    };

    dialog.addEventListener("click", onAction);
    dialog.addEventListener(
      "close",
      () => {
        dialog.removeEventListener("click", onAction);
        resolve(null);
      },
      { once: true },
    );

    dialog.showModal();
  });
}

// ── Scenario dialog ───────────────────────────────────────────────────────────

/**
 * Show scenario selection list.
 * @param {Array<{id:string, start:string, name:string, description:string, creator:string}>} scenarios
 * @returns {Promise<{id:string, start:string}|null>}
 */
export function showScenarioDialog(scenarios) {
  return new Promise((resolve) => {
    const dialog = getDialog("scenario-dialog");
    const list = document.getElementById("scenario-list");
    list.innerHTML = "";

    for (const s of scenarios) {
      const li = document.createElement("li");
      li.dataset.id = s.id;
      li.innerHTML = `
        <span class="s-name">${s.name}</span>
        <span class="s-meta">by ${s.creator || "unknown"}</span>
        <span class="s-desc">${s.description || ""}</span>
      `;
      li.addEventListener("click", () => {
        dialog.close();
        resolve(s);
      });
      list.appendChild(li);
    }

    wireCloseButtons(dialog);
    dialog.addEventListener("close", () => resolve(null), { once: true });
    dialog.showModal();
  });
}

// ── Save / load dialog ────────────────────────────────────────────────────────

/**
 * Show save dialog. The `onSave` callback receives the chosen slot name.
 * @param {function(string): void} onSave
 */
export async function showSaveDialog(onSave) {
  const existingSaves = await store.listSaves();

  const dialog = getDialog("saveload-dialog");
  const title = document.getElementById("saveload-title");
  const confirmBtn = document.getElementById("saveload-confirm");
  const deleteBtn = /** @type {HTMLButtonElement} */ (
    document.getElementById("saveload-delete")
  );
  const nameWrap = document.getElementById("save-name-wrap");
  const nameInput = /** @type {HTMLInputElement} */ (
    document.getElementById("save-name-input")
  );
  const slotList = document.getElementById("slot-list");

  if (title) title.textContent = "Save Game";
  if (nameWrap) nameWrap.style.display = "";
  if (confirmBtn) confirmBtn.textContent = "Save";
  if (nameInput) nameInput.value = "";
  if (deleteBtn) deleteBtn.disabled = true;

  let selectedSlotName = null;

  // Populate existing saves so the player can click one to overwrite
  slotList.innerHTML = "";
  for (const save of existingSaves) {
    const li = document.createElement("li");
    const date = new Date(save.savedAt).toLocaleString();
    li.innerHTML = `<span>${save.slotName}</span><span style="opacity:0.6;font-size:0.75em">${date}</span>`;
    li.addEventListener("click", () => {
      slotList
        .querySelectorAll("li")
        .forEach((el) => el.classList.remove("selected"));
      li.classList.add("selected");
      if (nameInput) nameInput.value = save.slotName;
      selectedSlotName = save.slotName;
      if (deleteBtn) deleteBtn.disabled = false;
    });
    slotList.appendChild(li);
  }

  wireCloseButtons(dialog);

  const deleteHandler = async () => {
    if (!selectedSlotName) return;
    await store.delete(selectedSlotName);
    selectedSlotName = null;
    if (deleteBtn) deleteBtn.disabled = true;
    if (nameInput) nameInput.value = "";
    // Remove the deleted entry from the list
    slotList.querySelectorAll("li.selected").forEach((el) => el.remove());
  };
  if (deleteBtn) deleteBtn.addEventListener("click", deleteHandler);

  const handler = () => {
    const name = nameInput?.value.trim() || "My Save";
    dialog.close();
    onSave(name);
  };
  confirmBtn.addEventListener("click", handler, { once: true });
  dialog.addEventListener(
    "close",
    () => {
      confirmBtn.removeEventListener("click", handler);
      if (deleteBtn) deleteBtn.removeEventListener("click", deleteHandler);
    },
    { once: true },
  );

  dialog.showModal();
  nameInput?.focus();
}

/**
 * Show load dialog with a list of saves to choose from.
 * The `onLoad` callback receives the chosen slot name.
 * @param {function(string): void} onLoad
 */
export async function showLoadDialog() {
  const saves = await store.listSaves();
  if (saves.length === 0) {
    showConfirm("No saved games found.", () => {}, { okOnly: true });
    return null;
  }

  const dialog = getDialog("saveload-dialog");
  const title = document.getElementById("saveload-title");
  const confirmBtn = document.getElementById("saveload-confirm");
  const deleteBtn = /** @type {HTMLButtonElement} */ (
    document.getElementById("saveload-delete")
  );
  const nameWrap = document.getElementById("save-name-wrap");
  const slotList = document.getElementById("slot-list");

  if (title) title.textContent = "Load Game";
  if (nameWrap) nameWrap.style.display = "none";
  if (confirmBtn) {
    confirmBtn.textContent = "Load";
    confirmBtn.disabled = true;
  }
  if (deleteBtn) deleteBtn.disabled = true;

  let selectedSlot = null;
  let _resolve;

  const resolveWith = (value) => {
    if (_resolve) {
      const r = _resolve;
      _resolve = null;
      r(value);
    }
  };

  function renderList(saveList) {
    slotList.innerHTML = "";
    for (const save of saveList) {
      const li = document.createElement("li");
      const date = new Date(save.savedAt).toLocaleString();
      li.innerHTML = `<span>${save.slotName}</span><span style="opacity:0.6;font-size:0.75em">${date}</span>`;
      if (save.slotName === selectedSlot) li.classList.add("selected");

      li.addEventListener("click", () => {
        slotList
          .querySelectorAll("li")
          .forEach((el) => el.classList.remove("selected"));
        li.classList.add("selected");
        selectedSlot = save.slotName;
        if (deleteBtn) deleteBtn.disabled = false;
        if (confirmBtn) confirmBtn.disabled = false;
      });
      li.addEventListener("dblclick", () => {
        resolveWith(save.slotName);
        dialog.close();
      });
      slotList.appendChild(li);
    }
  }

  // No pre-selection — buttons stay disabled until user clicks a row
  renderList(saves);

  wireCloseButtons(dialog);

  const deleteHandler = async () => {
    if (!selectedSlot) return;
    await store.delete(selectedSlot);
    const remaining = await store.listSaves();
    if (remaining.length === 0) {
      dialog.close();
      return;
    }
    selectedSlot = remaining[0].slotName;
    if (deleteBtn) deleteBtn.disabled = true;
    renderList(remaining);
  };
  if (deleteBtn) deleteBtn.addEventListener("click", deleteHandler);

  const handler = () => {
    if (selectedSlot) {
      resolveWith(selectedSlot);
      dialog.close();
    }
  };
  confirmBtn.addEventListener("click", handler, { once: true });
  return new Promise((resolve) => {
    _resolve = resolve;
    dialog.addEventListener(
      "close",
      () => {
        confirmBtn.removeEventListener("click", handler);
        if (deleteBtn) deleteBtn.removeEventListener("click", deleteHandler);
        resolveWith(null);
      },
      { once: true },
    );

    dialog.showModal();
  });
}

// ── Confirm dialog ────────────────────────────────────────────────────────────

/**
 * Show a yes/no confirmation dialog.
 * @param {string} message
 * @param {function(): void} onConfirm
 * @param {{okOnly: boolean}} [opts]
 */
export function showConfirm(message, onConfirm, opts = {}) {
  const dialog = getDialog("confirm-dialog");
  const msgEl = document.getElementById("confirm-message");
  const yesBtn = document.getElementById("confirm-yes");
  const noBtn = document.getElementById("confirm-no");

  if (msgEl) msgEl.innerHTML = message;
  if (noBtn) noBtn.style.display = opts.okOnly ? "none" : "";
  if (yesBtn) yesBtn.textContent = opts.okOnly ? "OK" : "Yes";

  const onYes = () => {
    dialog.close();
    onConfirm();
  };

  const onCancel = opts.preventCancel ? (e) => e.preventDefault() : null;
  if (onCancel) dialog.addEventListener("cancel", onCancel);

  yesBtn.addEventListener("click", onYes, { once: true });
  noBtn.addEventListener("click", () => dialog.close(), { once: true });
  dialog.addEventListener(
    "close",
    () => {
      yesBtn.removeEventListener("click", onYes);
      if (onCancel) dialog.removeEventListener("cancel", onCancel);
    },
    { once: true },
  );

  dialog.showModal();
}

// ── Win dialog ────────────────────────────────────────────────────────────────

/**
 * Show the win screen with an iframe pointing to the win URL.
 * @param {string} url  relative or absolute URL for the win HTML page
 * @param {function(): void} [onClose]
 */
export function showWinDialog(url, onClose) {
  const dialog = getDialog("win-dialog");
  const frame = document.getElementById("win-frame");
  if (frame) frame.src = url;

  wireCloseButtons(dialog);
  if (onClose) {
    dialog.addEventListener("close", onClose, { once: true });
  }
  dialog.showModal();
}

// ── Modal message ─────────────────────────────────────────────────────────────

/**
 * Show a short modal message (used for win conditions, key events, etc.).
 * If the message looks like a URL (.html extension), shows the win dialog.
 * Otherwise shows a confirm dialog with "OK" only.
 * @param {string} message
 * @param {function(): void} [onClose]
 */
export function showModalMessage(message, onClose) {
  if (message && message.endsWith(".html")) {
    showWinDialog(message, onClose);
  } else {
    showConfirm(message, () => {}, { okOnly: true });
  }
}

// ── Help dialog ───────────────────────────────────────────────────────────────

export function showHelp() {
  const dialog = getDialog("help-dialog");
  wireCloseButtons(dialog);
  closeOnBackdrop(dialog);
  return new Promise((resolve) => {
    dialog.addEventListener("close", () => resolve(), { once: true });
    dialog.showModal();
  });
}

// ── Wiring ────────────────────────────────────────────────────────────────────

/**
 * Connect the events bus to the modal-message dialog.
 * Call once during app init.
 * @param {function(): void} [onWinClose]  called when win dialog is closed
 */
export function initDialogs(onWinClose) {
  events.onModalMessage((msg) => {
    // Win conditions still use the full dialog
    if (msg && msg.endsWith(".html")) {
      showWinDialog(msg, onWinClose);
    }
    // All other modal messages are handled as positional popups in popup-messages.js
  });

  // Close dialogs on ESC is handled natively by <dialog>
}