import { html, TemplateResult } from 'lit'
import type { ContextEvent } from '@api-client/core/events/BaseEvents.js'
import type { IAmfDocument } from '@api-client/core/amf/definitions/Amf.js'
import { OidcAuthorization } from '@api-client/core/authorization/OidcAuthorization.js'
import { OAuth2Authorization } from '@api-client/core/authorization/OAuth2Authorization.js'
import type {
  IOAuth2Authorization,
  IOidcTokenError,
  IOidcTokenInfo,
  ITokenInfo,
} from '@api-client/core/models/Authorization.js'
import { DemoPage } from '../../../src/demo/DemoPage.js'
import type { IRoute, IRouteResult } from '../../../src/mixins/RouteMixin.js'
import * as Router from '../../../src/lib/route.js'
import reactive from '../../../src/decorators/reactive.js'
import type { ApiNavigationEvent } from '../../../src/events/amf/NavigationEvents.js'
import { DomEventsAmfStore } from '../../../src/amf/store/DomEventsAmfStore.js'
import { ReportingErrorEventDetail } from '../../../src/events/amf/ReportingEvents.js'
import { EventTypes } from '../../../src/events/amf/EventTypes.js'
import '../../../src/define/amf/api-navigation.js'
import '../../../src/md/icons/ui-icon.js'
import '../../../src/md/list/ui-list-item.js'
import '../../../src/md/dropdown-list/ui-dropdown-list.js'
import '../../../src/md/list/ui-list.js'
import '../../../src/md/button/ui-filled-button.js'
import '../../../src/md/button/ui-text-button.js'

export interface ApiDemoSource {
  label: string
  file: string
}

export abstract class AmfDemoPage extends DemoPage {
  @reactive() accessor initialized = false

  @reactive() accessor loaded = false

  /**
   * Currently loaded file.
   */
  @reactive() accessor selectedFile: string | undefined

  /**
   * Currently loaded file.
   */
  @reactive() accessor redirectUri: string | undefined

  store: DomEventsAmfStore

  /**
   * When set the endpoint section in navigation is opened by default.
   */
  endpointsOpened: boolean

  /**
   * When set the documentation section in navigation is opened by default.
   */
  docsOpened: boolean

  /**
   * When set the types section in navigation is opened by default.
   */
  typesOpened: boolean

  /**
   * When set the security section in navigation is opened by default.
   */
  securityOpened: boolean

  /**
   * AMF model read from the API model file downloaded after initialization.
   */
  amf?: IAmfDocument

  /**
   * When set the API Navigation element won't be rendered.
   */
  noApiNavigation: boolean

  apis: ApiDemoSource[]

  static get routes(): IRoute[] {
    return [
      {
        pattern: '/file/(?<file>[^/]*)',
        method: 'fileRoute',
      },
    ]
  }

  constructor() {
    super()
    this.store = new DomEventsAmfStore(window)
    this.store.listen()

    this.loaded = false
    this.initialized = false
    this.endpointsOpened = true
    this.docsOpened = false
    this.typesOpened = false
    this.securityOpened = false
    this.noApiNavigation = false

    this.redirectUri = `${window.location.origin}/demo/oauth-popup.html`

    window.addEventListener(EventTypes.Navigation.apiNavigate, this._navChanged.bind(this) as EventListener)

    this.apis = [
      {
        file: 'models/demo-api.json',
        label: 'Demo API',
      },
    ]
  }

  override firstRender(): void {
    super.firstRender()
    if (this.initialized) {
      this.selectApiWhenNeeded()
    }
  }

  async autoLoad(): Promise<void> {
    await this.loadMonaco()
    this.initializeRouting()
    this.initialized = true
    // router initializes itself in a raf
    requestAnimationFrame(() => {
      this.selectApiWhenNeeded()
    })
  }

  selectApiWhenNeeded(): void {
    if (this.selectedFile) {
      return
    }
    const [api] = this.apis
    if (!api) {
      return
    }
    Router.navigate('file', api.file)
  }

  /**
   * Handler for the API selection change
   */
  _apiChanged(e: CustomEvent): void {
    const target = e.detail.item as HTMLElement | undefined
    if (!target) {
      return
    }
    const { src } = target.dataset
    if (!src) {
      return
    }
    Router.navigate('file', src)
  }

