import { html, TemplateResult, CSSResult } from 'lit'
import { property, state } from 'lit/decorators.js'
import { Environment, type IEnvironment } from '@api-client/core/models/Environment.js'
import { type IProperty } from '@api-client/core/models/Property.js'
import { type IServer, Server } from '@api-client/core/models/Server.js'
import { type AuthorizationType } from '@api-client/core/models/Authorization.js'
import { type IRequestAuthorization, RequestAuthorization } from '@api-client/core/models/RequestAuthorization.js'
import { live } from 'lit/directives/live.js'
import ApiElement from '../ApiElement.js'
import ServerEditor from './ServerEditor.js'
import VariablesEditor from './VariablesEditor.js'
import { Events } from '../../events/Events.js'
import Input from '../../md/input/Input.js'
import elementStyles from './EnvironmentEditor.styles.js'
import SwitchElement from '../../md/switch/internals/SwitchElement.js'
import { authTypeToLabel } from '../authorization/AuthLabels.js'
import { Authorization } from '../authorization/ui/Authorization.js'
import { TabSelectionDetail } from '../../md/tabs/internals/Tabs.js'

import '../../define/environment/server-editor.js'
import '../../define/environment/variables-editor.js'
import '../../md/icons/ui-icon.js'
import '../../md/text-field/ui-filled-text-field.js'
import '../../md/switch/ui-switch.js'
import '../../md/icon-button/ui-icon-button.js'
import '../../md/button/ui-button.js'
import '../../md/divider/ui-divider.js'
import '../../md/tabs/ui-tabs.js'
import '../../md/tabs/ui-tab.js'
import '../../define/authorization/basic-authorization.js'
import '../../define/authorization/bearer-authorization.js'
import '../../define/authorization/ntlm-authorization.js'
import '../../define/authorization/apikey-authorization.js'
import '../../define/authorization/oauth2-authorization.js'
import '../../define/authorization/oidc-authorization.js'
import '../../define/authorization/cc-authorization.js'

type EditorPage = 'server' | 'variables' | 'authorization'

/**
 * An element that renders a form to set up an environment.
 */
export default class EnvironmentEditor extends ApiElement {
  static override get styles(): CSSResult[] {
    return [elementStyles]
  }

  /**
   * The environment schema to edit.
   */
  @property({ type: Object }) accessor environment: IEnvironment = new Environment().toJSON()

  /**
   * Redirect URL for the OAuth2 authorization.
   * If can be also set by dispatching `oauth2-redirect-url-changed`
   * with `value` property on the `detail` object.
   * @attribute
   */
  @property({ type: String }) accessor oauth2RedirectUri: string | undefined

  @state() accessor encapsulateHelp: boolean

  /**
   * @attribute
   */
  @property({ type: String }) accessor selected: EditorPage

  constructor() {
    super()
    this.encapsulateHelp = false
    this.selected = 'server'
  }

  updateName(value: string): void {
    this.environment.info.name = value
    this.notifyChange()
    Events.HttpProject.State.nameChanged(this.environment.key, this.environment.kind, this)
  }

  updateEncapsulated(value: boolean): void {
    this.environment.encapsulated = value
    this.notifyChange()
  }

  deleteServer(): void {
    this.environment.server = undefined
    this.requestUpdate()
    this.notifyChange()
  }

  updateVariables(value?: IProperty[]): void {
    if (value) {
      this.environment.variables = value
    }
    this.notifyChange()
    // when modifying variables they won't be reflected in the server editor.
    const node = this.shadowRoot?.querySelector('server-editor')
    if (node) {
      node.generateValue()
    }
  }

  protected handleServerChange(e: Event): void {
    const editor = e.target as ServerEditor
    const value = editor.getSchema()
    this.environment.server = value
    this.notifyChange()
  }

  protected handleVariableChange(e: Event): void {
    const editor = e.target as VariablesEditor
    const { variables } = editor
    this.updateVariables(variables)
  }

  protected _createServerHandler(): void {
    const server = new Server()
    this.environment.server = server.toJSON()
    this.requestUpdate()
  }

