import { html, TemplateResult, CSSResult, PropertyValues } from 'lit'
import { property, query } from 'lit/decorators.js'
import { live } from 'lit/directives/live.js'
import { type IProperty } from '@api-client/core/models/Property.js'
import { type IServer, Server } from '@api-client/core/models/Server.js'
import elementStyles from './ServerEditor.styles.js'
import Input from '../../md/input/Input.js'
import ApiElement from '../ApiElement.js'
import '../../md/text-field/ui-filled-text-field.js'
import '../../md/icons/ui-icon.js'
import '../../md/icon-button/ui-icon-button.js'

export const DefaultProtocols = ['http:', 'https:']

/**
 * An element to render a view for a server definition with a form to edit it.
 *
 * @fires change - When a value change.
 * @fires delete - When the user requested server definition delete.
 */
export default class ServerEditor extends ApiElement {
  static override get styles(): CSSResult[] {
    return [elementStyles]
  }

  /**
   * The base URI of the server.
   *
   * Note, the URL can contain URI templates (e.g. `http://{host}.api.com/v1`)
   * In this case the variable is replaced with the system or project variables.
   *
   * For simplicity, the `uri` can be the full base URI with protocol, host, and the `basePath`
   *
   * @attribute
   */
  @property({ type: String }) accessor uri: string | undefined

  /**
   * Usually included in the `uri`. When the `uri` is missing a protocol
   * this is then used.
   *
   * @attribute
   */
  @property({ type: String }) accessor protocol: string | undefined

  /**
   * The base path for the server. It starts with the `/`.
   * When set, it is appended to the `uri` value.
   *
   * @attribute
   */
  @property({ type: String }) accessor basePath: string | undefined

  /**
   * Optional description of the server.
   *
   * @attribute
   */
  @property({ type: String }) accessor description: string | undefined

  /**
   * Optional list of variables to set on the environment.
   * When set it evaluates the final URI value against these properties.
   */
  @property({ type: Array }) accessor variables: IProperty[] | undefined

  /**
   * The computed value of the full URI from the values.
   */
  protected valueInternal?: string

  /**
   * The server logic used with computations.
   */
  protected serverInternal = new Server()

  @query('#protocolInput', false) protected accessor protocolInput: HTMLInputElement | null = null

  protected override willUpdate(cp: PropertyValues<this>): void {
    if (cp.has('uri') || cp.has('protocol') || cp.has('basePath') || cp.has('description')) {
      this.setupValue()
    }
    super.willUpdate(cp)
  }

  /**
   * Updates the value of the generated base URI.
   * This can be called from the outside of the component to force computation.
   * This is helpful when deep-updating variables.
   */
  generateValue(): void {
    this.setupValue()
    this.requestUpdate()
  }

  protected setupValue(): void {
    const { serverInternal } = this
    this.updateServer()
    this.valueInternal = serverInternal.readUri(this.variables)
  }

  protected updateServer(): void {
    const { serverInternal, uri = '', protocol, basePath, description } = this
    serverInternal.uri = uri
    serverInternal.protocol = protocol
    serverInternal.basePath = basePath
    serverInternal.description = description
  }

  /**
   * Creates a schema for the current values.
   */
  getSchema(): IServer {
    this.updateServer()
    return this.serverInternal.toJSON()
  }

  /**
   * Updates name/value of a parameter on a specific index.
   */
  updateProperty(field: 'uri' | 'basePath' | 'protocol', value: string): void {
    this[field] = value
    this.notifyChange()
  }

  protected handleUriChange(e: Event): void {
    const input = e.target as Input
    this.updateProperty('uri', input.value)
    input.checkValidity()
  }

  protected handleBasePathChange(e: Event): void {
    const input = e.target as Input
    this.updateProperty('basePath', input.value)
    input.checkValidity()
  }

  protected handleProtocolChange(e: Event): void {
    const input = e.target as Input
    this.updateProperty('protocol', input.value)
    input.checkValidity()
  }

  protected handleDelete(): void {
    this.dispatchEvent(new Event('delete'))
  }

  override render(): TemplateResult {
    return html`
      ${this.renderHeader()}
      <form>${this.renderUriInput()} ${this.renderProtocolInput()} ${this.renderBasePathInput()}</form>
      ${this.renderValue()}
    `
  }

  protected renderHeader(): TemplateResult {
    return html`
      <div class="header">
        <div class="section-title">Server</div>
        ${this.renderDeleteButton()}
      </div>
    `
  }

  protected renderUriInput(): TemplateResult {
    return html`
      <div class="form-item">
        <ui-filled-text-field
          required
          name="uri"
          .value="${live(this.uri || '')}"
          label="URI"
          @change="${this.handleUriChange}"
        ></ui-filled-text-field>
      </div>
    `
  }

  protected renderProtocolInput(): TemplateResult {
    return html` <div class="form-item">
      <ui-filled-text-field
        name="protocol"
        .value="${live(this.protocol || '')}"
        .list="${DefaultProtocols}"
        label="Protocol (optional)"
        @change="${this.handleProtocolChange}"
      ></ui-filled-text-field>
    </div>`
  }

  protected renderBasePathInput(): TemplateResult {
    return html` <div class="form-item">
      <ui-filled-text-field
        name="basePath"
        .value="${live(this.basePath || '')}"
        label="Base path (optional)"
        @change="${this.handleBasePathChange}"
      ></ui-filled-text-field>
    </div>`
  }

  protected renderValue(): TemplateResult | string {
    const { valueInternal } = this
    if (!valueInternal) {
      return ''
    }
    return html`
      <div class="uri-label">Server URI</div>
      <div class="value-preview">${valueInternal}</div>
    `
  }

  protected renderDeleteButton(): TemplateResult {
    return html`
      <ui-icon-button aria-label="Remove this server" @click="${this.handleDelete}">
        <ui-icon icon="deleteOutline" role="presentation"></ui-icon>
      </ui-icon-button>
    `
  }
}
