import { html, TemplateResult } from 'lit'
import { property } from 'lit/decorators.js'
import { ApiDocumentationBase } from './ApiDocumentationBase.js'
import { QueryParameterProcessor } from '../../amf/lib/QueryParameterProcessor.js'
import { Events } from '../../events/amf/Events.js'
import { OperationParameter } from '../../amf/types.js'
import RadioElement from '../../md/radio/internals/RadioElement.js'
import '../../define/amf/api-payload-document.js'
import '../../define/amf/api-parameter-document.js'
import '../../md/radio/ui-radio.js'
import '../../md/divider/ui-divider.js'
import { IApiNodeShape, IShapeUnion } from '@api-client/core/amf/definitions/Shapes.js'
import {
  IApiEndPoint,
  IApiParameter,
  IApiPayload,
  IApiRequest,
  IApiServer,
} from '@api-client/core/amf/definitions/Api.js'

/**
 * A web component that renders the documentation page for an API request object.
 */
export default class ApiRequestDocument extends ApiDocumentationBase {
  protected requestValue?: IApiRequest

  protected payloadsValue?: IApiPayload[]

  /**
   * @returns true when has cookie parameters definition
   */
  get hasCookieParameters(): boolean {
    const request = this.requestValue
    if (!request) {
      return false
    }
    return Array.isArray(request.cookieParameters) && !!request.cookieParameters.length
  }

  /**
   * @returns true when has headers parameters definition
   */
  get hasHeaders(): boolean {
    const request = this.requestValue
    if (!request) {
      return false
    }
    return Array.isArray(request.headers) && !!request.headers.length
  }

  /**
   * @returns true when has query parameters definition
   */
  get hasQueryParameters(): boolean {
    const request = this.requestValue
    if (!request) {
      return false
    }
    return Array.isArray(request.queryParameters) && !!request.queryParameters.length
  }

  /**
   * @returns The combined list of path parameters in the server, endpoint, and the request.
   */
  get uriParameters(): IApiParameter[] {
    const request = this.requestValue
    const { server, endpoint } = this
    let result: IApiParameter[] = []
    if (server && Array.isArray(server.variables) && server.variables.length) {
      result = result.concat(server.variables)
    }
    if (endpoint && Array.isArray(endpoint.parameters) && endpoint.parameters.length) {
      result = result.concat(endpoint.parameters)
    }
    if (request && Array.isArray(request.uriParameters) && request.uriParameters.length) {
      result = result.concat(request.uriParameters)
    }
    return result
  }

  /**
   * @returns true when has query string definition
   */
  get hasQueryString(): boolean {
    const request = this.requestValue
    if (!request) {
      return false
    }
    return !!request.queryString
  }

  get payloadValue(): IApiPayload | undefined {
    const { mimeType } = this
    const payloads = this.payloadsValue
    if (!Array.isArray(payloads) || !payloads.length) {
      return undefined
    }
    if (!mimeType) {
      return payloads[0]
    }
    return payloads.find((item) => item.mediaType === mimeType)
  }

  get request(): IApiRequest | undefined {
    return this.requestValue
  }

  set request(value: IApiRequest | undefined) {
    const old = this.requestValue
    if (old === value) {
      return
    }
    this.requestValue = value
    this.processGraph()
  }

  /**
   * When set it opens the parameters section
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor parametersOpened: boolean | undefined

  /**
   * When set it opens the headers section
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor headersOpened: boolean | undefined

  /**
   * When set it opens the cookies section
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor cookiesOpened: boolean | undefined

  /**
   * When set it opens the payload section
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor payloadOpened: boolean | undefined

  /**
   * The currently selected media type for the payloads.
   * @attribute
   */
  @property({ type: String, reflect: true }) accessor mimeType: string | undefined

  /**
   * The current server in use.
   * It adds path parameters defined for the server.
   */
  @property({ type: Object }) accessor server: IApiServer | undefined

  /**
   * The parent endpoint of this request.
   * It adds path parameters defined for the endpoint.
   */
  @property({ type: Object }) accessor endpoint: IApiEndPoint | undefined

  protected queryParametersValue?: OperationParameter[]

  override async processGraph(): Promise<void> {
    this.mimeType = undefined
    await this.queryRequest()
    await this.queryPayloads()
    await this.processQueryParameters()
    this.preselectMime()
    this.requestUpdate()
    await this.updateComplete
  }

  /**
   * Queries the store for the request data, when needed.
   */
  async queryRequest(): Promise<void> {
    const { domainId } = this
    if (!domainId) {
      // this.requestValue = undefined;
      return
    }
    if (this.requestValue && this.requestValue.id === domainId) {
      // in case the request model was provided via the property setter.
      return
    }
    try {
      const info = await Events.Request.get(this, domainId)
      this.requestValue = info
    } catch (e) {
      this.requestValue = undefined
      const ex = e as Error
      Events.Telemetry.exception(this, ex.message, false)
      Events.Reporting.error(this, ex, `Unable to query for API request data: ${ex.message}`, this.localName)
    }
  }

