/* ----------- PppDialog Class 定義 ----------- */
/*
* ・コンストラクタ PppDialog(prefix = "")
*     prefix: CSSクラス接頭語
*
* ・メソッド open(options) : Promise
*     options: {
*       title: ダイアログタイトル文字列
*       bodyNode: ダイアログ本文に挿入するHTMLElement（bodyHtmlと排他）
*       bodyHtml: ダイアログ本文に挿入するHTML文字列（bodyNodeと排他）
*       buttons: [{ label: ボタン表示文字列, action: 戻り値の action, value: 戻り値の value（省略可） }, ...]
*       exposeAs: ダイアログインスタンスを window に公開する場合の名前（省略可）
*       width: ダイアログ幅（例: "400px", "50vw"）（省略可）
*       height: ダイアログ高さ（例: "300px", "50vh"）（省略可）
*       noButtons: true の場合、ボタンエリアを表示しない（省略可）
*     }
*     戻り値: Promise（resolve({ action: string, value: any })）
*
* ・メソッド close(result)
*     result: { action: string, value: any }（省略時は { action: "close", value: null }）
*     ダイアログを閉じ、open() の Promise を解決する。
*
*/

class PppDialog {
  static openDialogs = [];
  static zBase = 50000;

  constructor(prefix = "") {
    this.dialog = null;
    this._resolve = null;
    this._reject = null;
    this._moved = null;
    this._exposedName = null;
    this._prevfix = prefix;
  }

  close(result = { action: "close", value: null }) {
    if (!this.dialog) return;
    try {
      this.dialog.close(JSON.stringify(result));
    } catch (_) {
      this._finalizeClose(result);
    }
  }

  _finalizeClose(result) {
    if (this._moved) {
      const { el, parent, next } = this._moved;
      if (next && next.parentNode === parent) parent.insertBefore(el, next);
      else parent.appendChild(el);
    }
    this._moved = null;

    if (this.dialog && this.dialog.parentNode)
      this.dialog.parentNode.removeChild(this.dialog);

    if (this._exposedName && window[this._exposedName] === this)
      delete window[this._exposedName];
    this._exposedName = null;

    const idx = PppDialog.openDialogs.indexOf(this);
    if (idx >= 0) PppDialog.openDialogs.splice(idx, 1);

    if (this._resolve) {
      this._resolve(result);
      this._resolve = null;
      this._reject = null;
    }
  }

