import { html, nothing, TemplateResult } from 'lit'
import { type IHttpAssertion } from '@api-client/core/models/http-flows/HttpAssertion.js'
import { type IHttpStep } from '@api-client/core/models/http-flows/HttpStep.js'
import { property } from 'lit/decorators.js'
import { HttpFlowsUi, StepsTarget } from './HttpFlowsUi.js'
import type SwitchElement from '../../md/switch/internals/SwitchElement.js'
import type Input from '../../md/input/Input.js'
import UiDialog, { UiDialogClosingReason } from '../../md/dialog/internals/Dialog.js'
import '../../md/button/ui-button.js'
import '../../md/switch/ui-switch.js'
import '../../md/dialog/ui-dialog.js'
import '../../md/divider/ui-divider.js'

/**
 * @fires change When the model change
 */
export default class HttpAssertions extends HttpFlowsUi {
  /**
   * The list of assertions.
   * This is a live list. All changes are propagated in the model.
   */
  @property({ type: Array }) accessor model: IHttpAssertion[] = []

  /**
   * Adds a new empty assertion to the model.
   * Note, this function won't notify the parent element
   * about the change as empty rules can be discarded.
   */
  addAssertion(): void {
    const { model } = this
    model.push({
      enabled: true,
      steps: [],
    })
    this.requestUpdate()
  }

  /**
   * Removes an assertion at specified index.
   * @param index The index of the assertion in the `model` to remove.
   */
  removeAssertion(index: number): void {
    const { model } = this
    if (!model[index]) {
      throw new RangeError(`Invalid index: ${index}`)
    }
    model.splice(index, 1)
    this.requestUpdate()
    this.notifyChange()
  }

  /**
   * Toggles the enabled state of the assertion.
   * @param index The index of the assertion on the `model` array
   * @param value The `enabled` value.
   */
  toggleAssertion(index: number, value: boolean): void {
    const { model } = this
    if (!model[index]) {
      throw new RangeError(`Invalid index: ${index}`)
    }
    model[index].enabled = value
    this.requestUpdate()
    this.notifyChange()
  }

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

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

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

  protected getStepsTarget(assertionOrActionIndex: number): StepsTarget | undefined {
    if (Number.isNaN(assertionOrActionIndex)) {
      return undefined
    }
    return this.model[assertionOrActionIndex]
  }

  protected stepFromChangeEvent(e: Event): IHttpStep | undefined {
    const input = e.target as Input
    input.checkValidity()
    const assertionIndex = Number(input.dataset.assertionIndex)
    const stepIndex = Number(input.dataset.stepIndex)
    if (Number.isNaN(assertionIndex) || Number.isNaN(stepIndex)) {
      return undefined
    }
    const assertion = this.model[assertionIndex]
    if (!assertion) {
      return undefined
    }
    return assertion.steps[stepIndex]
  }

  protected handleRenameAssertion(e: Event): void {
    const target = e.currentTarget as HTMLElement
    const assertionIndex = Number(target.dataset.assertionIndex)
    if (Number.isNaN(assertionIndex)) {
      return
    }
    const assertion = this.model[assertionIndex]
    if (!assertion) {
      return
    }
    const { description } = assertion
    const dialog = this.shadowRoot?.querySelector('.rename-dialog') as UiDialog | null
    if (!dialog) {
      return
    }
    const input = dialog.querySelector('ui-filled-text-field') as Input
    input.value = description || ''
    dialog.open = true
    dialog.dataset.assertionIndex = target.dataset.assertionIndex
  }

  protected handleRenameDialogResult(e: CustomEvent<UiDialogClosingReason>): void {
    if (e.detail.cancelled) {
      return
    }
    const dialog = e.currentTarget as UiDialog
    const assertionIndex = Number(dialog.dataset.assertionIndex)
    if (Number.isNaN(assertionIndex)) {
      return
    }
    const assertion = this.model[assertionIndex]
    if (!assertion) {
      return
    }
    const input = dialog.querySelector('ui-filled-text-field') as Input
    assertion.description = input.value
    this.requestUpdate()
    this.notifyChange()
    input.value = ''
    delete dialog.dataset.assertionIndex
  }

