import { html, PropertyValues, TemplateResult } from 'lit'
import { property, state } from 'lit/decorators.js'
import { live } from 'lit/directives/live.js'
import { type IUrlParamPart, UrlProcessor } from '@api-client/core/lib/parsers/UrlProcessor.js'
import { UrlEncoder } from '@api-client/core/lib/parsers/UrlEncoder.js'
import ApiElement from '../ApiElement.js'
import type Input from '../../md/input/Input.js'
import type SwitchElement from '../../md/switch/internals/SwitchElement.js'
import '../../md/button/ui-button.js'
import '../../md/icon-button/ui-icon-button.js'
import '../../md/dropdown-list/ui-dropdown-list.js'
import '../../md/list/ui-list.js'
import '../../md/list/ui-list-item.js'
import '../../md/switch/ui-switch.js'
import '../../md/divider/ui-divider.js'
import '../../md/icons/ui-icon.js'
import '../../md/text-field/ui-filled-text-field.js'

export default class UrlParamsForm extends ApiElement {
  protected parser = new UrlProcessor('/')

  /**
   * When set the editor is in read only mode.
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor readOnly = false

  /**
   * Whether the text editor is rendered instead of the form.
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor text = false

  #value = ''

  /**
   * The current value of the editor.
   * @attribute
   */
  get value(): string {
    return this.#value
  }

  @property({ type: String })
  set value(value: string) {
    const old = this.#value
    if (old === value) {
      return
    }
    this.#value = value
    this.setupParser(value)
  }

  @state() protected accessor textValue: string | undefined

  protected override update(cp: PropertyValues<this>): void {
    if (cp.has('text')) {
      if (this.text) {
        this.textValue = this.parser.search.toParamsString()
      } else {
        this.textValue = undefined
      }
    }
    super.update(cp)
  }

  protected setupParser(value: string): void {
    if (value) {
      this.parser = new UrlProcessor(value)
    } else {
      this.parser = new UrlProcessor('/')
    }
    this.requestUpdate()
  }

  protected handleAdd(): void {
    this.addParameter()
  }

  addParameter(): void {
    if (this.readOnly) {
      return
    }
    this.parser.search.append('', '')
    this.requestUpdate()
    this.focusLast()
  }

  protected async focusLast(): Promise<void> {
    await this.updateComplete
    const row = this.shadowRoot?.querySelector('.param-row:last-of-type')
    if (!row) {
      return
    }
    try {
      const node = row.querySelector('.name-input input') as HTMLInputElement | undefined
      if (node) {
        node.focus()
      }
    } catch {
      // ...
    }
  }

  protected handleOption(e: CustomEvent): void {
    const element = e.detail.item as HTMLElement
    const { option } = element.dataset
    if (!option) {
      return
    }
    if (option === 'clear') {
      this.clear()
    } else if (option === 'text-editor') {
      this.toggle()
    }
  }

  /**
   * Toggles the state between form and text editors.
   */
  toggle(): void {
    this.text = !this.text
  }

  /**
   * Removes all parameters.
   */
  clear(): void {
    this.parser.search.clear()
    this.updateValue()
    this.requestUpdate()
    if (this.text) {
      this.textValue = this.parser.search.toParamsString()
    }
  }

  /**
   * Updates name/value of a parameter on a specific index.
   */
  updateProperty(index: number, field: 'name' | 'value', value: string): void {
    const list = this.parser.search.list()
    list[index][field] = value
    this.parser.search.update(index, list[index])
    this.updateValue()
    if (this.text) {
      this.textValue = this.parser.search.toParamsString()
    }
  }

  /**
   * Removes a parameter from an index.
   */
  removeProperty(index: number): void {
    this.parser.search.delete(index)
    this.updateValue()
    this.requestUpdate()
    if (this.text) {
      this.textValue = this.parser.search.toParamsString()
    }
  }

  /**
   * Toggles enabled state of a parameter.
   */
  toggleProperty(index: number, enabled: boolean): void {
    this.parser.search.toggle(index, enabled)
    this.updateValue()
    if (this.text) {
      this.textValue = this.parser.search.toParamsString()
    }
  }

  /**
   * Encodes all parameters.
   */
  encodeProperties(): void {
    const { search } = this.parser
    const list = search.list()
    list.forEach((part, index) => {
      part.name = UrlEncoder.encodeQueryString(part.name, true)
      part.value = UrlEncoder.encodeQueryString(part.value, true)
      search.update(index, part)
    })
    // TODO: UrlEncoder.decodeQueryString(part, false) on each path segment
    this.requestUpdate()
    this.updateValue()
    if (this.text) {
      this.textValue = this.parser.search.toParamsString()
    }
  }

  /**
   * Decodes all parameters.
   */
  decodeProperties(): void {
    const { search } = this.parser
    const list = search.list()
    list.forEach((part, index) => {
      part.name = UrlEncoder.decodeQueryString(part.name, true)
      part.value = UrlEncoder.decodeQueryString(part.value, true)
      search.update(index, part)
    })
    // TODO: UrlEncoder.encodeQueryString(part, false) on each path segment
    this.requestUpdate()
    this.updateValue()
    if (this.text) {
      this.textValue = this.parser.search.toParamsString()
    }
  }