  async open({
    title = "",
    bodyNode = null,
    bodyHtml = "",
    buttons = [],
    exposeAs = null,
    width = null,
    height = null,
    noButtons = false
  }) {

    const dlg = document.createElement("dialog");
    dlg.className = this._prevfix + "dlg_dialog";
    dlg.style.position = "fixed";
    dlg.style.zIndex = PppDialog.zBase + PppDialog.openDialogs.length * 5;

    // --- width / height 指定 ---
    if (width) dlg.style.width = width;
    if (height) dlg.style.height = height;

    dlg.style.maxWidth = "95vw";
    dlg.style.maxHeight = "95vh";

    dlg.addEventListener("cancel", (e) => e.preventDefault());

    dlg.addEventListener("click", (e) => {
      const r = dlg.getBoundingClientRect();
      const inside = e.clientX >= r.left && e.clientX <= r.right &&
        e.clientY >= r.top && e.clientY <= r.bottom;
      if (!inside) { e.preventDefault(); e.stopPropagation(); }
    });

    const wrapper = document.createElement("div");
    wrapper.className = this._prevfix + "dlg_wrapper";

    const header = document.createElement("div");
    header.className = this._prevfix + "dlg_header";
    if (title) {
      const t = document.createElement("div");
      t.className = this._prevfix + "dlg_title";
      t.textContent = title;
      header.appendChild(t);
    }

    const body = document.createElement("div");
    body.className = this._prevfix + "dlg_body";
    body.style.overflow = "auto";

    if (height) {
      body.style.height = "calc(" + height + " - 100px)";
    } else {
      body.style.maxHeight = "80vh";
    }

    if (bodyNode) body.appendChild(bodyNode);
    else body.innerHTML = bodyHtml;

    const footer = document.createElement("div");
    footer.className = this._prevfix + "dlg_footer";

    const btnElements = [];
    if (!noButtons) {
      for (const btn of buttons) {
        const b = document.createElement("button");
        b.className = this._prevfix + "dlg_btn";
        b.textContent = btn.label;
        b.dataset.dlg_action = btn.action;
        b.addEventListener("click", () => {
          this.close({ action: btn.action, value: btn.value ?? null });
        });
        if (btn.addClass) {
          b.classList.add(btn.addClass);
        }
        if (btn.style) {
          b.style.cssText = btn.style;
        }

        footer.appendChild(b);
        btnElements.push(b);
      }
    }
    if (title) {
      wrapper.appendChild(header);
    }
    wrapper.appendChild(body);
    if (buttons.length && !noButtons) wrapper.appendChild(footer);

    dlg.appendChild(wrapper);
    document.body.appendChild(dlg);
    this.dialog = dlg;

    if (exposeAs) {
      this._exposedName = exposeAs;
      window[exposeAs] = this;
    }

    dlg.addEventListener("close", () => {
      let result;
      try { result = JSON.parse(dlg.returnValue); }
      catch (_) { result = { action: "close", value: null }; }
      this._finalizeClose(result);
    });

    PppDialog.openDialogs.push(this);
    dlg.showModal();

    if (btnElements.length)
      setTimeout(() => btnElements[0].focus(), 0);

    return new Promise((resolve, reject) => {
      this._resolve = resolve;
      this._reject = reject;
    });
  }

  //簡易表示
  showDialog(message = null, title = null, buttonText = "閉じる", cancelText = null) {
    if (cancelText === null) {
      return this.openAlert({ title: title, message: message, buttonText: buttonText });
    } else {
      return this.openConfirm({ title: title, message: message, okText: buttonText, cancelText: cancelText });
    }
  }

  // OKボタンのみのダイアログを表示する(opts[title,message,buttonText,exposeAs,width,height])
  openAlert(opts) {
    return this.open({
      title: opts.title ?? "",
      bodyHtml: `<div class="${this._prevfix}dlg_message">${opts.message ?? ""}</div>`,
      buttons: [{ label: opts.buttonText ?? "閉じる", action: "close", addClass: this._prevfix + "dlg_btn_close" }],
      exposeAs: opts.exposeAs ?? null,
      width: opts.width ?? null,
      height: opts.height ?? null,
      noButtons: opts.noButtons ?? false
    });
  }

  // OK/キャンセルボタンのダイアログを表示する(opts[title,message,okText,cancelText,exposeAs,width,height]) resultにはaction: "ok" or "cancel"が返る
  openConfirm(opts) {
    return this.open({
      title: opts.title ?? "",
      bodyHtml: `<div class="${this._prevfix}dlg_message">${opts.message ?? ""}</div>`,
      buttons: [
        { label: opts.okText ?? "OK", action: "ok", addClass: this._prevfix + "dlg_btn_ok" },
        { label: opts.cancelText ?? "キャンセル", action: "cancel", addClass: this._prevfix + "dlg_btn_cancel" }
      ],
      exposeAs: opts.exposeAs ?? null,
      width: opts.width ?? null,
      height: opts.height ?? null
    });
  }

  // HTML文字列を本文に持つダイアログを表示する(opts[title,html,exposeAs,width,height])
  openHTML(opts) {
    return this.open({
      title: opts.title ?? "",
      bodyHtml: opts.html ?? "",
      buttons: [],
      exposeAs: opts.exposeAs ?? null,
      width: opts.width ?? null,
      height: opts.height ?? null
    });
  }

