import { html, TemplateResult } from "lit";
import { property, queryAsync, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { when } from 'lit/directives/when.js';
import { BeginPressConfig, EndPressConfig } from "../controllers/ActionController.js";
import { Button } from "./Button.js";
import { ripple } from '../effects/rippleDirective.js';
import UiRipple from "../effects/ripple.js";
import '../../define/ui/ui-ripple.js';

/**
 * The type of the button rendered.
 */
export enum ButtonType {
  'elevated' = 'elevated',
  'filled' = 'filled',
  'tonal' = 'tonal',
  'outlined' = 'outlined',
  'text' = 'text',
}

/**
 * A material design button.
 * 
 * Note, this control is form associated.
 * 
 * @slot icon - A slot for the icon element
 * @slot - The default slot for the label
 */
export default class UiButton extends Button {
  static get formAssociated(): boolean {
    return true;
  }

  #_internals = this.attachInternals();

  get form(): HTMLFormElement | null {
    return this.#_internals && this.#_internals.form;
  }

  /**
   * The type of the rendered button according to Material 3 spec.
   * 
   * @default {ButtonType.filled}
   * @attribute
   */
  @property({ type: String, reflect: true, }) type: ButtonType;

  /**
   * Determines when the element has an icon in the "icon" slot.
   */
  @state() protected _hasIcon: boolean;

  /**
   * The name of the button, submitted as a pair with the button's value as part of the form data, 
   * when that button is used to submit the form.
   * @attribute
   */
  @property({ type: String }) name?: string;

  #_value?: string;

  /**
   * Defines the value associated with the button's name when it's submitted with the form data. 
   * This value is passed to the server in params when the form is submitted using this button.
   * @attribute
   */
  @property({ type: String })
  get value(): string | undefined {
    return this.#_value;
  }

  set value(value: string | undefined) {
    if (this.#_value === value) {
      return;
    }
    this.#_value = value;
    this.#_internals?.setFormValue(value || null);
  }

  /**
   * Whether to render the icon at the inline end of the label rather than the
   * inline start.
   * @attribute
   */
  @property({ type: Boolean, attribute: 'trailingicon', reflect: true }) trailingIcon = false;

  @queryAsync('ui-ripple') protected ripple!: Promise<UiRipple | null>;

  @state() protected showRipple = false;

  /**
   * Whether the button is a submit button for a form.
   * @attribute
   */
  @property({ type: Boolean, reflect: true, }) submit?: boolean;

  protected readonly getRipple = (): Promise<UiRipple | null> => {
    this.showRipple = true;
    return this.ripple;
  };

  constructor() {
    super();

    this.type = ButtonType.filled;
    this._hasIcon = false;
  }

  protected async pressRipple(): Promise<void> {
    const element = await this.getRipple();
    if (element && !element.isPressed) {
      element.beginPress();
    }
  }

  protected async endRipple(): Promise<void> {
    const element = await this.getRipple();
    element?.endPress();
  }

  override beginPress(options: BeginPressConfig): void {
    super.beginPress(options);
    this.pressRipple();
  }

  override endPress(config: EndPressConfig): void {
    super.endPress(config);
    this.endRipple();
    const { cancelled, reason } = config;
    if (cancelled) {
      return;
    }
    if (!reason || reason === 'enter' || reason === 'space') {
      this._submitHandler();
    }
    if (reason === 'enter' || reason === 'space') {
      this.click();
    }
  }

  protected _submitHandler(): void {
    const { form, name, value, submit, disabled } = this;
    if (!form || !submit || disabled) {
      return;
    }
    let button: HTMLButtonElement | undefined;
    if (name || value) {
      button = document.createElement('button');
      if (name) {
        button.name = name;
      }
      if (value) {
        button.value = value;
      }
      button.type = 'submit';
      button.hidden = true;
      form.append(button);
    }
    try {
      form.requestSubmit(button);
    } catch (_) {
      // ...
    }
    if (button) {
      form.removeChild(button);
    }
  }

  /**
   * Sets the `_hasIcon` state property when the "icon" slot change event is dispatched.
   */
  protected _iconSlotChange(e: Event): void {
    const slot = e.target as HTMLSlotElement;
    this._hasIcon = !!slot.assignedNodes().length;
  }

  protected override render(): TemplateResult {
    const { pressed = false, trailingIcon = false } = this;
    const containerClasses = classMap({
      surface: true,
      withIcon: this._hasIcon,
      pressed,
    });
    return html`
    <div class="${containerClasses}" ${ripple(this.getRipple)}>
      <div class="container"></div>
      <div class="state"></div>
      ${when(this.showRipple, this.renderRipple)}
      <div class="content">
        ${trailingIcon ? '' : this._iconTemplate()}
        <slot></slot>
        ${trailingIcon ? this._iconTemplate() : ''}
      </div>
    </div>
    `;
  }

  protected _iconTemplate(): TemplateResult {
    return html`<slot name="icon" @slotchange="${this._iconSlotChange}"></slot>`;
  }

  protected renderRipple = (): TemplateResult => {
    const { disabled } = this;
    return html`<ui-ripple class="ripple" ?disabled="${disabled}"></ui-ripple>`;
  };
}