  fileRoute(info: IRouteResult): void {
    const { params } = info
    if (!params || !params.file) {
      return
    }
    const file = params.file as string
    if (file === this.selectedFile) {
      return
    }
    this.selectedFile = file
    this._loadFile(file)
  }

  /**
   * Handler for the API errors
   */
  _errorHandler(e: CustomEvent): void {
    const info = e.detail as ReportingErrorEventDetail
    const { description, component = 'Unknown component', error } = info
    console.error(`[${component}]: ${description}`)
    console.error(error)
  }

  async _loadFile(file: string): Promise<void> {
    this.loaded = false
    const response = await fetch(`/demo/${file}`)
    let data = await response.json()
    if (Array.isArray(data)) {
      ;[data] = data
    }
    this.amf = data
    this.store.amf = data
    this.loaded = true
    console.log(data)
    this.render()
  }

  oauth2authorizeHandler(e: ContextEvent<IOAuth2Authorization, ITokenInfo>): void {
    e.preventDefault()
    const config = { ...e.detail } as IOAuth2Authorization
    e.detail.result = this.authorizeOauth2(config)
  }

  /**
   * Authorize the user using provided settings.
   *
   * @param settings The authorization configuration.
   */
  async authorizeOauth2(settings: IOAuth2Authorization): Promise<ITokenInfo> {
    const auth = new OAuth2Authorization(settings)
    auth.checkConfig()
    return auth.authorize() as Promise<ITokenInfo>
  }

  oidcAuthorizeHandler(e: ContextEvent<IOAuth2Authorization, (IOidcTokenInfo | IOidcTokenError)[]>): void {
    const config = { ...e.detail } as IOAuth2Authorization
    e.detail.result = this.authorizeOidc(config)
  }

  /**
   * Authorize the user using provided settings.
   *
   * @param settings The authorization configuration.
   */
  async authorizeOidc(settings: IOAuth2Authorization): Promise<(IOidcTokenInfo | IOidcTokenError)[]> {
    const auth = new OidcAuthorization(settings)
    auth.checkConfig()
    return auth.authorize()
  }

  /**
   * This method to be overridden in child class to handle navigation.
   * @param e Dispatched navigation event
   */
  _navChanged(e: ApiNavigationEvent): void {
    const { domainId, domainType } = e.detail
    console.log(`Navigation changed. Type: ${domainId}, selected: ${domainType}`)
  }

  /**
   * This method to be overridden in child class to render API options.
   * @returns HTML template for apis dropdown options.
   */
  _apiListTemplate(): TemplateResult[] {
    const { apis } = this
    return apis.map(
      (item) => html` <ui-list-item role="menuitem" data-src="${item.file}">${item.label}</ui-list-item> `
    )
  }

  /**
   * Call this on the top of the `render()` method to render demo navigation
   * @return HTML template for demo header
   */
  override headerTemplate(): TemplateResult {
    const { componentName, selectedFile } = this
    const info = this.apis.find((i) => i.file === selectedFile)
    const label = info?.label || 'Select demo API'
    return html`
      <header>
        ${componentName ? html`<h1 class="display-medium">${componentName}</h1>` : ''}
        <ui-dropdown-list @select="${this._apiChanged}" closeOnOutsideClick style="margin-left: auto">
          <ui-filled-button>
            ${label}
            <ui-icon role="presentation" icon="arrowDropDown"></ui-icon>
          </ui-filled-button>
          <ui-list slot="dropdown" role="menu"> ${this._apiListTemplate()} </ui-list>
        </ui-dropdown-list>
      </header>
    `
  }

  override navigationTemplate(): TemplateResult {
    if (this.noApiNavigation) {
      return html``
    }
    // sort, filter
    return html` <api-navigation
      summary
      ?endpointsOpened="${this.endpointsOpened}"
      ?documentationsOpened="${this.docsOpened}"
      ?schemasOpened="${this.typesOpened}"
      ?securityOpened="${this.securityOpened}"
      layout="tree"
    ></api-navigation>`
  }
}