  protected override render(): TemplateResult {
    return html`
      ${this.renderActionButtons()}
      <div class="container">
        ${this.renderEditor()}
        <ui-divider vertical></ui-divider>
        ${this.renderAssertions()}
      </div>
      ${this.renderRenameAssertionDialog()}
    `
  }

  protected renderActionButtons(): TemplateResult {
    return html`
      <div class="editor-actions">
        <ui-button color="tonal" @click="${this.handleAdd}" class="add-button">Add</ui-button>
      </div>
    `
  }

  protected renderAssertions(): TemplateResult {
    return html`
      <section aria-label="Assertion options" class="options">
        ${this.renderResponseDataSourceSuggestions()} ${this.renderTransformationSuggestions()}
        ${this.renderAssertionSuggestions()}
      </section>
    `
  }

  protected renderEditor(): TemplateResult {
    const { model } = this
    const content = Array.isArray(model) && !!model.length ? this.renderList(model) : this.renderEmptyMessage()
    return html` <section aria-label="Assertions editor" class="editor surface1">${content}</section> `
  }

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

  protected renderList(model: IHttpAssertion[]): TemplateResult {
    return html` ${model.map((item, index) => this.renderAssertion(item, index))} `
  }

  protected renderAssertion(item: IHttpAssertion, index: number): TemplateResult {
    return html`
      <div class="step-row">
        ${this.renderAssertionDescription(item)}
        <div class="param-row">
          ${this.renderToggleButton(item, index)} ${this.renderSteps(item, index)}
          <div class="action-actions">${this.renderRenameAssertion(index)} ${this.renderDeleteAssertion(index)}</div>
        </div>
      </div>
    `
  }

  protected renderAssertionDescription(item: IHttpAssertion): TemplateResult | typeof nothing {
    const { description } = item
    if (!description) {
      return nothing
    }
    return html` <div class="assertion-description label-medium">${description}</div> `
  }

  protected renderToggleButton(item: IHttpAssertion, assertionIndex: number): TemplateResult {
    return html`
      <ui-switch
        .checked="${!!item.enabled}"
        @change="${this.handleToggle}"
        aria-label="Toggle this assertion"
        data-index="${assertionIndex}"
        class="toggle-button"
      ></ui-switch>
    `
  }

  protected renderRenameAssertion(assertionIndex: number): TemplateResult {
    return html`
      <ui-icon-button
        aria-label="Rename this assertion"
        title="Rename this assertion"
        data-assertion-index="${assertionIndex}"
        @click="${this.handleRenameAssertion}"
        class="rename-button"
      >
        <ui-icon icon="rename" role="presentation"></ui-icon>
      </ui-icon-button>
    `
  }

  protected renderDeleteAssertion(assertionIndex: number): TemplateResult {
    return html`
      <ui-icon-button
        aria-label="Delete this assertion"
        title="Delete this assertion"
        data-index="${assertionIndex}"
        @click="${this.handleRemove}"
        class="delete-button"
      >
        <ui-icon icon="deleteOutline" role="presentation"></ui-icon>
      </ui-icon-button>
    `
  }

  protected renderRenameAssertionDialog(): TemplateResult {
    return html`
      <ui-dialog
        modal
        class="rename-dialog"
        @close="${this.handleRenameDialogResult}"
        dismissLabel="Cancel"
        confirmLabel="Accept"
      >
        <ui-icon slot="icon" icon="rename"></ui-icon>
        <span slot="title">Rename assertion</span>

        <ui-filled-text-field name="rename" label="Assertion description" required class="input"></ui-filled-text-field>
      </ui-dialog>
    `
  }
}
