import { html, type TemplateResult, nothing } from 'lit'
import { property, state } from 'lit/decorators.js'
import { ApiEditor } from './ApiEditor.js'
import { apiActionKindToIcon, getPropertyIcon } from '../lib/ui_mapping.js'
import { renderHelp } from '../../help/render.js'
import type { SearchAction, ApiModel, DomainEntity, ExposedEntity, DomainProperty } from '@api-client/core/browser.js'
import type { TabSelectionDetail } from '../../md/tabs/ui-tabs.js'
import type { UiListSelection } from '../../md/list/ui-list.js'
import type { UiMenuItemElement } from '../../md/menu/ui-menu-item.js'

export default class ApiSearchActionEditor extends ApiEditor {
  /**
   * Computed exposed entity being edited.
   */
  @state() accessor exposed: ExposedEntity | undefined
  /**
   * Computed domain entity that corresponds to the exposed entity being edited.
   */
  @state() accessor entity: DomainEntity | undefined
  /**
   * Computed search action definition being edited.
   */
  @state() accessor action: SearchAction | undefined
  /**
   * All the indexed properties of the entity.
   */
  @state() accessor indexed: DomainProperty[] = []
  /**
   * When true, shows a close button in the editor header.
   * @attribute
   */
  @property({ type: Boolean }) accessor showCloseButton = false
  /**
   * The currently active tab index.
   */
  @state() accessor activeTab = 0

  protected override setupModel(model?: ApiModel, domainKey?: string): void | Promise<void> {
    this.exposed = undefined
    this.entity = undefined
    this.action = undefined
    this.indexed = []
    if (!model || !domainKey) {
      return
    }
    const exposed = model.exposes.find((exposed) => exposed.key === domainKey)
    this.exposed = exposed
    if (!exposed) {
      return
    }
    const domain = model.domain
    if (!domain) {
      return
    }
    const entity = domain.findEntity(exposed.entity.key, exposed.entity.domain)
    if (!entity) {
      return
    }
    this.entity = entity
    const action = exposed.actions.find((action) => action.kind === 'search')
    this.action = action
    const indexed: DomainProperty[] = []
    for (const property of entity.properties) {
      if (property.index) {
        // Note that properties on the entity can't change as they belong to a published data domain.
        indexed.push(property)
      }
    }
    this.indexed = indexed
  }

  protected handleCloseButtonClick(): void {
    this.dispatchEvent(new Event('close'))
  }

  /**
   * Handles tab change events.
   */
  protected handleTabChange(e: CustomEvent<TabSelectionDetail>): void {
    this.activeTab = e.detail.index
  }

  protected handleAddSearchField(e: CustomEvent<UiListSelection>): void {
    const item = e.detail.item as UiMenuItemElement
    if (!item.value) {
      return
    }
    this.addSearchField(item.value)
  }

  protected handleRemoveField(fieldKey: string): void {
    const { action } = this
    if (!action) {
      throw new Error('No action is set.')
    }
    this.removeSearchField(fieldKey)
  }

  addSearchField(fieldKey: string): void {
    const { action } = this
    if (!action) {
      throw new Error('No action is set.')
    }
    if (!action.fields.includes(fieldKey)) {
      action.fields.push(fieldKey)
      this.notifyChange()
      this.requestUpdate()
    }
  }

  removeSearchField(fieldKey: string): void {
    const { action } = this
    if (!action) {
      throw new Error('No action is set.')
    }
    const index = action.fields.indexOf(fieldKey)
    if (index !== -1) {
      action.fields.splice(index, 1)
      this.notifyChange()
      this.requestUpdate()
    }
  }

  protected handleConfigurationChange(): void {
    this.notifyChange()
  }

  protected override render(): TemplateResult | typeof nothing {
    if (!this.exposed || !this.entity || !this.action) {
      return nothing
    }
    return html`<div class="container">${this.renderHeader()} ${this.renderTabs()} ${this.renderTabContent()}</div>`
  }

  /**
   * Renders a template of the editor header.
   */
  protected renderHeader(): TemplateResult {
    const { entity } = this
    let label = 'Search action'
    if (entity) {
      label = entity.info.getLabel() + ': Search action'
    }
    return html`<div class="intro">
      <div class="header">
        <ui-icon>${apiActionKindToIcon('search')}</ui-icon>
        <span class="headline-medium">${label}</span>
        ${this.renderCloseButton()}
      </div>
      <div class="body-medium description">Search action allows searching through the indexed fields.</div>
    </div>`
  }