  async queryPayloads(): Promise<void> {
    const { request } = this
    if (!request || !request.payloads.length) {
      this.payloadsValue = undefined
      return
    }
    this.payloadsValue = request.payloads
  }

  /**
   * Creates a parameter
   */
  async processQueryParameters(): Promise<void> {
    this.queryParametersValue = undefined
    const { request } = this
    if (!request) {
      return
    }
    const nodeShape = request.queryString as IApiNodeShape
    if (!nodeShape) {
      return
    }
    const factory = new QueryParameterProcessor()
    const params = factory.collectOperationParameters(request.queryString as IShapeUnion, 'query')
    this.queryParametersValue = params
  }

  /**
   * Pre-selects when needed the mime type for the current payload.
   */
  protected preselectMime(): void {
    const { mimeType } = this
    const payloads = this.payloadsValue
    if (!Array.isArray(payloads) || !payloads.length) {
      return
    }
    const mime: string[] = []
    let hasCurrent = false
    payloads.forEach((item) => {
      if (item.mediaType) {
        mime.push(item.mediaType)
        if (!hasCurrent && item.mediaType === mimeType) {
          hasCurrent = true
        }
      }
    })
    // do not change the selection when already exist.
    if (hasCurrent) {
      return
    }
    if (!mime.length) {
      return
    }
    const [first] = mime
    this.mimeType = first
    this.notifyMime()
  }

  protected mediaTypeSelectHandler(e: Event): void {
    const node = e.target as RadioElement
    const { value } = node
    this.mimeType = value
    this.notifyMime()
  }

  /**
   * Dispatches the `mimechange` event.
   */
  protected notifyMime(): void {
    this.dispatchEvent(new Event('mimechange'))
  }

  override render(): TemplateResult {
    const { request, server, endpoint } = this
    if (!request && !server && !endpoint) {
      return html``
    }
    return html`
      ${this.queryParamsTemplate()} ${this.headersTemplate()} ${this.cookiesTemplate()} ${this.payloadTemplate()}
    `
  }

  /**
   * @return The template for the query parameters
   */
  protected queryParamsTemplate(): TemplateResult | string {
    const { uriParameters, hasQueryParameters } = this
    if (!hasQueryParameters && !uriParameters.length) {
      return ''
    }
    const { request } = this
    let queryParameters: IApiParameter[] = []
    if (request && Array.isArray(request.queryParameters)) {
      queryParameters = request.queryParameters
    }
    if (!queryParameters.length && this.queryParametersValue && this.queryParametersValue.length) {
      queryParameters = this.queryParametersValue.map((i) => i.parameter)
    }
    const content: (TemplateResult | string)[] = []
    uriParameters.forEach((param) => content.push(this.schemaItemTemplate(param, 'uri')))
    queryParameters.forEach((param) => content.push(this.schemaItemTemplate(param, 'query')))
    return this.paramsSectionTemplate(
      'Parameters',
      'parametersOpened',
      content.map((item, index) => {
        if (index === content.length - 1) {
          return item
        }
        return html`
          ${item}
          <ui-divider></ui-divider>
        `
      })
    )
  }

  /**
   * @return The template for the headers
   */
  protected headersTemplate(): TemplateResult | string {
    if (!this.hasHeaders) {
      return ''
    }
    const { request } = this
    const content = (request as IApiRequest).headers.map((param) => this.schemaItemTemplate(param, 'header'))
    return this.paramsSectionTemplate('Headers', 'headersOpened', content)
  }

  /**
   * @return The template for the cookies list section
   */
  protected cookiesTemplate(): TemplateResult | string {
    if (!this.hasCookieParameters) {
      return ''
    }
    const { request } = this
    const content = (request as IApiRequest).cookieParameters.map((param) => this.schemaItemTemplate(param, 'cookie'))
    return this.paramsSectionTemplate('Cookies', 'cookiesOpened', content)
  }

  /**
   * @return The template for the payload section
   */
  protected payloadTemplate(): TemplateResult | string {
    const payload = this.payloadValue
    if (!payload) {
      return ''
    }
    const content = html`
      ${this.payloadSelectorTemplate()}
      <api-payload-document .domainId="${payload.id}" .payload="${payload}"></api-payload-document>
    `
    return this.paramsSectionTemplate('Request body', 'payloadOpened', content)
  }

  /**
   * @return The template for the payload media type selector.
   */
  protected payloadSelectorTemplate(): TemplateResult | string {
    const payloads = this.payloadsValue
    if (!Array.isArray(payloads) || payloads.length < 2) {
      return ''
    }
    const mime: string[] = []
    payloads.forEach((item) => {
      if (item.mediaType) {
        mime.push(item.mediaType)
      }
    })
    if (!mime.length) {
      return ''
    }
    const mimeType = this.mimeType || mime[0]
    return html`
      <div class="media-type-selector">
        <span id="body-mime">Body content type</span>
        ${mime.map(
          (item) => html`
            <label class="label-large">
              <ui-radio
                class="response-toggle"
                name="responseMime"
                value="${item}"
                .checked="${item === mimeType}"
                @change="${this.mediaTypeSelectHandler}"
              ></ui-radio>
              ${item}
            </label>
          `
        )}
      </div>
    `
  }
}
