import { html, nothing, PropertyValues, TemplateResult } from "lit";
import { property, query, queryAssignedElements, queryAssignedNodes, state } from "lit/decorators.js";
import { ClassInfo, classMap } from "lit/directives/class-map.js";
import { UiElement } from "../UiElement.js";
import { isDisabled, setDisabled } from "../lib/disabled.js";
import UiButton from "../button/UiButton.js";
import '../../define/ui/ui-button.js'

export interface UiDialogClosingReason {
  /**
   * Whether the dialog was cancelled by either activating the `dismiss` button
   * or by pressing escape.
   */
  cancelled: boolean;
  /**
   * This is used in cases when the dialog has more complex purpose.
   * This is the value expected from the dialog.
   */
  value?: unknown;
}

/**
 * Styled dialog using a native `<dialog>` element under the hood.
 * Note, since native dialog renders in the top layer it is not necessary 
 * to place the dialog in the `<body>`.
 * 
 * ## Using buttons
 * 
 * The dialog automatically recognizes buttons with values `confirm` and `dismiss` 
 * to close the dialog and dispatch the `close` event. The event has additional 
 * closing reason detail.
 * 
 * ```javascript
 * <ui-button value="dismiss">Cancel</ui-button> 
 * <ui-button value="confirm">Take action</ui-button> 
 * ```
 * 
 * ```javascript
 * <button value="dismiss">Cancel</button> 
 * <button value="confirm">Take action</button> 
 * ```
 * 
 * The detail object of the `close` event has the following properties:
 * - cancelled - Whether the dialog was cancelled by either activating the `dismiss` button or by pressing escape.
 * 
 * The `close` event is only dispatched when the user interact with the dialog. Imperative control of the 
 * dialog won't trigger the close button. 
 * 
 * ## Full example
 * 
 * ```javascript
 * <ui-dialog modal>
 *  <ui-icon slot="icon" icon="delete"></ui-icon>
 * 
 *  <span slot="title">Delete photos?</span>
 *  <p>This action will permanently remove the selected pictures from your account.</p>
 * 
 *  <ui-button slot="button" value="dismiss" type="text">Cancel</ui-button>
 *  <ui-button slot="button" value="confirm" type="text">Confirm</ui-button>
 * </ui-dialog>
 * ```
 * 
 * @slot - The slot for the content of the dialog.
 * @slot icon - The slot to place the dialog icon
 * @slot title - The slot to place the dialog title. Do not put elements here, just the text.
 * @slot button - The slot to place the dialog buttons. Use the `confirm` or `dismiss` buttons to automatically close the dialog.
 * @fires close - A non-bubbling, non-cancellable event with the `UiDialogClosingReason` as the detail.
 */
export default class UiDialog extends UiElement {
  get disabled(): boolean {
    return isDisabled(this);
  }

  /**
   * When set, the button is a disabled state.
   * @attribute
   */
  @property({ reflect: true, type: Boolean })
  set disabled(value: boolean) {
    const old = isDisabled(this);
    setDisabled(this, value);
    this.requestUpdate('disabled', old);
  }

  /**
   * Opens the dialog as modal when toggling dialog's open state.
   * 
   * Setting this value after the dialog was opened has no effect.
   * 
   * @attribute
   */
  @property({ type: Boolean }) modal = false;

  /**
   * Toggles visibility of the dialog.
   * Note, the dialog is opened asynchronously after the update is performed.
   * @attribute
   */
  @property({ type: Boolean }) open = false;

  /**
   * Imperative access to create a dismiss button.
   * When set this will render a dismiss button at the end of the buttons line, before the `confirmLabel` button.
   * @attribute
   */
  @property({ type: String }) dismissLabel?: string;

  /**
   * Imperative access to create a confirm button.
   * When set this will render a confirm button at the end of the buttons line, after the `dismissLabel` button.
   * @attribute
   */
  @property({ type: String }) confirmLabel?: string;

  /**
   * A reference to the underlying dialog element.
   */
  @query('dialog') readonly dialog!: HTMLDialogElement;

  @state() protected hasIcon = false;

  @state() protected hasTitle = false;

  @state() protected hasButton = false;

  @queryAssignedElements({ flatten: true, slot: 'icon' }) protected readonly icons!: HTMLElement[];

  @queryAssignedNodes({ flatten: true, slot: 'title' }) protected readonly titles!: HTMLElement[];

  @queryAssignedElements({ flatten: true, slot: 'button' }) protected readonly buttons!: HTMLElement[];

