import { html, LitElement, nothing, TemplateResult } from 'lit'
import type { DataCatalogSchemaWithVersion, DataCatalogScope } from '@api-client/core/models/DataCatalog.js'
import type { DataCatalogLifecycle } from '@api-client/core/models/DataCatalogVersion.js'
import { state, property } from 'lit/decorators.js'
import type { LiveData } from '../../reactive/LiveData.js'
import type { DataCatalogTableSelectionDetail } from '../../elements/data-table/internals/DataCatalogTable.js'

import '../../md/icons/ui-icon.js'
import '../../md/button/ui-button.js'
import '../../md/button/ui-button-group.js'
import '../../md/icon-button/ui-icon-button.js'
import '../../md/divider/ui-divider.js'
import '../../md/text-field/ui-outlined-text-field.js'
import '../../md/progress/ui-circular-progress.js'
import '../../elements/data-table/data-catalog-table.js'

/**
 * The detail for the data catalog query event.
 */
export interface DataCatalogQueryEventDetail {
  /**
   * The scope of the data catalog query.
   */
  scope?: DataCatalogScope
  /**
   * The lifecycle of the data catalog query.
   */
  lifecycle?: DataCatalogLifecycle
  /**
   * The query string to search for. When not set, it resets the search
   * and requests for the regular page.
   */
  query?: string
}

function isDataCatalogScope(value: string): value is DataCatalogScope {
  return ['public', 'private', 'organization'].includes(value)
}

function isDataCatalogLifecycle(value: string): value is DataCatalogLifecycle {
  return ['stable', 'beta', 'dev'].includes(value)
}

/**
 * A browser for the data catalog.
 *
 * This element allows the user to browse the data catalog, filter by scope and lifecycle, and search by text.
 * It renders a table with the results and pagination support.
 *
 * @fires CustomEvent<DataCatalogQueryEventDetail> query - Dispatched when the user searches or changes filters.
 * @fires CustomEvent<DataCatalogTableSelectionDetail> select - Dispatched when the selection in the table changes.
 *
 * @csspart loader - The container for the loader.
 * @csspart error - The container for the error message.
 * @csspart controls - The container for the search and filters.
 * @csspart results - The data table element.
 * @csspart search - The search input field.
 * @csspart filters - The container for the filters.
 */
export default class DataCatalogBrowser extends LitElement {
  /**
   * When set, the table will render additional column with a button to directly select the item.
   * @attribute
   */
  @property({ type: Boolean }) accessor directSelect: boolean | undefined
  /**
   * The data to display in the table.
   * @attribute
   */
  @property({ type: Object }) accessor items: LiveData<DataCatalogSchemaWithVersion[]> | undefined
  /**
   * The current state of the browser. The controller managing the query service will set this property
   * depending on the query state.
   * @attribute
   */
  @property({ type: String }) accessor state: 'loading' | 'ready' | 'error' = 'loading'

  /**
   * Required when in error state. Renders the title of the error message.
   * @attribute
   */
  @property({ type: String }) accessor errorTitle: string | undefined
  /**
   * Required when in error state. Renders the description of the error message.
   * @attribute
   */
  @property({ type: String }) accessor errorMessage: string | undefined

  #selection?: DataCatalogTableSelectionDetail

  /**
   * The currently selected item in the table, if any.
   * @readonly
   */
  get selection(): DataCatalogTableSelectionDetail | undefined {
    return this.#selection ? { ...this.#selection } : undefined
  }

  /**
   * The current scope of the data catalog query.
   */
  @state() accessor scope: DataCatalogScope | undefined

  /**
   * The current lifecycle of the data catalog query.
   */
  @state() accessor lifecycle: DataCatalogLifecycle | undefined

  /**
   * The current query string.
   */
  @state() accessor userQuery: string | undefined

  /**
   * Dispatches the `query` event with the current filters.
   * The parent application needs to track state of the query. If the `query` event
   * has the `query` property set to `undefined` it means the search should be reset.
   */
  query(): void {
    this.dispatchEvent(
      new CustomEvent<DataCatalogQueryEventDetail>('query', {
        detail: this.getFilters(),
      })
    )
  }

  /**
   * Returns the map of current filters.
   */
  protected getFilters(): DataCatalogQueryEventDetail {
    const result: DataCatalogQueryEventDetail = {}
    if (this.userQuery) {
      result.query = this.userQuery
    }
    if (this.scope) {
      result.scope = this.scope
    }
    if (this.lifecycle) {
      result.lifecycle = this.lifecycle
    }
    return result
  }

  protected handleSearchKeydown(e: KeyboardEvent): void {
    const input = e.currentTarget as HTMLInputElement
    const value = input.value.trim()
    if (e.key === 'Enter' && value) {
      this.userQuery = value
      this.query()
    } else if (e.key === 'Escape') {
      e.preventDefault()
      e.stopPropagation()
      input.value = ''
      this.userQuery = undefined
      this.query()
    }
  }

