import { CSSResult, html, nothing, TemplateResult } from 'lit'
import { classMap } from 'lit/directives/class-map.js'
import type { IAmfDocument } from '@api-client/core/amf/definitions/Amf.js'
import { AmfSerializer } from '@api-client/core/amf/AmfSerializer.js'
import type { UiListSelection } from '../../src/md/list/internals/List.js'
import { DemoPage } from '../../src/demo/DemoPage.js'
import type { IRoute, IRouteResult } from '../../src/mixins/RouteMixin.js'
import { DomEventsAmfStore } from '../../src/amf/store/DomEventsAmfStore.js'
import reactive from '../../src/decorators/reactive.js'
import { EventTypes as AmfEventTypes } from '../../src/events/amf/EventTypes.js'
import * as MonacoLoader from '../../src/monaco/loader.js'
import * as Router from '../../src/lib/route.js'
import type { ApiNavigationEvent } from '../../src/events/amf/NavigationEvents.js'
import { SelectionType } from '../../src/amf/types.js'
import { WebConfigurationBindings } from '../../src/bindings/web/WebConfigurationBindings.js'
// import { WebProxyBindings } from '../../src/bindings/web/WebProxyBindings.js'
// import { WebStoreBindings } from '../../src/bindings/web/WebStoreBindings.js'
import globalStyles from '../../src/styles/global-styles.js'
import typography from '../../src/styles/m3/typography.module.js'
import dialogStyles from '../../src/styles/m3/dialog.module.js'
import surfaceStyles from '../../src/styles/m3/surface.module.js'
import '../../src/define/amf/api-navigation.js'
import '../../src/define/amf/api-documentation.js'
import '../../src/md/icons/ui-icon.js'
import '../../src/md/icon-button/ui-icon-button.js'
import '../../src/md/dropdown-list/ui-dropdown-list.js'
import '../../src/md/list/ui-list.js'
import '../../src/md/list/ui-list-item.js'

const apiTitleValue = Symbol('apiTitleValue')

const ApiList = [
  ['httpbin', 'httpbin.org'],
  ['google-drive-api', 'Google Drive'],
  ['demo-api', 'Demo API'],
  ['multi-server', 'Multiple servers'],
  ['array-body', 'Body with array test case'],
  ['nexmo-sms-api', 'Nexmo SMS API'],
  ['appian-api', 'Applian API'],
  ['APIC-15', 'APIC-15'],
  ['oauth1-fragment', 'OAuth 1 fragment'],
  ['oauth2-fragment', 'OAuth 2 fragment'],
  ['documentation-fragment', 'Documentation fragment'],
  ['type-fragment', 'Type fragment'],
  ['lib-fragment', 'Library fragment'],
  ['SE-10469', 'SE-10469'],
  ['SE-11415', 'SE-11415'],
  ['async-api', 'Async API'],
  ['Streetlights', 'Streetlights Async API'],
]

export class ApiConsole extends DemoPage {
  config: WebConfigurationBindings

  static override get styles(): CSSResult[] {
    return [globalStyles, typography, dialogStyles, surfaceStyles]
  }

  storeChanged(): Promise<void> {
    throw new Error('Method not implemented.')
  }

  static get routes(): IRoute[] {
    return [
      {
        pattern: '/api-selector.*',
        method: 'selectorRoute',
        fallback: true,
        name: 'Select an API',
        title: 'Select an API',
      },
      { pattern: '/404', method: 'e404Route', name: 'Not found', title: 'Not found' },
      { pattern: '/api/(?<apiId>[^/]*)', method: 'apiRoute', name: 'API Documentation', title: 'API Documentation' },
      {
        pattern: '/api/(?<apiId>[^/]*)/(?<domainType>[^/]*)/(?<domainId>[^/]*)/(?<operationId>.*)',
        method: 'operationRoute',
        name: 'API Operation',
        title: 'API Operation',
      },
      {
        pattern: '/api/(?<apiId>[^/]*)/(?<domainType>[^/]*)/(?<domainId>.*)',
        method: 'selectionRoute',
        name: 'API Documentation',
        title: 'API Documentation',
      },
    ]
  }

  store: DomEventsAmfStore

  /**
   * When set the application is loading an API model.
   */
  @reactive() accessor loading: boolean

  @reactive() accessor page: string | undefined

  @reactive() accessor isMobile: boolean | undefined

  @reactive() accessor apiLoaded: boolean

  /**
   * Whether the application navigation is visible, when toggling.
   */
  @reactive() accessor navigationOpened: boolean

  /**
   * The loaded AMF model form the demo APIs.
   */
  @reactive() accessor model: IAmfDocument | undefined

  @reactive() accessor apiId: string | undefined

  /**
   * The domain id of the currently selected graph object.
   */
  @reactive() accessor domainId: string

  /**
   * The navigation type of the currently selected graph object.
   */
  @reactive() accessor domainType: SelectionType

