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 { ListAction, ApiModel, DomainEntity, ExposedEntity, DomainProperty } from '@api-client/core/browser.js'
import type { TabSelectionDetail } from '../../md/tabs/ui-tabs.js'
import type { UiSelectChangeEvent } from '../../md/select/ui-select.js'
import type { UiListSelection } from '../../md/list/ui-list.js'
import type { UiMenuItemElement } from '../../md/menu/ui-menu-item.js'

export default class ApiListActionEditor 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 list action definition being edited.
   */
  @state() accessor action: ListAction | 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 === 'list')
    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 handlePaginationStrategyChange(e: CustomEvent<UiSelectChangeEvent>): void {
    const { value } = e.detail
    if (!value) {
      return
    }
    this.setPaginationStrategy(value)
  }

  /**
   * Sets the pagination strategy for the action.
   * @param strategy The strategy to set on the action. Must be `offset` or `cursor`.
   */
  setPaginationStrategy(strategy: string): void {
    if (!['offset', 'cursor'].includes(strategy)) {
      throw new Error(`Invalid pagination strategy: ${strategy}.`)
    }
    const { action } = this
    if (!action) {
      throw new Error('No action is set.')
    }
    if (!action.pagination) {
      action.pagination = { kind: strategy }
    } else {
      action.pagination.kind = strategy
    }
    this.notifyChange()
    this.requestUpdate()
  }

  protected handleDefaultPageSizeChange(e: Event): void {
    const input = e.target as HTMLInputElement
    const value = input.valueAsNumber
    this.setDefaultPageSize(value)
  }

  /**
   * Sets the default page size for the action's pagination.
   * @param pageSize The page size to set. Must be between 1 and 100.
   */
  setDefaultPageSize(pageSize: number): void {
    if (pageSize < 1 || pageSize > 100) {
      throw new Error('Page size must be between 1 and 100.')
    }
    const { action } = this
    if (!action) {
      throw new Error('No action is set.')
    }
    if (!action.pagination) {
      action.pagination = { kind: 'offset', pageSize }
    } else {
      action.pagination.pageSize = pageSize
    }
    this.notifyChange()
    this.requestUpdate()
  }

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

  protected handleRemoveField(fieldKey: string, type: 'filter' | 'sort'): void {
    const { action } = this
    if (!action) {
      throw new Error('No action is set.')
    }
    const index =
      type === 'filter' ? action.filterableFields.indexOf(fieldKey) : action.sortableFields.indexOf(fieldKey)
    if (index !== -1) {
      if (type === 'filter') {
        action.filterableFields.splice(index, 1)
      } else {
        action.sortableFields.splice(index, 1)
      }
      this.notifyChange()
      this.requestUpdate()
    }
  }

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

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

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

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

  removeSortField(fieldKey: string): void {
    const { action } = this
    if (!action) {
      throw new Error('No action is set.')
    }
    const index = action.sortableFields.indexOf(fieldKey)
    if (index !== -1) {
      action.sortableFields.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 = 'List action'
    if (entity) {
      label = entity.info.getLabel() + ': List action'
    }
    return html`<div class="intro">
      <div class="header">
        <ui-icon>${apiActionKindToIcon('list')}</ui-icon>
        <span class="headline-medium">${label}</span>
        ${this.renderCloseButton()}
      </div>
      <div class="body-medium description">List action allows performing pagination on a collection of resources.</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.renderPaginationConfiguration(this.action)} ${this.renderFiltersConfiguration(this.action, this.indexed)}
      ${this.renderSortConfiguration(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="list"
        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="list"
        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 renderPaginationConfiguration(action: ListAction): TemplateResult {
    const { pagination } = action
    const strategy = pagination?.kind || 'offset'
    const defaultPageSize = pagination?.pageSize || 30

    return html`<div class="pagination-configuration config-section">
      <div class="config-intro">
        <div class="section-header">
          <span class="title-large">Pagination</span>
          ${renderHelp('modeling', 'api:list-action:pagination')}
        </div>
        <div class="section-description body-medium">
          Configure the pagination strategy to allow listing resources in this collection.
        </div>
      </div>

      <div class="row input-row">
        <ui-select
          class="input"
          label="Pagination strategy"
          .value="${strategy}"
          @change="${this.handlePaginationStrategyChange}"
        >
          <ui-option value="offset">Offset pagination</ui-option>
          <ui-option value="cursor">Cursor pagination</ui-option>
        </ui-select>
      </div>

      <div class="row input-row">
        <ui-outlined-text-field
          class="input"
          label="Default page size"
          type="number"
          .value="${String(defaultPageSize)}"
          min="1"
          max="100"
          @change="${this.handleDefaultPageSizeChange}"
        ></ui-outlined-text-field>
      </div>
    </div>`
  }

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

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

      <ui-button class="add-button" color="tonal" popovertarget="filters-menu" ?disabled="${!hasAvailableFields}">
        <ui-icon slot="icon" icon="add"></ui-icon>
        Add field
      </ui-button>
      <ui-menu id="filters-menu" popover="auto" @select="${this.handleAddFilterField}">
        ${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, 'filter'))}</ui-list
          >`
        : nothing}
    </div>`
  }

  protected renderFieldListItem(fieldKey: string, indexed: DomainProperty[], type: 'filter' | 'sort'): 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, type)}"
        aria-label="Remove filter field"
        inwardFocus
      >
        <ui-icon icon="deleteOutline"></ui-icon>
      </ui-icon-button>
    </ui-list-item>`
  }

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

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

      <ui-button class="add-button" color="tonal" popovertarget="sort-menu" ?disabled="${!hasAvailableFields}">
        <ui-icon slot="icon" icon="add"></ui-icon>
        Add field
      </ui-button>
      <ui-menu id="sort-menu" popover="auto" @select="${this.handleAddSortField}">
        ${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, 'sort'))}</ui-list
          >`
        : nothing}
    </div>`
  }
}
