import type { LGraphNode, LGraphNodeConstructor } from "typings/litegraph.js";
import { createElement as $el, getClosestOrSelf, setAttributes } from "./utils_dom.js";

type RgthreeDialogButton = {
  label: string;
  className?: string;
  closes?: boolean;
  disabled?: boolean;
  callback?: (e: PointerEvent | MouseEvent) => void;
};

export type RgthreeDialogOptions = {
  content: string | HTMLElement | HTMLElement[];
  class?: string | string[];
  title?: string | HTMLElement | HTMLElement[];
  closeX?: boolean;
  closeOnEsc?: boolean;
  closeOnModalClick?: boolean;
  closeButtonLabel?: string | boolean;
  buttons?: RgthreeDialogButton[];
  onBeforeClose?: () => Promise<boolean> | boolean;
};

/**
 * A Dialog that shows content, and closes.
 */
export class RgthreeDialog extends EventTarget {
  element: HTMLDialogElement;
  contentElement: HTMLDivElement;
  titleElement: HTMLDivElement;
  options: RgthreeDialogOptions;

  constructor(options: RgthreeDialogOptions) {
    super();
    this.options = options;
    let container = $el("div.rgthree-dialog-container");
    this.element = $el("dialog", {
      classes: ["rgthree-dialog", options.class || ""],
      child: container,
      parent: document.body,
      events: {
        click: (event: MouseEvent) => {
          // Close the dialog if we've clicked outside of our container. The dialog modal will
          // report itself as the dialog itself, so we use the inner container div (and CSS to
          // remove default padding from the dialog element).
          if (
            !this.element.open ||
            event.target === container ||
            getClosestOrSelf(event.target, `.rgthree-dialog-container`) === container
          ) {
            return;
          }
          return this.close();
        },
      },
    });
    this.element.addEventListener("close", (event) => {
      this.onDialogElementClose();
    });

    this.titleElement = $el("div.rgthree-dialog-container-title", {
      parent: container,
      children: !options.title
        ? null
        : options.title instanceof Element || Array.isArray(options.title)
        ? options.title
        : typeof options.title === "string"
        ? !options.title.includes("<h2")
          ? $el("h2", { html: options.title })
          : options.title
        : options.title,
    });

    this.contentElement = $el("div.rgthree-dialog-container-content", {
      parent: container,
      child: options.content,
    });

    const footerEl = $el("footer.rgthree-dialog-container-footer", { parent: container });
    for (const button of options.buttons || []) {
      $el("button", {
        text: button.label,
        className: button.className,
        disabled: !!button.disabled,
        parent: footerEl,
        events: {
          click: (e: MouseEvent) => {
            button.callback?.(e);
          },
        },
      });
    }

    if (options.closeButtonLabel !== false) {
      $el("button", {
        text: options.closeButtonLabel || "Close",
        className: "rgthree-button",
        parent: footerEl,
        events: {
          click: (e: MouseEvent) => {
            this.close(e);
          },
        },
      });
    }
  }

  setTitle(content: string | HTMLElement | HTMLElement[]) {
    const title =
      typeof content !== "string" || content.includes("<h2")
        ? content
        : $el("h2", { html: content });
    setAttributes(this.titleElement, { children: title });
  }

  setContent(content: string | HTMLElement | HTMLElement[]) {
    setAttributes(this.contentElement, { children: content });
  }

  show() {
    document.body.classList.add("rgthree-dialog-open");
    this.element.showModal();
    this.dispatchEvent(new CustomEvent("show"));
    return this;
  }

  async close(e?: MouseEvent | PointerEvent) {
    if (this.options.onBeforeClose && !(await this.options.onBeforeClose())) {
      return;
    }
    this.element.close();
  }

  onDialogElementClose() {
    document.body.classList.remove("rgthree-dialog-open");
    this.element.remove();
    this.dispatchEvent(new CustomEvent("close", this.getCloseEventDetail()));
  }

  protected getCloseEventDetail(): { detail: any } {
    return { detail: null };
  }
}

/**
 * A help extension for the dialog class that standardizes help content.
 */
export class RgthreeHelpDialog extends RgthreeDialog {
  constructor(
    node: LGraphNode | LGraphNodeConstructor,
    content: string,
    opts: Partial<RgthreeDialogOptions> = {},
  ) {
    const title = (node.type || node.title || "").replace(
      /\s*\(rgthree\).*/,
      " <small>by rgthree</small>",
    );
    const options = Object.assign({}, opts, {
      class: "-iconed -help",
      title,
      content,
    });
    super(options);
  }
}