import { html, TemplateResult, CSSResult } from 'lit'
import { live } from 'lit/directives/live.js'
import { classMap } from 'lit/directives/class-map.js'
import { type IProperty, Property } from '@api-client/core/models/Property.js'
import { property, query, state } from 'lit/decorators.js'
import { variableValueLabel } from './Utils.js'
import { IconType } from '../../md/icons/Icons.js'
import ApiElement from '../ApiElement.js'
import elementStyles from './VariablesEditor.styles.js'
import Input from '../../md/input/Input.js'
import SwitchElement from '../../md/switch/internals/SwitchElement.js'
import '../../md/icons/ui-icon.js'
import '../../md/list/ui-list.js'
import '../../md/list/ui-list-item.js'
import '../../md/switch/ui-switch.js'
import '../../md/icon-button/ui-icon-button.js'

/**
 * An element to render a list of variables with an ability to edit them.
 */
export default class VariablesEditor extends ApiElement {
  static override get styles(): CSSResult[] {
    return [elementStyles]
  }

  /**
   * The list of variables to render or edit.
   * This list is mutated. If you don't want this to mutate changes
   * make a copy first.
   */
  @property({ type: Array }) accessor variables: IProperty[] | undefined

  /**
   * By default all values are masked. When this is set it renders all values visible.
   */
  @property({ type: Boolean }) accessor renderValues = false

  @state() protected accessor editedVariable: number | undefined

  @query('.editor') protected accessor editor: HTMLElement | null = null

  /**
   * Updates name/value of a parameter on a specific index.
   */
  updateProperty(index: number, field: 'name' | 'value', value: string): void {
    const { variables = [] } = this
    if (!variables[index]) {
      return
    }
    variables[index][field] = value
    this.notifyChange()
  }

  /**
   * Removes a parameter from an index.
   */
  removeProperty(index: number): void {
    const { variables = [] } = this
    if (!variables[index]) {
      return
    }
    variables.splice(index, 1)
    this.notifyChange()
    this.requestUpdate()
  }

  /**
   * Toggles enabled state of a parameter.
   */
  toggleProperty(index: number, enabled: boolean): void {
    const { variables = [] } = this
    if (!variables[index]) {
      return
    }
    const variable = variables[index]
    if (variable.enabled === enabled) {
      return
    }
    variable.enabled = enabled
    this.notifyChange()
  }

  protected handleAddVariable(): void {
    if (!this.variables) {
      this.variables = []
    }
    const item = Property.String()
    const index = this.variables.push(item.toJSON())
    this.editedVariable = index - 1
    this.requestUpdate()
    this.ensureEditorInView()
  }

  protected async ensureEditorInView(): Promise<void> {
    await this.updateComplete
    const { editor } = this
    if (editor) {
      editor.scrollIntoView()
    }
  }

  /**
   * Toggles visibility of the variable values.
   */
  protected handleVisibilityToggle(): void {
    this.renderValues = !this.renderValues
  }

  /**
   * A handler for the click event on the variable edit icon.
   * Sets state to edit this variable
   */
  protected handleEditVariable(e: Event): void {
    const node = e.currentTarget as HTMLElement
    const index = Number(node.dataset.index)
    if (Number.isNaN(index)) {
      return
    }
    this.editedVariable = index
  }

  protected handleNameChange(e: Event): void {
    const input = e.target as Input
    const index = Number(input.dataset.index)
    if (Number.isNaN(index)) {
      return
    }
    this.updateProperty(index, 'name', input.value)
    input.checkValidity()
  }

  protected handleValueChange(e: Event): void {
    const input = e.target as Input
    const index = Number(input.dataset.index)
    if (Number.isNaN(index)) {
      return
    }
    this.updateProperty(index, 'value', input.value)
    input.checkValidity()
  }

  /**
   * Removes the variable from the environment
   */
  protected handleVariableDelete(e: Event): void {
    const node = e.currentTarget as HTMLElement
    const index = Number(node.dataset.index)
    if (Number.isNaN(index)) {
      return
    }
    this.removeProperty(index)
  }

  protected handleRemove(e: Event): void {
    const node = e.currentTarget as HTMLElement
    const index = Number(node.dataset.index)
    if (Number.isNaN(index)) {
      return
    }
    this.removeProperty(index)
  }

  protected handleToggle(e: Event): void {
    const node = e.currentTarget as SwitchElement
    const index = Number(node.dataset.index)
    if (Number.isNaN(index)) {
      return
    }
    this.toggleProperty(index, !!node.checked)
  }

  /**
   * A handler for the variable editor close button click.
   */
  protected handleEditorClose(): void {
    this.editedVariable = undefined
    this.requestUpdate()
  }

  protected override render(): TemplateResult {
    return html` ${this.renderHeader()} ${this.renderList()} `
  }

  protected renderHeader(): TemplateResult {
    return html`
      <div class="header">
        <div class="section-title">Variables</div>
        ${this.renderAddButton()} ${this.renderToggleVisibilityButton()}
      </div>
    `
  }