  protected updateValue(): void {
    this.#value = this.parser.toString()
    this.notifyChange()
  }

  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()
  }

  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)
  }

  protected handleEncode(): void {
    this.encodeProperties()
  }

  protected handleDecode(): void {
    this.decodeProperties()
  }

  protected handleText(e: Event): void {
    const input = e.target as HTMLTextAreaElement
    const { value } = input
    this.parser.search.fromParamsString(value)
    this.updateValue()
  }

  protected override render(): TemplateResult {
    return html` ${this.renderActionButtons()} ${this.renderEditor()} `
  }

  protected renderActionButtons(): TemplateResult {
    return html`
      <div class="editor-actions">
        <ui-button color="tonal" @click="${this.handleAdd}">Add</ui-button>
        <ui-button color="tonal" @click="${this.handleEncode}">URL encode</ui-button>
        <ui-button color="tonal" @click="${this.handleDecode}">URL decode</ui-button>
        <ui-dropdown-list @select="${this.handleOption}" closeOnOutsideClick class="dropdown">
          <ui-icon-button aria-label="Additional options"
            ><ui-icon icon="moreVert" role="presentation"></ui-icon
          ></ui-icon-button>
          <ui-list slot="dropdown" role="menu">
            <ui-list-item
              role="menuitem"
              data-option="text-editor"
              image="icon"
              title="Toggles between a text editor and a form"
            >
              <ui-icon role="presentation" slot="start" icon="${this.text ? 'checkBox' : 'checkBoxBlank'}"></ui-icon>
              Text editor
            </ui-list-item>
            <ui-divider type="inset"></ui-divider>
            <ui-list-item
              role="menuitem"
              data-option="clear"
              image="icon"
              title="Removes all parameters from the editor"
            >
              <ui-icon role="presentation" slot="start" icon="deleteOutline"></ui-icon>
              Clear form
            </ui-list-item>
          </ui-list>
        </ui-dropdown-list>
      </div>
    `
  }

  protected renderEditor(): TemplateResult {
    if (this.text) {
      return this.renderText()
    }
    return this.renderList()
  }

  protected renderText(): TemplateResult {
    const { textValue = '' } = this
    return html`
      <div class="text-input">
        <div class="error" role="presentation">Invalid character</div>
        <div class="state"></div>
        <textarea .value="${live(textValue)}" data-enable-grammarly="false" @change="${this.handleText}"></textarea>
      </div>
    `
  }

  protected renderList(): TemplateResult {
    const items = this.parser.search.list()
    if (!Array.isArray(items) || !items.length) {
      return this.renderEmptyMessage()
    }
    return this.renderParams(items)
  }

  protected renderEmptyMessage(): TemplateResult {
    return html` <div class="empty-message">No parameters added</div> `
  }

  protected renderParams(items: IUrlParamPart[]): TemplateResult {
    return html` ${items.map((item, index) => this.renderParam(item, index))} `
  }

  protected renderParam(item: IUrlParamPart, index: number): TemplateResult {
    return html`
      <div class="param-row">
        ${this.renderToggleButton(item, index)} ${this.renderNameField(item, index)}
        ${this.renderValueField(item, index)} ${this.renderDeleteParameter(index)}
      </div>
    `
  }

  protected renderToggleButton(item: IUrlParamPart, index: number): TemplateResult {
    return html`
      <ui-switch
        .checked="${item.enabled}"
        @change="${this.handleToggle}"
        aria-label="Toggle this parameter"
        data-index="${index}"
        ?disabled="${this.readOnly}"
      ></ui-switch>
    `
  }

  protected renderDeleteParameter(index: number): TemplateResult {
    return html`
      <ui-icon-button
        aria-label="Delete this parameter"
        data-index="${index}"
        @click="${this.handleRemove}"
        ?disabled="${this.readOnly}"
      >
        <ui-icon icon="deleteOutline" role="presentation"></ui-icon>
      </ui-icon-button>
    `
  }

  protected renderNameField(item: IUrlParamPart, 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}"
          ?readOnly="${this.readOnly}"
          pattern="\\S*"
          aria-label="Parameter name value"
          @change="${this.handleNameChange}"
        />
      </div>
    `
  }

  protected renderValueField(item: IUrlParamPart, index: number): TemplateResult {
    return html`
      <div class="input-wrapper value-input">
        <div class="error" role="presentation">Invalid character</div>
        <div class="state"></div>
        <input
          type="text"
          name="value"
          .value="${live(item.value)}"
          data-index="${index}"
          ?readOnly="${this.readOnly}"
          pattern="\\S*"
          aria-label="Parameter value"
          @change="${this.handleValueChange}"
        />
      </div>
    `
  }
}
