import { html, type TemplateResult, nothing } from 'lit'
import { property, state } from 'lit/decorators.js'
import { live } from 'lit/directives/live.js'
import { ApiEditor } from './ApiEditor.js'
import type { ApiAction, ApiModel, DomainEntity, ExposedEntity } from '@api-client/core/browser.js'
import { apiActionKindToIcon, apiActionKindToLabel } from '../lib/ui_mapping.js'
import { renderHelp } from '../../help/render.js'
import type { UiOutlinedTextFieldElement } from '../../md/text-field/ui-outlined-text-field.js'

type ActionKind = 'list' | 'read' | 'create' | 'update' | 'delete' | 'search'

/**
 * An entity editor displaying information about an exposed entity
 * in the API model, including its domain entity.
 *
 * @fires close - Fired when the user clicks the close button in the header.
 */
export default class ApiModelingEntityEditor 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
  /**
   * When true, shows a close button in the editor header.
   * @attribute
   */
  @property({ type: Boolean }) accessor showCloseButton = false

  protected override setupModel(model?: ApiModel, domainKey?: string): void | Promise<void> {
    this.exposed = undefined
    this.entity = undefined
    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
  }

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

  protected handleCollectionPathChange(e: Event): void {
    const { exposed } = this
    if (!exposed) {
      return
    }
    const input = e.target as UiOutlinedTextFieldElement
    input.invalid = false
    try {
      exposed.setCollectionPath(input.value)
      this.notifyChange()
    } catch (e) {
      input.invalid = true
      input.invalidText = (e as Error).message
    }
  }

  protected handleResourcePathChange(e: Event): void {
    const { exposed } = this
    if (!exposed) {
      return
    }
    const input = e.target as UiOutlinedTextFieldElement
    input.invalid = false
    try {
      exposed.setResourcePath(input.value)
      this.notifyChange()
    } catch (e) {
      input.invalid = true
      input.invalidText = (e as Error).message
    }
  }

  protected handleActionToggle(e: Event): void {
    const { exposed } = this
    if (!exposed) {
      return
    }
    const toggle = e.target as HTMLInputElement
    const { name, checked } = toggle
    if (checked) {
      this.addAction(name as ActionKind)
    } else {
      this.removeAction(name as ActionKind)
    }
  }

  removeAction(kind: ActionKind): void {
    const { exposed } = this
    if (!exposed) {
      return
    }
    const action = exposed.actions.findIndex((action) => action.kind === kind)
    if (action === -1) {
      return
    }
    exposed.actions.splice(action, 1)
    this.notifyChange()
    this.requestUpdate()
  }

  addAction(kind: ActionKind): void {
    const { exposed } = this
    if (!exposed) {
      return
    }
    const existing = exposed.actions.find((action) => action.kind === kind)
    if (existing) {
      return
    }
    let action: ApiAction
    switch (kind) {
      case 'list':
        action = { kind: 'list', filterableFields: [], sortableFields: [], pagination: { kind: 'offset' } }
        break
      case 'read':
      case 'create':
      case 'delete':
        action = { kind }
        break
      case 'update':
        action = { kind: 'update', allowedMethods: ['PUT'] }
        break
      case 'search':
        action = { kind: 'search', fields: [] }
        break
      default:
        throw new Error(`Unsupported action kind: ${kind}`)
    }
    exposed.actions.push(action)
    this.notifyChange()
    this.requestUpdate()
  }

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

  /**
   * Renders a template of the editor header.
   * @param entity The related domain entity.
   */
  protected renderHeader(entity: DomainEntity): TemplateResult {
    return html`<div class="intro">
      <div class="header">
        <span class="headline-medium ">${entity.info.getLabel()}</span>
        ${this.renderCloseButton()}
      </div>
      <div class="body-medium description">
        Define the logic related to the data entity. It is an endpoint that will be exposed through the API after
        attaching actions to it.
      </div>
    </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 renderEndpointsConfiguration(exposed: ExposedEntity): TemplateResult {
    return html`<div class="endpoints-configuration config-section">
      <div class="section-header title-large">Endpoints</div>
      <div class="section-description body-medium">Define endpoint paths for the collection and resource.</div>
      ${exposed.hasCollection ? this.renderCollectionPathInput(exposed) : nothing}
      ${this.renderResourcePathInput(exposed)}
    </div>`
  }

  protected renderCollectionPathInput(exposed: ExposedEntity): TemplateResult {
    return html`<div class="row input-row">
      <ui-outlined-text-field
        class="input"
        name="collectionPath"
        label="Collection path"
        .value=${live(exposed.collectionPath || '')}
        ?disabled="${this.readonly}"
        @change=${this.handleCollectionPathChange}
      ></ui-outlined-text-field>
      ${renderHelp('modeling', 'api:collection-path-input')}
    </div>`
  }

  protected renderResourcePathInput(exposed: ExposedEntity): TemplateResult {
    return html`<div class="row input-row">
      <ui-outlined-text-field
        class="input"
        name="resourcePath"
        label="Resource path"
        .value=${exposed.resourcePath || ''}
        ?disabled="${this.readonly}"
        @change=${this.handleResourcePathChange}
      ></ui-outlined-text-field>
      ${renderHelp('modeling', 'api:resource-path-input')}
    </div>`
  }

  protected renderActionsSection(exposed: ExposedEntity): TemplateResult {
    return html`<div class="actions-configuration config-section">
      <div class="section-header title-large">Actions</div>
      <div class="section-description body-medium">
        In this section you can decide which actions can be performed on a collection and a resource.
      </div>
      ${this.renderCollectionActionsSection(exposed)} ${this.renderResourceActionsSection(exposed)}
    </div>`
  }

  protected renderCollectionActionsSection(exposed: ExposedEntity): TemplateResult | typeof nothing {
    if (!exposed.hasCollection) {
      return nothing
    }
    let list = false
    let create = false
    let search = false
    for (const action of exposed.actions) {
      if (action.kind === 'list') {
        list = true
      } else if (action.kind === 'create') {
        create = true
      } else if (action.kind === 'search') {
        search = true
      }
    }
    return html`<div class="collection-actions-configuration config-section">
      <div class="section-header">
        <div class="title-medium">Collection Actions</div>
        ${renderHelp('modeling', 'api:collection-actions')}
      </div>
      <ui-list class="action-presets">
        <ui-list-item lines="two">
          <ui-icon slot="start" class="action-icon">${apiActionKindToIcon('create')}</ui-icon>
          <span>${apiActionKindToLabel('create')}</span>
          <span slot="supporting-text">Create a new resource</span>
          <ui-switch
            slot="end"
            .checked=${create}
            name="create"
            @change=${this.handleActionToggle}
            ?disabled=${this.readonly}
          ></ui-switch>
        </ui-list-item>
        <ui-list-item lines="two">
          <ui-icon slot="start" class="action-icon">${apiActionKindToIcon('list')}</ui-icon>
          <span>${apiActionKindToLabel('list')}</span>
          <span slot="supporting-text">Paginate through the collection</span>
          <ui-switch
            slot="end"
            .checked=${list}
            name="list"
            @change=${this.handleActionToggle}
            ?disabled=${this.readonly}
          ></ui-switch>
        </ui-list-item>
        <ui-list-item lines="two">
          <ui-icon slot="start" class="action-icon">${apiActionKindToIcon('search')}</ui-icon>
          <span>${apiActionKindToLabel('search')}</span>
          <span slot="supporting-text">Search for a record</span>
          <ui-switch
            slot="end"
            .checked=${search}
            name="search"
            @change=${this.handleActionToggle}
            ?disabled=${this.readonly}
          ></ui-switch>
        </ui-list-item>
      </ui-list>
    </div>`
  }

  protected renderResourceActionsSection(exposed: ExposedEntity): TemplateResult {
    let read = false
    let update = false
    let del = false
    for (const action of exposed.actions) {
      if (action.kind === 'read') {
        read = true
      } else if (action.kind === 'update') {
        update = true
      } else if (action.kind === 'delete') {
        del = true
      }
    }
    return html`<div class="resource-actions-configuration config-section">
      <div class="section-header">
        <div class="title-medium">Resource Actions</div>
        ${renderHelp('modeling', 'api:resource-actions')}
      </div>
      <ui-list class="action-presets">
        <ui-list-item lines="two">
          <ui-icon slot="start" class="action-icon">${apiActionKindToIcon('read')}</ui-icon>
          <span>${apiActionKindToLabel('read')}</span>
          <span slot="supporting-text">Read a single object</span>
          <ui-switch
            slot="end"
            .checked=${read}
            name="read"
            @change=${this.handleActionToggle}
            ?disabled=${this.readonly}
          ></ui-switch>
        </ui-list-item>
        <ui-list-item lines="two">
          <ui-icon slot="start" class="action-icon">${apiActionKindToIcon('update')}</ui-icon>
          <span>${apiActionKindToLabel('update')}</span>
          <span slot="supporting-text">Update the object</span>
          <ui-switch
            slot="end"
            .checked=${update}
            name="update"
            @change=${this.handleActionToggle}
            ?disabled=${this.readonly}
          ></ui-switch>
        </ui-list-item>
        <ui-list-item lines="two">
          <ui-icon slot="start" class="action-icon">${apiActionKindToIcon('delete')}</ui-icon>
          <span>${apiActionKindToLabel('delete')}</span>
          <span slot="supporting-text">Delete from the collection</span>
          <ui-switch
            slot="end"
            .checked=${del}
            name="delete"
            @change=${this.handleActionToggle}
            ?disabled=${this.readonly}
          ></ui-switch>
        </ui-list-item>
      </ui-list>
    </div>`
  }
}