  protected handleEncapsulatedChange(e: Event): void {
    const input = e.target as SwitchElement
    this.updateEncapsulated(!!input.checked)
  }

  protected handleNameChange(e: Event): void {
    const input = e.target as Input
    this.updateName(input.value)
    input.checkValidity()
  }

  protected handleToggleHelp(): void {
    this.encapsulateHelp = !this.encapsulateHelp
  }

  protected handleServerDelete(): void {
    this.deleteServer()
  }

  protected handleAuthAdd(e: CustomEvent): void {
    const target = e.detail.item as HTMLElement | undefined
    if (!target) {
      return
    }
    const selected = target.dataset.type as AuthorizationType | undefined
    if (!selected) {
      return
    }
    this.addAuthorization(selected)
  }

  /**
   * Adds a new authorization object to the request for the given type.
   *
   * @returns The created instance of the authorization.
   */
  addAuthorization(type: AuthorizationType): RequestAuthorization {
    const cnf = RequestAuthorization.fromTypedConfig(type, {}, false)
    cnf.enabled = true
    if (!this.environment.authorization) {
      this.environment.authorization = []
    }
    this.environment.authorization.push(cnf.toJSON())
    this.notifyChange()
    this.requestUpdate()
    return cnf
  }

  protected handleAuthChange(e: Event): void {
    const editor = e.target as Authorization
    const { authorization } = editor
    this.updateAuthorization(authorization as IRequestAuthorization, editor.checkValidity())
  }

  protected handleAuthDelete(e: Event): void {
    const editor = e.target as Authorization
    const { authorization } = editor
    if (!authorization) {
      return
    }
    this.removeAuthorization(authorization.type)
  }

  /**
   * Updates authorization schema on the editor.
   * @param schema The new schema to replace the old one
   * @param isValid Whether the schema is valid.
   */
  updateAuthorization(schema: IRequestAuthorization, isValid: boolean): void {
    const { authorization } = this.environment
    if (!authorization) {
      return
    }
    const index = authorization.findIndex((i) => i.type === schema.type)
    if (index === -1) {
      return
    }
    schema.valid = isValid
    authorization[index] = schema
    this.notifyChange()
  }

  /**
   * Removes authorization schema from the editor.
   * @param type The authorization type to remove.
   */
  removeAuthorization(type: string): void {
    const { authorization } = this.environment
    if (!authorization) {
      return
    }
    const index = authorization.findIndex((i) => i.type === type)
    if (index === -1) {
      return
    }
    authorization.splice(index, 1)
    this.notifyChange()
    this.requestUpdate()
  }

  protected handleTabSelection(e: CustomEvent<TabSelectionDetail>): void {
    const { page } = e.detail.item.dataset
    if (!page) {
      return
    }
    this.selected = page as EditorPage
  }

  protected override render(): TemplateResult {
    return html` ${this.renderHeader()} ${this.renderEnvironmentEditor()} ${this.renderTabs()} ${this.renderPage()} `
  }

  protected renderHeader(): TemplateResult {
    return html`<div class="section-title">Environment</div>`
  }

  protected renderPage(): TemplateResult {
    switch (this.selected) {
      case 'authorization':
        return this.renderAuthorization()
      case 'variables':
        return this.renderVariables()
      default:
        return this.renderServerTemplate()
    }
  }

  protected renderEnvironmentEditor(): TemplateResult {
    return html`
      <section aria-label="Environment configuration">${this.renderNameInput()} ${this.renderEncapsulate()}</section>
    `
  }

  protected renderNameInput(): TemplateResult {
    const { environment } = this
    return html` <div class="form-item">
      <ui-filled-text-field
        name="name"
        label="Environment name"
        required
        .value="${live(environment.info.name || '')}"
        @change="${this.handleNameChange}"
      ></ui-filled-text-field>
    </div>`
  }

  protected renderEncapsulate(): TemplateResult {
    const { encapsulateHelp } = this
    return html`
      <div class="encapsulate-item">${this.renderEncapsulateInput()} ${this.renderEncapsulateInfoToggle()}</div>
      ${encapsulateHelp
        ? html`
            <div class="help-banner">
              By default when running requests in a folder the application reads all environments up to the project
              root. The variables are then combined into a single object. When <i>encapsulated</i> is enabled, the
              program stops reading environments above the folder the environment is defined.
            </div>
          `
        : ''}
    `
  }