  /**
   * To be set by a child class when closing the dialog.
   * This is passed to the close event.
   */
  dialogValue?: unknown;

  constructor() {
    super();

    this.addEventListener('click', this.handleClick);
    this.addEventListener('keydown', this.handleKeyDown);
  }

  override handleClick(e: MouseEvent): void {
    super.handleClick(e);
    const path = e.composedPath();
    const { buttons } = this;
    const button = path.find(i => buttons.includes(i as HTMLElement)) as HTMLButtonElement | UiButton | undefined;
    if (!button) {
      return;
    }
    const { value ='' } = button;
    this.handleInteraction(value as 'dismiss' | 'confirm');
  }

  override handleKeyDown(e: KeyboardEvent): void {
    super.handleKeyDown(e);
    if (e.key === 'Escape') {
      this.handleInteraction('dismiss');
    }
  }

  protected override updated(changedProperties: PropertyValues<this>): void {
    if (changedProperties.has('open')) {
      this.controlVisibility();
    }
    super.updated(changedProperties);
  }

  protected controlVisibility(): void {
    const { dialog, modal, open } = this;
    if (!dialog) {
      return;
    }
    if (open) {
      if (modal) {
        dialog.showModal();
      } else {
        dialog.show();
      }
    } else {
      dialog.close();
    }
  }

  protected handleSlotChange(): void {
    const { icons, titles, buttons } = this;
    this.hasIcon = !!icons && !!icons.length;
    this.hasTitle = !!titles && !!titles.length;
    this.hasButton = !!buttons && !!buttons.length;
  }

  protected handleInteraction(value: 'dismiss' | 'confirm'): void {
    if (!['dismiss', 'confirm'].includes(value)) {
      return;
    }
    this.open = false;
    const detail: UiDialogClosingReason = {
      cancelled: value === 'dismiss',
    }
    if (this.dialogValue !== undefined) {
      detail.value = this.dialogValue;
    }
    this.dispatchEvent(new CustomEvent<UiDialogClosingReason>('close', {
      composed: true,
      detail,
    }));
  }

  protected handleDialogClose(): void {
    if (!this.open) {
      return;
    }
    this.open = false;
    this.handleInteraction('dismiss');
  }

  protected handleDismiss(): void {
    this.handleInteraction('dismiss');
  }

  protected handleConfirm(): void {
    this.handleInteraction('confirm');
  }

  override render(): TemplateResult {
    return html`
    <dialog @close="${this.handleDialogClose}">
      ${this.renderIcon()}
      ${this.renderTitle()}
      ${this.renderBody()}
      ${this.renderButtons()}
    </dialog>
    `;
  }

  protected renderIcon(): TemplateResult {
    const classes: ClassInfo = {
      icon: true,
      'with-icon': this.hasIcon,
    };
    return html`
    <div class="${classMap(classes)}" part="icon"><slot name="icon" @slotchange="${this.handleSlotChange}"></slot></div>
    `;
  }

  protected renderTitle(): TemplateResult {
    const classes: ClassInfo = {
      title: true,
      'with-title': this.hasTitle,
    };
    return html`
    <div class="${classMap(classes)}" part="title"><slot name="title" @slotchange="${this.handleSlotChange}"></slot></div>
    `;
  }

  protected renderBody(): TemplateResult {
    return html`
    <div class="content"><slot></slot></div>
    `;
  }

  protected renderButtons(): TemplateResult {
    const classes: ClassInfo = {
      buttons: true,
      'with-buttons': this.hasButton || !!this.confirmLabel || !!this.dismissLabel,
    };
    return html`
    <div class="${classMap(classes)}" part="button">
      <slot name="button" @slotchange="${this.handleSlotChange}"></slot>
      ${this.renderDismissButton()}
      ${this.renderConfirmButton()}
    </div>
    `;
  }

  protected renderDismissButton(): TemplateResult | typeof nothing {
    const { dismissLabel } = this;
    if (!dismissLabel) {
      return nothing;
    }
    return html`
     <ui-button value="dismiss" type="text" class="internal-button" @click="${this.handleDismiss}">${dismissLabel}</ui-button>
    `;
  }

  protected renderConfirmButton(): TemplateResult | typeof nothing {
    const { confirmLabel } = this;
    if (!confirmLabel) {
      return nothing;
    }
    return html`
     <ui-button value="confirm" type="text" class="internal-button" @click="${this.handleConfirm}">${confirmLabel}</ui-button>
    `;
  }
}
