import { html, PropertyValues, TemplateResult } from 'lit'
import { property, query } from 'lit/decorators.js'
import { classMap } from 'lit/directives/class-map.js'
import { UiElement } from '../../UiElement.js'
import { BeginPressConfig, EndPressConfig } from '../../../controllers/ActionController.js'
import UiRipple from '../../ripple/internals/ripple.js'
import { setDisabled } from '../../../lib/disabled.js'
import { ripple } from '../../effects/rippleDirective.js'

import '@material/web/focus/md-focus-ring.js'
import '../../ripple/ui-ripple.js'

export enum ListItemLines {
  one = 'one',
  two = 'two',
  three = 'three',
}

export enum ListItemImage {
  icon = 'icon',
  avatar = 'avatar',
  image = 'image',
  video = 'video',
  auto = 'auto',
}

/**
 * @slot
 * @slot start
 * @slot end
 * @slot end-text
 * @slot supporting-text
 */
export default class UiListItem extends UiElement {
  @query('ui-ripple') accessor ripple!: UiRipple

  /**
   * Whether the chip is disabled. The user can't interact with the chip when `true`.
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor disabled = false

  /**
   * The number of lines to render the list template for.
   * @default ListItemLines.one
   * @attribute
   */
  @property({ type: String, reflect: true }) accessor lines: ListItemLines

  /**
   * The type of the list image.
   * @default ListItemImage.icon
   * @attribute
   */
  @property({ type: String, reflect: true }) accessor image: ListItemImage

  /**
   * Whether the list item is static and should not be focusable.
   * The ripple effect will not be applied when `true`.
   * @default false
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor static = false

  @property({ type: Number, reflect: true }) override accessor tabIndex = -1

  constructor() {
    super()

    this.lines = ListItemLines.one
    this.image = ListItemImage.icon

    this.addEventListener('click', this.handleClick.bind(this))
    this.addEventListener('pointerdown', this.handlePointerDown.bind(this))
    this.addEventListener('pointerup', this.handlePointerUp.bind(this))
    this.addEventListener('pointercancel', this.handlePointerCancel.bind(this))
    this.addEventListener('pointerleave', this.handlePointerLeave.bind(this))
    this.addEventListener('pointerenter', this.handlePointerEnter.bind(this))
    this.addEventListener('contextmenu', this.handleContextMenu.bind(this))
    this.addEventListener('keydown', this.handleKeyDown.bind(this))
    this.addEventListener('keyup', this.handleKeyUp.bind(this))
  }

  protected override updated(changedProperties: PropertyValues<this>): void {
    super.updated(changedProperties)
    if (changedProperties.has('tabIndex') && (this.tabIndex === -1 || this.tabIndex === null)) {
      this.ripple?.endPress()
    }
  }

  override connectedCallback(): void {
    super.connectedCallback()
    if (!this.hasAttribute('role')) {
      this.setAttribute('role', 'listitem')
    }
  }

  protected override willUpdate(cp: PropertyValues<this>): void {
    if (cp.has('disabled')) {
      setDisabled(this, cp.get('disabled'))
    }
  }

  override handleKeyDown(e: KeyboardEvent): void {
    if (this.disabled || this.static) {
      e.stopPropagation()
      e.preventDefault()
      return
    }
    if (e.key !== ' ' && e.key !== 'Enter') return
    // do not prevent default, so that parent elements can handle the key event
    this.beginPress({ positionEvent: e })
  }

  override handleKeyUp(e: KeyboardEvent): void {
    if (this.disabled || this.static) {
      e.stopPropagation()
      e.preventDefault()
      return
    }
    if (e.key !== ' ' && e.key !== 'Enter') return

    // do not prevent default, so that parent elements can handle the key event
    this.endPress({ cancelled: false, actionData: { item: this } })
  }

  override beginPress(options: BeginPressConfig): void {
    if (this.disabled || this.static) {
      return
    }
    super.beginPress(options)
    this.ripple.beginPress(options.positionEvent)
  }

  override endPress(options: EndPressConfig): void {
    if (this.disabled || this.static) {
      return
    }
    super.endPress(options)
    this.ripple.endPress()
  }

  override handlePointerEnter(e: PointerEvent): void {
    if (this.disabled || this.static) {
      e.stopPropagation()
      e.preventDefault()
      return
    }
    this.ripple.beginHover(e)
  }

  override handlePointerLeave(e: PointerEvent): void {
    if (this.disabled || this.static) {
      e.stopPropagation()
      e.preventDefault()
      return
    }
    super.handlePointerLeave(e)

    this.ripple.endHover()
  }

  /**
   * Focuses list item and makes list item focusable via keyboard.
   */
  activate(): void {
    if (this.disabled || this.static) {
      return
    }
    this.setAttribute('tabindex', '0')
    this.focus()
  }

  /**
   * Returns true if list item is currently focused and is focusable.
   */
  isActive(): boolean {
    if (this.disabled || this.static) {
      return false
    }
    return this.getAttribute('tabindex') === '0'
  }

  /**
   * Removes list item from sequential keyboard navigation.
   */
  deactivate(): void {
    if (this.disabled || this.static) {
      return
    }
    this.removeAttribute('tabindex')
  }

  protected handleSlotChange(): void {
    this.requestUpdate()
  }

  protected override render(): TemplateResult {
    const { pressed = false } = this
    const surfaceClasses = classMap({
      surface: true,
      pressed,
    })

    return html`
      <md-focus-ring part="focus-ring" .control="${this as HTMLElement}" inward></md-focus-ring>
      <div class="${surfaceClasses}" ${ripple(() => this.ripple)}>
        <div class="container"></div>
        <div class="state"></div>
        <div class="content">${this.renderStart()} ${this.renderBody()} ${this.renderEnd()}</div>
        ${this.renderRipple()}
      </div>
    `
  }

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

  protected renderStart(): TemplateResult {
    return html`<div class="start">
      <slot name="start" @slotchange=${this.handleSlotChange}></slot>
    </div>`
  }

  protected renderEnd(): TemplateResult {
    return html`<div class="end">
      <slot name="end" @slotchange=${this.handleSlotChange}></slot>
      <span class="trailing-supporting-text"><slot name="end-text"></slot></span>
    </div>`
  }

  protected renderBody(): TemplateResult {
    const { lines = ListItemLines.one } = this
    const hasSupportingText = lines !== ListItemLines.one
    return html`
      <div class="body">
        <span class="headline"><slot></slot></span>
        ${hasSupportingText ? html`<span class="supporting-text"><slot name="supporting-text"></slot></span>` : ''}
      </div>
    `
  }
}