  protected renderEncapsulateInput(): TemplateResult {
    const { environment } = this
    return html`
      <label>
        <ui-switch
          name="encapsulated"
          .checked="${environment.encapsulated || false}"
          @change="${this.handleEncapsulatedChange}"
          aria-label="Encapsulate the environment"
        ></ui-switch>
        Encapsulate the environment
      </label>
    `
  }

  protected renderEncapsulateInfoToggle(): TemplateResult {
    return html`
      <ui-icon-button aria-label="Show or hide help topic" @click="${this.handleToggleHelp}">
        <ui-icon icon="info" role="presentation"></ui-icon>
      </ui-icon-button>
    `
  }

  protected renderServerTemplate(): TemplateResult {
    const { server } = this.environment
    return html`
      <section aria-label="Server information" id="server" class="editor-container">
        ${server ? this.renderServerEditor(server) : this.renderNoServer()}
      </section>
    `
  }

  protected renderNoServer(): TemplateResult {
    return html`
      <div class="section-title">Server</div>
      <p class="empty-info">No server defined.</p>
      <ui-button color="tonal" @click="${this._createServerHandler}">Create server</ui-button>
    `
  }

  protected renderServerEditor(server: IServer): TemplateResult {
    const { uri, basePath, description, protocol } = server
    return html`
      <server-editor
        .uri="${uri}"
        .basePath="${basePath}"
        .description="${description}"
        .protocol="${protocol}"
        .variables="${this.environment.variables}"
        @change="${this.handleServerChange}"
        @delete="${this.handleServerDelete}"
      ></server-editor>
    `
  }

  protected renderVariables(): TemplateResult {
    return html`
      <section aria-label="Variables" id="variables" class="editor-container">${this.renderVariablesEditor()}</section>
    `
  }

  protected renderVariablesEditor(): TemplateResult {
    const { variables } = this.environment
    return html`<variables-editor .variables="${variables}" @change="${this.handleVariableChange}"></variables-editor>`
  }

  protected renderAuthorization(): TemplateResult {
    const { authorization = [] } = this.environment
    return html`
      <section id="authorization" aria-label="Authorization" class="editor-container">
        <div class="section-title">Authorization</div>
        <p class="auth-info">
          The authorization is applied to all requests in the current folder when the request is executed.
        </p>
        ${this.renderAuthorizationActions(authorization)} ${this.renderAuthorizationList(authorization)}
      </section>
    `
  }

  protected renderAuthorizationActions(current: IRequestAuthorization[] = []): TemplateResult {
    const all = ['basic', 'oauth 2', 'open id', 'bearer', 'api key', 'ntlm', 'client certificate']
    const toRender: string[] = all.filter((type) => !current.some((i) => i.type === type))
    return html`
      <ui-dropdown-list verticalAlign="top" @select="${this.handleAuthAdd}" class="auth-dropdown" closeOnOutsideClick>
        <ui-button
          color="tonal"
          title="Add authorization option"
          aria-label="Opens a menu to select authorization option"
          ?disabled="${!toRender.length}"
        >
          <ui-icon icon="arrowDropDown" slot="icon" role="presentation"></ui-icon>
          Add
        </ui-button>
        <ui-list slot="dropdown" role="menu"> ${this.renderAuthorizationOptions(toRender)} </ui-list>
      </ui-dropdown-list>
    `
  }

  protected renderAuthorizationOptions(available: string[]): TemplateResult {
    return html`
      ${available.map(
        (type) => html` <ui-list-item role="menuitem" data-type="${type}"> ${authTypeToLabel(type)} </ui-list-item> `
      )}
    `
  }