  protected handleFilterClick(e: Event): void {
    const target = e.target as HTMLElement
    const field = target.dataset.field
    const value = target.dataset.value
    if (!field || !value) {
      return
    }
    if (field === 'scope' && isDataCatalogScope(value)) {
      if (this.scope === value) {
        this.scope = 'public' // Reset to default if the same value is clicked
      } else {
        this.scope = value
      }
    } else if (field === 'lifecycle' && isDataCatalogLifecycle(value)) {
      if (this.lifecycle === value) {
        this.lifecycle = 'stable' // Reset to default if the same value is clicked
      } else {
        this.lifecycle = value
      }
    }
    this.query()
  }

  protected handleTableSelect(e: CustomEvent<DataCatalogTableSelectionDetail>): void {
    const { item, version } = e.detail
    if (!item || !version) {
      this.#selection = undefined
    } else {
      this.#selection = { item, version }
    }
    this.dispatchEvent(new CustomEvent<DataCatalogTableSelectionDetail>('select', { detail: this.selection }))
  }

  override render(): TemplateResult | typeof nothing {
    const { state } = this
    if (state === 'loading') {
      return this.#renderLoader()
    }
    if (state === 'error') {
      return this.#renderError()
    }
    return this.#renderBody()
  }

  #renderLoader(): TemplateResult {
    return html`
      <div class="browse-dialog centered" part="loader">
        <ui-circular-progress indeterminate></ui-circular-progress>
        <p>One second...</p>
      </div>
    `
  }

  #renderError(): TemplateResult {
    return html`
      <div class="browse-dialog centered error" part="error">
        <h2 class="title-large">${this.errorTitle}</h2>
        <p class="body-large">${this.errorMessage}</p>
        <ui-button @click="${this.query}">Retry</ui-button>
      </div>
    `
  }

  #renderBody(): TemplateResult {
    return html`<div class="controls" part="controls">${this.#renderSearchInput()} ${this.#renderFilters()}</div>
      <data-catalog-table
        ?directSelect="${this.directSelect}"
        .items="${this.items}"
        @select="${this.handleTableSelect}"
        @scrollend="${this.query}"
        class="results"
        part="results"
      ></data-catalog-table>`
  }

  #renderSearchInput(): TemplateResult {
    return html`
      <ui-outlined-text-field
        id="domain-search"
        label="Search"
        type="search"
        class="search"
        part="search"
        .value="${this.userQuery ?? ''}"
        @keydown="${this.handleSearchKeydown}"
      ></ui-outlined-text-field>
    `
  }

  #renderFilters(): TemplateResult {
    const isPublic = this.scope === 'public'
    const isOrganization = this.scope === 'organization'
    const isPrivate = this.scope === 'private'
    const isStable = this.lifecycle === 'stable'
    const isBeta = this.lifecycle === 'beta'
    const isDev = this.lifecycle === 'dev'
    return html`<div class="filters" @click="${this.handleFilterClick}" part="filters">
      <ui-button-group type="connected">
        <ui-button color="tonal" size="xs" toggle data-field="scope" data-value="public" ?selected="${isPublic}"
          >${isPublic ? html`<ui-icon slot="icon">check</ui-icon>` : nothing}Public</ui-button
        >
        <ui-button
          color="tonal"
          size="xs"
          toggle
          data-field="scope"
          data-value="organization"
          ?selected="${isOrganization}"
          >${isOrganization ? html`<ui-icon slot="icon">check</ui-icon>` : nothing}Organization</ui-button
        >
        <ui-button color="tonal" size="xs" toggle data-field="scope" data-value="private" ?selected="${isPrivate}"
          >${isPrivate ? html`<ui-icon slot="icon">check</ui-icon>` : nothing}Private</ui-button
        >
      </ui-button-group>
      <ui-button-group type="connected">
        <ui-button color="tonal" size="xs" toggle data-field="lifecycle" data-value="stable" ?selected="${isStable}"
          >${isStable ? html`<ui-icon slot="icon">check</ui-icon>` : nothing}Stable</ui-button
        >
        <ui-button color="tonal" size="xs" toggle data-field="lifecycle" data-value="beta" ?selected="${isBeta}"
          >${isBeta ? html`<ui-icon slot="icon">check</ui-icon>` : nothing}Beta</ui-button
        >
        <ui-button color="tonal" size="xs" toggle data-field="lifecycle" data-value="dev" ?selected="${isDev}"
          >${isDev ? html`<ui-icon slot="icon">check</ui-icon>` : nothing}Dev</ui-button
        >
      </ui-button-group>
    </div>`
  }
}
