/**
 * @license
 * Copyright 2022 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import '@material/web/button/filled-button.js';
import '@material/web/button/text-button.js';

import {css, CSSResultGroup, html, LitElement} from 'lit';

// The padding on the label start/end when there is no icons.
const LABEL_PADDING_START_END = css`16px`;
// The padding on the icon start/end when present.
const ICON_PADDING_START_END = css`12px`;
// The inline gap between the label and the optional icons.
const ICON_GAP = css`8px`;
const CONTAINER_HEIGHT = css`36px`;
const ICON_SIZE = css`20px`;
const MIN_WIDTH = css`64px`;

/**
 * A chromeOS compliant button.
 */
export class Button extends LitElement {
  /** @nocollapse */
  static override shadowRootOptions:
      ShadowRootInit = {mode: 'open', delegatesFocus: true};

  // Note that theme colours have opacity defined in the colour, but default
  // colours have opacities set separately. As a consequence, styles are broken
  // unless a cros theme is present.
  /** @nocollapse */
  static override styles: CSSResultGroup = css`
    :host {
      display: inline-block;
      text-overflow: ellipsis;
      text-wrap: nowrap;
      width: fit-content;
    }

    .button {
      max-width: var(--cros-button-max-width,200px);
      min-width: ${MIN_WIDTH};
      text-overflow: inherit;
      text-wrap: inherit;
      width: 100%;
      height: 100%;
    }

    .label {
      overflow: hidden;
      text-overflow: inherit;
    }

    :host([overflow="stack"]) {
      text-wrap: wrap;
    }

    ::slotted(*) {
      display: inline-flex;
      block-size: ${ICON_SIZE};
      inline-size: ${ICON_SIZE};
    }

    .content-container {
      align-items: center;
      display: flex;
      gap: ${ICON_GAP};
    }

    md-filled-button:has(.content-container.has-leading-icon)  {
      --md-filled-button-leading-space: ${ICON_PADDING_START_END};
    }

    md-filled-button:has(.content-container.has-trailing-icon)  {
      --md-filled-button-trailing-space: ${ICON_PADDING_START_END};
    }

    md-text-button:has(.content-container.has-leading-icon)  {
      --md-text-button-leading-space: ${ICON_PADDING_START_END};
    }

    md-text-button:has(.content-container.has-trailing-icon)  {
      --md-text-button-trailing-space: ${ICON_PADDING_START_END};
    }

    md-filled-button {
      --md-filled-button-container-height: ${CONTAINER_HEIGHT};
      --md-filled-button-disabled-container-color: var(--cros-sys-disabled_container);
      --md-filled-button-disabled-container-opacity: 100%;
      --md-filled-button-disabled-label-text-color: var(--cros-sys-disabled);
      --md-filled-button-disabled-label-text-opacity: 100%;
      --md-filled-button-focus-state-layer-opacity: 100%;
      --md-filled-button-hover-container-elevation: 0;
      --md-filled-button-hover-state-layer-opacity: 100%;
      --md-filled-button-label-text-font: var(--cros-button-2-font-family);
      --md-filled-button-label-text-size: var(--cros-button-2-font-size);
      --md-filled-button-label-text-line-height: var(--cros-button-2-line-height);
      --md-filled-button-label-text-weight: var(--cros-button-2-font-weight);
      --md-filled-button-leading-space: ${LABEL_PADDING_START_END};
      --md-filled-button-pressed-state-layer-opacity: 100%;
      --md-filled-button-trailing-space: ${LABEL_PADDING_START_END};
      --md-focus-ring-duration: 0s;
      --md-focus-ring-width: 2px;
      --md-sys-color-secondary: var(--cros-sys-focus_ring);
    }

    :host(:not([button-style="secondary"]):is([inverted][disabled])) {
      opacity: var(--cros-disabled-opacity);
    }

    :host([inverted][button-style="primary"]) md-filled-button {
      /** Base styles */
      --md-sys-color-primary: var(--cros-sys-inverse_primary);
      --md-sys-color-secondary: var(--cros-sys-inverse_focus_ring);
      --md-sys-color-on-primary: var(--cros-sys-inverse_on_primary);
      --md-filled-button-label-text-color: var(--cros-sys-inverse_on_primary);
      /** Disabled */
      --md-filled-button-disabled-container-color: var(--cros-sys-inverse_primary);
      --md-filled-button-disabled-label-text-color: var(--cros-sys-inverse_on_primary);
      /** Hover */
      --md-filled-button-hover-state-layer-color: var(--cros-sys-inverse_hover_on_prominent);
      /** Pressed */
      --md-filled-button-pressed-state-layer-color: var(--cros-sys-inverse_ripple_primary);
    }

    :host([button-style="primary"]) md-filled-button {
      --md-sys-color-primary: var(--cros-sys-primary);
      --md-sys-color-on-primary: var(--cros-sys-on_primary);
      --md-filled-button-hover-state-layer-color: var(--cros-sys-hover_on_prominent);
      --md-filled-button-pressed-state-layer-color: var(--cros-sys-ripple_primary);
    }

    :host([button-style="secondary"]) md-filled-button {
      --md-filled-button-hover-state-layer-color: var(--cros-sys-hover_on_subtle);
      --md-filled-button-pressed-state-layer-color: var(--cros-sys-ripple_primary);
      --md-sys-color-primary: var(--cros-sys-primary_container);
      --md-sys-color-on-primary: var(--cros-sys-on_primary_container);
    }

    md-text-button {
      --md-sys-color-primary: var(--cros-sys-primary);
      --md-sys-color-secondary: var(--cros-sys-focus_ring);
      --md-focus-ring-duration: 0s;
      --md-focus-ring-width: 2px;
      --md-text-button-container-height: ${CONTAINER_HEIGHT};
      --md-text-button-disabled-label-text-color: var(--cros-sys-disabled);
      --md-text-button-disabled-label-text-opacity: 100%;
      --md-text-button-focus-state-layer-opacity: 100%;
      --md-text-button-hover-state-layer-color: var(--cros-sys-hover_on_subtle);
      --md-text-button-hover-state-layer-opacity: 100%;
      --md-text-button-label-text-color: var(--cros-sys-primary);
      --md-text-button-label-text-font: var(--cros-button-2-font-family);
      --md-text-button-label-text-size: var(--cros-button-2-font-size);
      --md-text-button-label-text-line-height: var(--cros-button-2-line-height);
      --md-text-button-label-text-weight: var(--cros-button-2-font-weight);
      --md-text-button-leading-space: ${LABEL_PADDING_START_END};
      --md-text-button-pressed-state-layer-color: var(--cros-sys-ripple_neutral_on_subtle);
      --md-text-button-pressed-state-layer-opacity: 100%;
      --md-text-button-trailing-space: ${LABEL_PADDING_START_END};
    }

    :host([inverted]) md-text-button {
      /** Base styles */
      --md-sys-color-primary: var(--cros-sys-inverse_primary);
      --md-sys-color-secondary: var(--cros-sys-inverse_focus_ring);
      --md-text-button-label-text-color: var(--cros-sys-inverse_primary);
      /** Disabled */
      --md-text-button-disabled-label-text-color: var(--cros-sys-inverse_primary);
      --md-text-button-pressed-state-layer-color: var(--cros-sys-inverse_ripple_neutral_on_subtle);
      --md-text-button-hover-state-layer-color: var(--cros-sys-inverse_hover_on_subtle);
    }

    ::slotted(ea-icon) {
      --ea-icon-size: 20px;
    }
  `;