  protected renderAuthorizationList(authorization: IRequestAuthorization[] = []): TemplateResult[] {
    if (!Array.isArray(authorization) || !authorization.length) {
      return [this.renderEmptyAuthorization()]
    }
    return authorization.map((item, index) => {
      let content: TemplateResult | undefined
      switch (item.type) {
        case 'basic':
          content = this.renderBasicAuthorization(item)
          break
        case 'bearer':
          content = this.renderBearerAuthorization(item)
          break
        case 'ntlm':
          content = this.renderNtlmAuthorization(item)
          break
        case 'oauth 2':
          content = this.renderOauthAuthorization(item)
          break
        case 'open id':
          content = this.renderOidcAuthorization(item)
          break
        case 'client certificate':
          content = this.renderCCAuthorization(item)
          break
        case 'api key':
          content = this.renderApiKeyAuthorization(item)
          break
        default:
      }
      if (content) {
        const isLast = authorization.length === index + 1
        return html` ${content} ${!isLast ? html`<ui-divider></ui-divider>` : ''} `
      }
      return html``
    })
  }

  protected renderEmptyAuthorization(): TemplateResult {
    return html``
  }

  protected renderBasicAuthorization(auth: IRequestAuthorization): TemplateResult {
    return html`
      <basic-authorization
        .authorization="${auth}"
        class="auth-panel"
        canDelete
        canToggle
        @change="${this.handleAuthChange}"
        @delete="${this.handleAuthDelete}"
      ></basic-authorization>
    `
  }

  protected renderBearerAuthorization(auth: IRequestAuthorization): TemplateResult {
    return html`
      <bearer-authorization
        .authorization="${auth}"
        class="auth-panel"
        canDelete
        canToggle
        @change="${this.handleAuthChange}"
        @delete="${this.handleAuthDelete}"
      ></bearer-authorization>
    `
  }

  protected renderNtlmAuthorization(auth: IRequestAuthorization): TemplateResult {
    return html`
      <ntlm-authorization
        .authorization="${auth}"
        class="auth-panel"
        canDelete
        canToggle
        @change="${this.handleAuthChange}"
        @delete="${this.handleAuthDelete}"
      ></ntlm-authorization>
    `
  }

  protected renderOauthAuthorization(auth: IRequestAuthorization): TemplateResult {
    return html`
      <oauth2-authorization
        .authorization="${auth}"
        class="auth-panel"
        canDelete
        canToggle
        @change="${this.handleAuthChange}"
        @delete="${this.handleAuthDelete}"
        .defaultRedirectUri="${this.oauth2RedirectUri}"
      ></oauth2-authorization>
    `
  }

  protected renderOidcAuthorization(auth: IRequestAuthorization): TemplateResult {
    return html`
      <oidc-authorization
        .authorization="${auth}"
        class="auth-panel"
        canDelete
        canToggle
        @change="${this.handleAuthChange}"
        @delete="${this.handleAuthDelete}"
        .defaultRedirectUri="${this.oauth2RedirectUri}"
      ></oidc-authorization>
    `
  }

  protected renderCCAuthorization(auth: IRequestAuthorization): TemplateResult {
    return html`
      <cc-authorization
        .authorization="${auth}"
        class="auth-panel"
        canDelete
        canToggle
        @change="${this.handleAuthChange}"
        @delete="${this.handleAuthDelete}"
      ></cc-authorization>
    `
  }

  protected renderApiKeyAuthorization(auth: IRequestAuthorization): TemplateResult {
    return html`
      <apikey-authorization
        .authorization="${auth}"
        class="auth-panel"
        canDelete
        canToggle
        @change="${this.handleAuthChange}"
        @delete="${this.handleAuthDelete}"
      ></apikey-authorization>
    `
  }

  protected renderTabs(): TemplateResult {
    const { selected: s } = this
    return html`
      <ui-tabs @select="${this.handleTabSelection}">
        <ui-tab .selected="${s === 'server'}" aria-controls="server" data-page="server">Server</ui-tab>
        <ui-tab .selected="${s === 'variables'}" aria-controls="variables" data-page="variables">Variables</ui-tab>
        <ui-tab .selected="${s === 'authorization'}" aria-controls="authorization" data-page="authorization"
          >Authorization</ui-tab
        >
      </ui-tabs>
    `
  }
}