  // 既存のHTMLElementを本文に持つダイアログを表示する(opts[title,element,exposeAs,width,height])
  openElement(opts) {
    if (!(opts.element instanceof HTMLElement))
      return Promise.reject("element must be HTMLElement");

    this._moved = {
      el: opts.element,
      parent: opts.element.parentNode,
      next: opts.element.nextSibling
    };
    opts.element.remove();

    return this.open({
      title: opts.title ?? "",
      bodyNode: opts.element,
      buttons: [],
      exposeAs: opts.exposeAs ?? null,
      width: opts.width ?? null,
      height: opts.height ?? null
    });
  }

  // テキスト入力ダイアログを表示する(opts[title,message,defaultValue,placeholder,okText,cancelText,exposeAs,width,height,multiline])
  // resultにはaction: "ok" or "cancel", value: 入力値が返る
  async openPrompt(opts) {
    const inputId = "dlg_prompt_input_" + Date.now();
    const isMultiline = opts.multiline ?? false;
    const defaultVal = (opts.defaultValue ?? "").replace(/"/g, "&quot;");
    const placeholderVal = (opts.placeholder ?? "").replace(/"/g, "&quot;");

    const inputElement = isMultiline
      ? `<textarea id="${inputId}" class="${this._prevfix}dlg_input ${this._prevfix}dlg_textarea" placeholder="${placeholderVal}" rows="4">${opts.defaultValue ?? ""}</textarea>`
      : `<input type="text" id="${inputId}" class="${this._prevfix}dlg_input" value="${defaultVal}" placeholder="${placeholderVal}">`;

    const bodyHtml = `
      <div class="${this._prevfix}dlg_prompt_container">
        ${opts.message ? `<div class="${this._prevfix}dlg_message">${opts.message}</div>` : ""}
        ${inputElement}
      </div>
    `;

    const self = this;
    let inputValue = opts.defaultValue ?? "";

    // カスタムボタンハンドラを使用して、閉じる前に入力値を取得
    const promise = new Promise((resolve, reject) => {
      this.open({
        title: opts.title ?? "",
        bodyHtml: bodyHtml,
        buttons: [],
        exposeAs: opts.exposeAs ?? null,
        width: opts.width ?? "400px",
        height: opts.height ?? null,
        noButtons: true
      }).then(() => {
        // ダイアログが閉じられた後に呼ばれる（backdrop clickなど）
        resolve({ action: "cancel", value: null });
      });

      // ダイアログが開いた後にボタンを追加
      setTimeout(() => {
        const footer = document.createElement("div");
        footer.className = this._prevfix + "dlg_footer";

        const okBtn = document.createElement("button");
        okBtn.className = this._prevfix + "dlg_btn " + this._prevfix + "dlg_btn_ok";
        okBtn.textContent = opts.okText ?? "OK";
        okBtn.addEventListener("click", () => {
          const input = document.getElementById(inputId);
          inputValue = input ? input.value : "";
          this.close({ action: "ok", value: inputValue });
          resolve({ action: "ok", value: inputValue });
        });

        const cancelBtn = document.createElement("button");
        cancelBtn.className = this._prevfix + "dlg_btn " + this._prevfix + "dlg_btn_cancel";
        cancelBtn.textContent = opts.cancelText ?? "キャンセル";
        cancelBtn.addEventListener("click", () => {
          this.close({ action: "cancel", value: null });
          resolve({ action: "cancel", value: null });
        });

        footer.appendChild(okBtn);
        footer.appendChild(cancelBtn);

        const wrapper = this.dialog.querySelector("." + this._prevfix + "dlg_wrapper");
        if (wrapper) wrapper.appendChild(footer);

        // 入力欄にフォーカス
        const input = document.getElementById(inputId);
        if (input) {
          input.focus();
          if (!isMultiline) input.select();
        }

        // Enterキーで確定
        if (input) {
          input.addEventListener("keydown", (e) => {
            if (e.key === "Enter" && !isMultiline) {
              e.preventDefault();
              okBtn.click();
            }
          });
        }
      }, 10);
    });

    return promise;
  }
}