  protected renderAddButton(): TemplateResult {
    return html`
      <ui-icon-button
        title="Add a variable"
        aria-label="Activate to add a new variable"
        @click="${this.handleAddVariable}"
        data-action="add-variables"
        ><ui-icon icon="add"></ui-icon
      ></ui-icon-button>
    `
  }

  protected renderToggleVisibilityButton(): TemplateResult {
    const icon = (this.renderValues ? 'visibilityOff' : 'visibility') as IconType
    return html`
      <ui-icon-button
        title="Toggle values visibility"
        aria-label="Activate to toggle variables visibility"
        @click="${this.handleVisibilityToggle}"
        data-action="toggle-visibility"
        ><ui-icon icon="${icon}"></ui-icon
      ></ui-icon-button>
    `
  }

  protected renderList(): TemplateResult {
    const { variables } = this
    if (!Array.isArray(variables) || !variables.length) {
      return this.renderEmptyInfo()
    }

    return html`
      <ul class="variables-list">
        ${variables.map((item, index) => this.renderListItem(item, index))}
      </ul>
    `
  }

  protected renderEmptyInfo(): TemplateResult {
    return html`<p class="empty-message">This environment has no variables.</p>`
  }

  /**
   * @param item The variable to render.
   * @returns The template for the variable line or variable editor
   */
  protected renderListItem(item: IProperty | Property, index: number): TemplateResult {
    if (this.editedVariable === index) {
      return this.renderEditor(item, index)
    }
    const classes = {
      'disabled': !item.enabled,
      'variable-item': true,
    }
    return html` <li class=${classMap(classes)}>
      <span class="variable-name">${item.name}</span>
      <span class="variable-value">${variableValueLabel((item.value as string) || '', !this.renderValues)}</span>
      ${this.renderListActions(index)}
    </li>`
  }

  /**
   * @param index The index of the property on the list
   * @returns The template for list item actions
   */
  protected renderListActions(index: number): TemplateResult {
    return html` <div class="list-actions">${this.renderEditButton(index)} ${this.renderDeleteButton(index)}</div>`
  }

  /**
   * @param item The variable to render.
   * @returns The template for the variables editor
   */
  protected renderEditor(item: IProperty | Property, index: number): TemplateResult {
    return html`
      <li class="editor">
        <div class="param-row">
          ${this.renderToggleButton(item, index)} ${this.renderNameField(item, index)}
          ${this.renderValueField(item, index)} ${this.renderCloseEditor(index)}
        </div>
      </li>
    `
  }

  protected renderToggleButton(item: IProperty | Property, index: number): TemplateResult {
    return html`
      <ui-switch
        .checked="${!!item.enabled}"
        @change="${this.handleToggle}"
        title="Toggle variable enabled"
        aria-label="Toggle variable enabled state"
        data-index="${index}"
      ></ui-switch>
    `
  }

  protected renderDeleteButton(index: number): TemplateResult {
    return html`
      <ui-icon-button
        aria-label="Deletes this variable"
        title="Deletes this variable"
        data-index="${index}"
        @click="${this.handleRemove}"
        data-action="delete-editor"
      >
        <ui-icon icon="deleteOutline" role="presentation"></ui-icon>
      </ui-icon-button>
    `
  }

  protected renderEditButton(index: number): TemplateResult {
    return html`
      <ui-icon-button
        aria-label="Edits this variable"
        title="Edits this variable"
        data-index="${index}"
        @click="${this.handleEditVariable}"
        data-action="edit"
      >
        <ui-icon icon="edit" role="presentation"></ui-icon>
      </ui-icon-button>
    `
  }

  protected renderCloseEditor(index: number): TemplateResult {
    return html`
      <ui-icon-button
        aria-label="Closes the editor"
        title="Closes the editor"
        data-index="${index}"
        @click="${this.handleEditorClose}"
        data-action="close-editor"
      >
        <ui-icon icon="close" role="presentation"></ui-icon>
      </ui-icon-button>
    `
  }

  protected renderNameField(item: IProperty | Property, index: number): TemplateResult {
    return html`
      <div class="input-wrapper name-input">
        <div class="error" role="presentation">Invalid character</div>
        <div class="state"></div>
        <input
          type="text"
          name="name"
          autocomplete="off"
          .value="${live(item.name)}"
          data-index="${index}"
          pattern="[a-zA-Z0-9_]+"
          required
          aria-label="Variable name"
          @change="${this.handleNameChange}"
        />
      </div>
    `
  }

  protected renderValueField(item: IProperty | Property, index: number): TemplateResult {
    const type = this.renderValues ? 'text' : 'password'
    return html`
      <div class="input-wrapper value-input">
        <div class="error" role="presentation">Invalid character</div>
        <div class="state"></div>
        <input
          type="${type}"
          name="value"
          .value="${live(item.value as string)}"
          data-index="${index}"
          aria-label="Variable value"
          @change="${this.handleValueChange}"
        />
      </div>
    `
  }
}