  /** @export */
  label: string;
  /** @export */
  disabled: boolean;
  /**
   * How the button should be styled. One of {primary, secondary, floating}.
   * @export
   */
  buttonStyle: 'primary'|'secondary'|'floating' = 'primary';

  /**
   * If the button should be in the inverted color scheme, eg for use in
   * cros-snackbar. Inverted color schemes are only supported for `primary` and
   * floating button styles.
   * @export
   */
  inverted = false;

  /**
   * If button should truncate with ellipsis or stack contents if label
   * overflows button container.
   * @export
   */
  overflow: 'truncate'|'stack' = 'truncate';

  /** @nocollapse */
  static override properties = {
    ariaLabel: {type: String, reflect: true, attribute: 'aria-label'},
    label: {type: String, reflect: true},
    disabled: {type: Boolean, reflect: true},
    buttonStyle: {type: String, reflect: true, attribute: 'button-style'},
    inverted: {type: Boolean, reflect: true},
    ariaHasPopup: {type: String, reflect: true, attribute: 'aria-haspopup'},
    overflow: {type: String, reflect: true},
  };

  constructor() {
    super();
    this.ariaLabel = '';
    this.ariaHasPopup = 'false';
    this.label = '';
    this.disabled = false;
  }

  override connectedCallback() {
    super.connectedCallback();
    // All aria properties on button just get proxied down to the real <button>
    // element, as such we set role to presentation so screenreaders ignore
    // this component and instead only read aria attributes off the inner
    // interactive element.
    this.setAttribute('role', 'presentation');
  }

  override firstUpdated() {
    this.addEventListener('click', this.clickListener);
  }

  private clickListener (e: MouseEvent) {
    if (this.disabled) {
      e.stopImmediatePropagation();
      e.preventDefault();
      return;
    }
  }

  override render() {
    const ariaHasPopup = (this.ariaHasPopup ?? 'false') as 'false' | 'true' |
        'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';

    if (this.buttonStyle === 'floating') {
      return html`
        <md-text-button
            class="button"
            aria-label=${this.ariaLabel || ''}
            aria-haspopup=${ariaHasPopup}
            ?disabled=${this.disabled}>
          ${this.renderButtonContent()}
        </md-text-button>
        `;
    }

    return html`
        <md-filled-button
            class="button"
            aria-label=${this.ariaLabel || ''}
            aria-haspopup=${ariaHasPopup}
            ?disabled=${this.disabled}>
          ${this.renderButtonContent()}
        </md-filled-button>
        `;
  }

  private renderButtonContent() {
    return html`
      <div class="content-container">
        <slot name="leading-icon" @slotchange=${this.onSlotChange}></slot>
        <span class="label">${this.label}</span>
        <slot name="trailing-icon" @slotchange=${this.onSlotChange}></slot>
      </div>
    `;
  }

  private hasSlottedIcon(icon: 'leading'|'trailing'): boolean {
    return !!this.querySelector(`*[slot='${icon}-icon']`);
  }

  // The padding before/after the label changes based on whether there is an
  // icon present. We use the slot change event to toggle the different padding
  // styles on the container, as it's easier to achieve here compared with pure
  // CSS selectors. The actual padding is applied to the material button which
  // is difficult to define a pure CSS relationship with due to the nature of
  // how the slots are arranged.
  private onSlotChange() {
    const container = this.shadowRoot!.querySelector('.content-container');
    container!.classList.toggle(
        'has-leading-icon', this.hasSlottedIcon('leading'));
    container!.classList.toggle(
        'has-trailing-icon', this.hasSlottedIcon('trailing'));
  }
}

customElements.define('cros-button', Button);

declare global {
  interface HTMLElementTagNameMap {
    'cros-button': Button;
  }
}
