import { html, nothing, TemplateResult } from 'lit'
import { property, state } from 'lit/decorators.js'
import type { IRequestLog } from '@api-client/core/models/RequestLog.js'
import type { IRequest } from '@api-client/core/models/Request.js'
import { EventTypes } from '../../events/amf/EventTypes.js'
import { ApiNavigationEvent } from '../../events/amf/NavigationEvents.js'
import { Oauth2Credentials } from '../authorization/types.js'
import ApiElement from '../ApiElement.js'
import '../../define/amf/api-request-editor.js'
import '../../define/http/http-request-log.js'

export default class ApiRequest extends ApiElement {
  /**
   * By default application hosting the element must set `domainId`
   * property. When using `api-navigation` element
   * by setting this property the element listens for navigation events
   * and updates the state
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor handleNavigationEvents: boolean | undefined

  /**
   * When set it renders the URL input above the URL parameters.
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor urlEditor: boolean | undefined

  /**
   * When set it renders a label with the computed URL.
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor urlLabel: boolean | undefined

  /**
   * A base URI for the API. To be set if RAML spec is missing `baseUri`
   * declaration and this produces invalid URL input. This information
   * is passed to the URL editor that prefixes the URL with `baseUri` value
   * if passed URL is a relative URL.
   * @attribute
   */
  @property({ type: String }) accessor baseUri: string | undefined

  /**
   * OAuth2 redirect URI.
   * This value **must** be set in order for OAuth 1/2 to work properly.
   * @attribute
   */
  @property({ type: String }) accessor redirectUri: string | undefined

  /**
   * If set it computes `hasOptional` property and shows checkbox in the
   * form to show / hide optional properties.
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor allowHideOptional: boolean | undefined

  /**
   * When set, renders "add custom" item button.
   * If the element is to be used without AMF model this should always
   * be enabled. Otherwise users won't be able to add a parameter.
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor allowCustom: boolean | undefined

  /**
   * Holds the value of the currently selected server
   * Data type: URI
   * @attribute
   */
  @property({ type: String }) accessor serverValue: string | undefined

  /**
   * Holds the type of the currently selected server
   * Values: `server` | `slot` | `custom`
   * @attribute
   */
  @property({ type: String }) accessor serverType: string | undefined

  /**
   * Optional property to set
   * If true, the server selector is not rendered
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor noServerSelector: boolean | undefined

  /**
   * Optional property to set
   * If true, the server selector custom base URI option is rendered
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor allowCustomBaseUri: boolean | undefined

  /**
   * List of credentials source
   */
  @property({ type: Array }) accessor credentialsSource: Oauth2Credentials[] | undefined

  protected domainIdValue?: string

  /**
   * The domain id (AMF's id) of an API operation.
   */
  get domainId(): string | undefined {
    return this.domainIdValue
  }

  @property({ type: String })
  set domainId(value: string | undefined) {
    const old = this.domainIdValue
    /* istanbul ignore if */
    if (old === value) {
      return
    }
    this.domainIdValue = value
    this.requestUpdate('domainId', old)
    this.domainIdChanged(value)
  }

  /**
   * The latest response for the current request.
   */
  @state() accessor response: IRequestLog | undefined

  constructor() {
    super()
    this.navigationHandler = this.navigationHandler.bind(this)
    this.handleNavigationEvents = false
    this.allowHideOptional = false
    this.allowCustom = false
    this.noServerSelector = false
    this.allowCustomBaseUri = false
  }

  override connectedCallback(): void {
    super.connectedCallback()
    const node = this.eventsTarget
    node.addEventListener(EventTypes.Navigation.apiNavigate, this.navigationHandler as EventListener)
  }

  override disconnectedCallback(): void {
    super.disconnectedCallback()
    const node = this.eventsTarget
    node.removeEventListener(EventTypes.Navigation.apiNavigate, this.navigationHandler as EventListener)
  }

  /**
   * Serializes the state of the request editor.
   *
   * Note this is the same object as the one passed to the detail of the api request event.
   */
  async serialize(): Promise<IRequest> {
    const { shadowRoot } = this
    if (!shadowRoot) {
      throw new Error(`The component is not yet initialized.`)
    }
    const editor = shadowRoot.querySelector('api-request-editor')
    if (!editor) {
      throw new Error(`The request editor has been manually removed from the DOM.`)
    }
    return editor.serialize()
  }

  /**
   * Clears response panel when the `domainId` change.
   */
  protected domainIdChanged(id?: string): void {
    if (!id) {
      return
    }
    this.clearResponse()
  }

  /**
   * Clears response panel.
   */
  clearResponse(): void {
    if (this.response) {
      this.response = undefined
    }
  }

  /**
   * Handles navigation events and computes available servers.
   *
   * When `handleNavigationEvents` is set then it also manages the selection.
   */
  protected navigationHandler(e: ApiNavigationEvent): void {
    if (this.handleNavigationEvents) {
      const { domainId, domainType } = e.detail
      this.domainId = domainType === 'operation' ? domainId : undefined
    }
  }

  /**
   * Retargets the change event from the editor.
   */
  protected changeHandler(): void {
    this.notifyChange()
  }

  protected handleResponse(e: CustomEvent<IRequestLog>): void {
    this.response = e.detail
  }

  override render(): TemplateResult {
    return html`${this.requestTemplate()}${this.responseTemplate()}`
  }

  /**
   * @returns A template for the request panel
   */
  protected requestTemplate(): TemplateResult {
    const {
      redirectUri,
      domainId,
      urlEditor,
      urlLabel,
      baseUri,
      eventsTarget,
      allowHideOptional,
      allowCustom,
      serverValue,
      serverType,
      noServerSelector,
      allowCustomBaseUri,
      credentialsSource,
    } = this

    return html` <api-request-editor
      .redirectUri="${redirectUri}"
      .domainId="${domainId}"
      ?urlEditor="${urlEditor}"
      ?urlLabel="${urlLabel}"
      .baseUri="${baseUri}"
      .eventsTarget="${eventsTarget}"
      ?allowHideOptional="${allowHideOptional}"
      ?allowCustom="${allowCustom}"
      .serverValue="${serverValue}"
      .serverType="${serverType}"
      ?noServerSelector="${noServerSelector}"
      ?allowCustomBaseUri="${allowCustomBaseUri}"
      .credentialsSource="${credentialsSource}"
      @change="${this.changeHandler}"
      @response="${this.handleResponse}"
    >
      <slot name="custom-base-uri" slot="custom-base-uri"></slot>
    </api-request-editor>`
  }

  /**
   * @returns A template for the response view
   */
  protected responseTemplate(): TemplateResult | typeof nothing {
    const { response } = this
    if (!response) {
      return nothing
    }
    return html`<http-request-log .httpLog="${response}"></http-request-log>`
  }
}