  /**
   * Renders the tabs navigation.
   */
  protected renderTabs(): TemplateResult {
    return html`<ui-tabs
      class="tabs"
      .activeTabIndex="${this.activeTab}"
      @change="${this.handleTabChange}"
      priority="secondary"
    >
      <ui-tab>Configuration</ui-tab>
      <ui-tab>Access</ui-tab>
      <ui-tab>Limits</ui-tab>
    </ui-tabs>`
  }

  /**
   * Renders the content for the currently active tab.
   */
  protected renderTabContent(): TemplateResult | typeof nothing {
    if (!this.action) {
      return nothing
    }
    switch (this.activeTab) {
      case 0:
        return this.renderConfigurationTab()
      case 1:
        return this.renderAccessTab()
      case 2:
        return this.renderLimitsTab()
      default:
        return nothing
    }
  }

  /**
   * Renders the Configuration tab content.
   */
  protected renderConfigurationTab(): TemplateResult | typeof nothing {
    if (!this.action) {
      return nothing
    }
    return html`<div class="configuration-tab">${this.renderSearchConfiguration(this.action, this.indexed)}</div>`
  }

  /**
   * Renders the Access tab content.
   */
  protected renderAccessTab(): TemplateResult {
    return html`<div class="config-section">
      <api-modeling-access-configuration
        .model="${this.model}"
        domainKey="${this.exposed?.key || ''}"
        operation="search"
        target="action"
        class="access-configuration"
        @change="${this.handleConfigurationChange}"
      ></api-modeling-access-configuration>
    </div>`
  }

  /**
   * Renders the Limits tab content.
   */
  protected renderLimitsTab(): TemplateResult {
    return html`<div class="config-section">
      <api-modeling-rate-limiting
        .model="${this.model}"
        domainKey="${this.exposed?.key || ''}"
        operation="search"
        target="action"
        class="rate-limiting-configuration"
        @change="${this.handleConfigurationChange}"
      ></api-modeling-rate-limiting>
    </div>`
  }

  /**
   * Renders the close button if the `showCloseButton` flag is set.
   */
  protected renderCloseButton(): TemplateResult | typeof nothing {
    if (!this.showCloseButton) {
      return nothing
    }
    return html`<ui-icon-button
      color="outlined"
      class="close-button"
      aria-label="Close editor"
      @click="${this.handleCloseButtonClick}"
    >
      <ui-icon icon="close"></ui-icon>
    </ui-icon-button>`
  }

  protected renderSearchConfiguration(action: SearchAction, indexed: DomainProperty[]): TemplateResult {
    const { fields: selected } = action
    const availableFields = indexed.filter((property) => !selected.includes(property.key))
    const hasAvailableFields = availableFields.length > 0

    return html`<div class="search-configuration config-section">
      <div class="config-intro">
        <div class="section-header">
          <span class="title-large">Search Fields</span>
          ${renderHelp('modeling', 'api:search-action:fields')}
        </div>
        <div class="section-description body-medium">Fields from the entity that can be used for searching.</div>
      </div>

      <ui-button class="add-button" color="tonal" popovertarget="fields-menu" ?disabled="${!hasAvailableFields}">
        <ui-icon slot="icon" icon="add"></ui-icon>
        Add field
      </ui-button>
      <ui-menu id="fields-menu" popover="auto" @select="${this.handleAddSearchField}">
        ${availableFields.map(
          (property) => html`<ui-menu-item value="${property.key}">${property.info.getLabel()}</ui-menu-item>`
        )}
      </ui-menu>
      ${selected.length > 0
        ? html`<ui-list class="fields-list" delegateFocus
            >${selected.map((fieldKey) => this.renderFieldListItem(fieldKey, indexed))}</ui-list
          >`
        : nothing}
    </div>`
  }

  protected renderFieldListItem(fieldKey: string, indexed: DomainProperty[]): TemplateResult {
    const property = indexed.find((p) => p.key === fieldKey)
    const title = property?.info?.getLabel() || fieldKey
    const description = property?.info?.description
    const icon = property ? getPropertyIcon(property) : 'questionMark'

    return html`<ui-list-item static class="field-list-item">
      <span slot="start">
        <ui-icon icon="${icon}"></ui-icon>
      </span>
      <span>${title}</span>
      ${description ? html`<span slot="supporting-text">${description}</span>` : nothing}
      <ui-icon-button
        slot="end"
        color="standard"
        @click="${() => this.handleRemoveField(fieldKey)}"
        aria-label="Remove filter field"
        inwardFocus
      >
        <ui-icon icon="deleteOutline"></ui-icon>
      </ui-icon-button>
    </ui-list-item>`
  }
}