  /**
   * When the current domain type is an operation this is the selected operation domain id.
   */
  @reactive() accessor operationId: string | undefined;

  /**
   * The title of the currently loaded API.
   */
  [apiTitleValue]?: string

  /**
   * @returns The title of the currently loaded API.
   */
  get apiTitle(): string | undefined {
    return this[apiTitleValue]
  }

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

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

    this.loading = false
    this.apiLoaded = false
    this.navigationOpened = false
    this.domainId = 'summary'
    this.domainType = 'summary'
    this.initialize()
  }

  /**
   * Loads the Monaco editor and initializes routing.
   */
  async initialize(): Promise<void> {
    await this.config.initialize()
    await this.loadMonaco()
    this.initializeRouting()
    window.addEventListener(AmfEventTypes.Navigation.apiNavigate, this.apiNavigationHandler.bind(this) as EventListener)
  }

  /**
   * Loads the Monaco editor using ARC's helpers.
   * This can be implemented in any way.
   */
  override async loadMonaco(): Promise<void> {
    const base = `../../node_modules/monaco-editor/`
    MonacoLoader.createEnvironment(base)
    await MonacoLoader.loadMonaco(base)
    await MonacoLoader.monacoReady()
  }

  /**
   * Cleans up the API and the selection state,
   */
  cleanUp(): void {
    this.apiId = undefined
    this.domainId = 'summary'
    this.domainType = 'summary'
    this.operationId = undefined
    this.model = undefined
    this[apiTitleValue] = undefined
  }

  protected resetRoute(): void {
    this.page = undefined
    this.apiId = undefined
  }

  protected selectorRoute(): void {
    this.resetRoute()
    this.page = 'selector'
  }

  protected apiRoute(info: IRouteResult): void {
    this.resetRoute()
    if (!info.params) {
      Router.navigate('404')
      return
    }
    if (info.params.apiId && this.apiId !== info.params.apiId) {
      this.loadFile(`/demo/models/${info.params.apiId}.json`)
    }
    this.page = 'api'
    this.apiId = info.params.apiId as string
  }

  protected selectionRoute(info: IRouteResult): void {
    this.resetRoute()
    if (!info.params) {
      Router.navigate('404')
      return
    }
    if (info.params.apiId && this.apiId !== info.params.apiId) {
      this.loadFile(`/demo/models/${info.params.apiId}.json`)
    }
    this.page = 'api'
    this.apiId = info.params.apiId as string
    this.domainId = info.params.domainId as string
    this.domainType = info.params.domainType as SelectionType
  }

  protected operationRoute(info: IRouteResult): void {
    this.resetRoute()
    if (!info.params) {
      Router.navigate('404')
      return
    }
    if (info.params.apiId && this.apiId !== info.params.apiId) {
      this.loadFile(`/demo/models/${info.params.apiId}.json`)
    }
    this.page = 'api'
    this.apiId = info.params.apiId as string
    this.domainId = info.params.domainId as string
    this.domainType = info.params.domainType as SelectionType
    this.operationId = info.params.operationId as string
  }

  protected e404Route(): void {
    this.resetRoute()
    this.page = '404'
  }

  /**
   * @param e Dispatched navigation event
   */
  apiNavigationHandler(e: ApiNavigationEvent): void {
    const { domainId, domainType, parentId, passive } = e.detail
    if (passive === true) {
      return
    }
    if (domainType === 'operation') {
      // /operation/{ResourceId}/{OperationId}
      Router.navigate('api', this.apiId as string, domainType, parentId as string, domainId)
    } else {
      Router.navigate('api', this.apiId as string, domainType, domainId)
    }
  }

  menuToggleHandler(): void {
    this.navigationOpened = !this.navigationOpened
  }

  /**
   * A handler for the API Console menu selection.
   */
  apiConsoleMenuHandler(e: CustomEvent): void {
    const element = e.detail.item as HTMLElement
    const { option } = element.dataset
    switch (option) {
      case 'close':
        this.closeApi()
        break
      default:
        console.warn(`Unhandled action: ${option}`)
    }
  }

  /**
   * Closes the current API project.
   */
  closeApi(): void {
    this.apiLoaded = false
    this.cleanUp()
    Router.navigate('api-selector')
  }

  /**
   * Handler for the API selection change
   */
  apiSelected(e: CustomEvent<UiListSelection>): void {
    const { item } = e.detail
    const { src } = item.dataset
    Router.navigate('api', src as string, 'summary', 'summary')
  }

  async loadFile(file: string): Promise<void> {
    this.loading = true
    try {
      const response = await fetch(`${file}`)
      let data = await response.json()
      if (Array.isArray(data)) {
        ;[data] = data
      }
      this.model = data
      this.store.amf = data
      this.postApiLoad(data)
      this.apiLoaded = true
      this.render()
    } catch (e) {
      console.error(e)
    }
    this.loading = false
  }

  /**
   * Operations performed after the API model is rendered.
   */
  postApiLoad(model: IAmfDocument): void {
    this[apiTitleValue] = undefined
    if (!model) {
      return
    }
    const factory = new AmfSerializer(model)
    const api = factory._computeApi(model)
    if (!api) {
      return
    }
    const summary = factory.apiSummary(api)
    this[apiTitleValue] = summary.name
  }

  contentTemplate(): TemplateResult {
    throw new Error('Method not implemented.')
  }

  override pageTemplate(): TemplateResult {
    return html`
      ${this.navigationTemplate()}
      <div class="layout-content">${this.headerTemplate()} ${this.mainTemplate()}</div>
    `
  }

  /**
   * @returns The application header template
   */
  override headerTemplate(): TemplateResult {
    const { isMobile } = this
    return html` <header class="start-page-header">
      ${isMobile ? this.navigationTriggerTemplate() : this.apiNameHeaderTemplate()}
      <span class="spacer"></span>
      ${this.toolbarActionsTemplate()}
    </header>`
  }

  /**
   * @returns The navigation trigger button template
   */
  navigationTriggerTemplate(): TemplateResult {
    return html`
      <ui-icon-button
        title="Open the menu"
        aria-label="Activate to open the API navigation"
        @click="${this.menuToggleHandler}"
      >
        <ui-icon icon="menu" role="presentation"></ui-icon>
      </ui-icon-button>
    `
  }

  /**
   * @returns The template for the API name.
   */
  apiNameHeaderTemplate(): TemplateResult {
    const { apiTitle } = this
    const label = apiTitle || 'API Console'
    return html` <h1 class="title-large nav-title">${label}</h1> `
  }

  /**
   * @returns A template for the toolbar actions.
   */
  toolbarActionsTemplate(): TemplateResult | typeof nothing {
    if (this.page !== 'api') {
      return nothing
    }
    return html`
      <ui-dropdown-list
        @select="${this.apiConsoleMenuHandler}"
        closeOnOutsideClick
        class="dropdown"
        class="header-menu"
      >
        <ui-icon-button aria-label="Activate for API Console menu" title="API Console menu">
          <ui-icon icon="moreVert" role="presentation"></ui-icon>
        </ui-icon-button>
        <ui-list slot="dropdown" role="menu">
          <ui-list-item role="menuitem" data-option="close" image="icon" title="Closes the current API">
            <ui-icon role="presentation" slot="start" icon="close"></ui-icon>
            Close API
          </ui-list-item>
        </ui-list>
      </ui-dropdown-list>
    `
  }

  protected mainTemplate(): TemplateResult {
    return html` <main class="body-medium">
      <div class="content surface1">${this.renderPage()}</div>
    </main>`
  }

  protected e404Template(): TemplateResult {
    return html`
      <div class="full-error">
        <h2>Not found</h2>
        <p class="description">Page not found.</p>
      </div>
    `
  }

  /**
   * @returns A template for the loader
   */
  loadingApiTemplate(): TemplateResult {
    return html`
      <div class="app-loader">
        <p class="message">Reading API specification data...</p>
      </div>
    `
  }

  /**
   * @returns The template for API navigation.
   */
  override navigationTemplate(): TemplateResult {
    if (['selector'].includes(this.page as string)) {
      return html`<nav></nav>`
    }
    const { isMobile = false, navigationOpened } = this
    const classes = {
      navigation: true,
      toggle: isMobile,
      opened: navigationOpened,
    }
    return html` <nav class="${classMap(classes)}">
      <api-navigation summary filter layout="natural"></api-navigation>
    </nav>`
  }

  /**
   * @returns The template for the current page.
   */
  renderPage(): TemplateResult {
    switch (this.page) {
      case 'api':
        return this.apiTemplate()
      case '404':
        return this.e404Template()
      default:
        return this.apiSelectorTemplate()
    }
  }

  /**
   * @returns The template for the API console's main documentation.
   */
  apiTemplate(): TemplateResult {
    return html`
      <api-documentation
        class="docs"
        .domainId="${this.domainId}"
        .operationId="${this.operationId}"
        .domainType="${this.domainType}"
        .redirectUri="${this.redirectUri}"
        tryItPanel
      >
      </api-documentation>
    `
  }

  /**
   * @returns The screen when there is no API selection.
   */
  apiSelectorTemplate(): TemplateResult {
    return html`
      <section class="api-selector">
        <div class="title-line"><h2 class="title-medium">Select an API</h2></div>
        <div class="api-list">
          <ui-list id="apiList" @select="${this.apiSelected}">
            ${ApiList.map(([file, label]) => html` <ui-list-item data-src="${file}">${label}</ui-list-item> `)}
          </ui-list>
        </div>
      </section>
    `
  }
}

const page = new ApiConsole()
page.render()
