import { html, TemplateResult, PropertyValues, nothing } from 'lit'
import { property, state } from 'lit/decorators.js'
import { classMap } from 'lit/directives/class-map.js'
import { type HttpProject, Kind as HttpProjectKind } from '@api-client/core/models/HttpProject.js'
import type { IApplication } from '@api-client/core/models/Application.js'
import type { ProjectFolder } from '@api-client/core/models/ProjectFolder.js'
import type { IProjectRunnerOptions } from '@api-client/core/runtime/node/InteropInterfaces.js'
import type { IProjectExecution, IProjectExecutionLog } from '@api-client/core/models/ProjectExecution.js'
import type { IHttpProjectProxyInit } from '@api-client/core/proxy/HttpProjectProxy.js'
import type { IProjectExecutionListOptions } from '@api-client/core/events/BaseEvents.js'
import { Events as CoreEvents } from '@api-client/core/events/Events.js'
import ApiElement from '../ApiElement.js'
import type CheckboxElement from '../../md/checkbox/internals/CheckboxElement.js'
import type Input from '../../md/input/Input.js'

import '@github/relative-time-element'
import '../../define/http-project/project-run-report.js'
import '../../md/checkbox/ui-checkbox.js'
import '../../md/text-field/ui-filled-text-field.js'
import '../../md/button/ui-button.js'
import '../../md/progress/ui-progress.js'
import '../../md/icons/ui-icon.js'
import '../../md/dropdown-list/ui-dropdown-list.js'
import '../../md/list/ui-list.js'
import '../../md/list/ui-list-item.js'

/**
 * @fires change - When the configuration of the runner change.
 */
export default class ProjectRunner extends ApiElement {
  /**
   * A reference to the global shared variables.
   * Used with the communication with the execution server.
   */
  @property({ type: Object }) accessor sharedVariables: Record<string, string> | undefined

  /**
   * The instance of the current project.
   */
  @property({ type: Object }) accessor project: HttpProject | undefined

  /**
   * This property is required for the API access to work.
   * Set it to the current application information.
   */
  @property({ type: Object }) accessor appInfo: IApplication | undefined

  /**
   * The key of the folder to render the runner for.
   * When not set, it renders the runner for the project.
   * @attribute
   */
  @property({ type: String, reflect: true }) accessor folder: string | undefined

  /**
   * The object we operate on.
   */
  @state() protected accessor root: HttpProject | ProjectFolder | undefined

  /**
   * Whether the element is currently running the project execution.
   */
  @state() protected accessor running: boolean | undefined

  /**
   * Whether the element is currently querying for the history data.
   */
  @state() protected accessor querying: boolean | undefined

  /**
   * The page cursor for pagination.
   */
  protected pageCursor?: string

  /**
   * The run configuration.
   */
  @property({ type: Object }) accessor config: IProjectRunnerOptions | undefined

  @state() protected accessor lastError: string | undefined

  protected history: IProjectExecution[] = []

  protected queryPageDebouncer?: number

  debounceTimeout = 100

  /**
   * The key of the currently rendered report.
   */
  protected selectedReport?: string

  /**
   * The currently rendered report.
   */
  @state() accessor renderedReport: IProjectExecution | undefined

  override willUpdate(cp: PropertyValues<this>): void {
    super.willUpdate(cp)
    if (cp.has('project') || cp.has('folder')) {
      this.setupRoot()
    }
  }

  protected setupRoot(): void {
    const { project, folder } = this
    if (!project) {
      this.root = undefined
      return
    }
    if (!folder) {
      this.root = project
    } else {
      this.root = project.findFolder(folder)
    }
    this.resetHistory()
    this.queryPageDebounce()
  }

  protected handleRun(): void {
    this.execute()
  }

  /**
   * Executes the current configuration.
   */
  async execute(): Promise<void> {
    this.lastError = undefined
    const { config = {}, folder, project } = this
    if (!project) {
      return
    }
    if (folder) {
      config.parent = folder
    }
    // Note, we intentionally don't add shared variables.
    // Project / folder execution should be contained and can only leak
    // out the variables to the rest of the app, not in.

    // if (sharedVariables && !config.variables) {
    //   config.variables = sharedVariables;
    // }
    const init: IHttpProjectProxyInit = {
      kind: HttpProjectKind,
      project: project.toJSON(),
      options: config,
    }

    this.running = true
    try {
      const result = await CoreEvents.Transport.Core.httpProject(init, this)
      if (!result) {
        this.lastError = 'The project execution event was not handled.'
      } else {
        this.mergeExecutedVariables(result.result)
        this.createHistory(result.result)
      }
    } catch (e) {
      this.lastError = (e as Error).message
    }
    this.running = false
  }

  mergeExecutedVariables(result: IProjectExecutionLog): void {
    const { sharedVariables } = this
    if (!sharedVariables) {
      return
    }

    result.iterations.forEach((iteration) => {
      const { variables } = iteration
      if (!variables) {
        return
      }
      Object.keys(variables).forEach((key) => {
        sharedVariables[key] = variables[key]
      })
    })
  }

  protected async createHistory(result: IProjectExecutionLog): Promise<void> {
    const { appInfo, project } = this
    if (!appInfo) {
      throw new Error(`The appInfo is not set on ${this.localName}`)
    }
    if (!project) {
      return
    }
    throw new Error(`Not yet implemented: ${result}`)
    // await Events.Store.ProjectRuns.add({
    //   ...result,
    //   app: appInfo.code,
    //   project: project.key,
    //   key: '',
    //   kind: ProjectExecutionKind,
    //   midnight: 0,
    //   parent: this.folder,
    // })
  }

  resetHistory(): void {
    this.querying = false
    this.pageCursor = undefined
    this.history = []
    if (this.queryPageDebouncer) {
      clearTimeout(this.queryPageDebouncer)
      this.queryPageDebouncer = undefined
    }
  }

  queryPageDebounce(): void {
    if (this.queryPageDebouncer) {
      return
    }
    this.queryPageDebouncer = setTimeout(() => {
      this.queryPageDebouncer = undefined
      this.queryPage()
    }, this.debounceTimeout) as unknown as number
  }

  async queryPage(): Promise<void> {
    const { project, appInfo, folder, pageCursor } = this
    if (this.querying || !project) {
      return
    }
    this.querying = true
    const opts: IProjectExecutionListOptions = {
      organization: '',
      project: project.key,
    }
    if (appInfo) {
      opts.app = appInfo.code
    }
    if (folder) {
      opts.parent = folder
    }
    if (pageCursor) {
      opts.cursor = pageCursor
    }
    try {
      throw new Error(`Not yet implemented: ${opts}`)
      // const result = await Events.Store.ProjectRuns.list(opts, this)
      // if (!result) {
      //   throw new Error(`Project runs query not handled by the store.`)
      // }
      // const { items, cursor } = result
      // if (items.length) {
      //   if (cursor) {
      //     this.pageCursor = cursor
      //   }
      //   this.history = this.history.concat(items)
      //   this.history.sort(this.historySort)
      //   if (!this.selectedReport) {
      //     const [last] = this.history
      //     this.selectHistory(last.key)
      //   }
      //   this.requestUpdate()
      // }
    } catch (e) {
      // eslint-disable-next-line no-console
      console.warn(e)
    } finally {
      this.querying = false
    }
  }

  protected historySort(a: IProjectExecution, b: IProjectExecution): number {
    return b.ended - a.ended
  }

  setRecursive(value: boolean): void {
    const { config = {} } = this
    config.recursive = value
    this.config = config
    this.notifyChange()
  }

  setParallel(value: boolean): void {
    const { config = {} } = this
    config.parallel = value
    this.config = config
    this.notifyChange()
  }

  setIterations(value: number): void {
    const { config = {} } = this
    config.iterations = value
    this.config = config
    this.notifyChange()
  }

  setIterationDelay(value: number): void {
    const { config = {} } = this
    config.iterationDelay = value
    this.config = config
    this.notifyChange()
  }

  protected handleRecursiveChecked(e: Event): void {
    const input = e.target as CheckboxElement
    this.setRecursive(!!input.checked)
  }

  protected handleParallelChecked(e: Event): void {
    const input = e.target as CheckboxElement
    this.setParallel(!!input.checked)
  }

  protected handleIterationsChange(e: Event): void {
    const input = e.target as Input
    this.setIterations(input.valueAsNumber)
    input.checkValidity()
  }

  protected handleIterationDelayChange(e: Event): void {
    const input = e.target as Input
    this.setIterationDelay(input.valueAsNumber)
    input.checkValidity()
  }

  protected handleHistorySelection(e: CustomEvent): void {
    const element = e.detail.item as HTMLElement
    const { key } = element.dataset
    if (!key) {
      return
    }
    this.selectHistory(key)
  }

  selectHistory(key: string): void {
    this.selectedReport = key
    const { history } = this
    const report = history.find((i) => i.key === key)
    if (report) {
      this.renderedReport = report
    } else {
      this.renderedReport = undefined
    }
  }

  protected override render(): TemplateResult {
    const { root } = this
    if (!root) {
      return html``
    }
    return html`
      <section id="config">
        <div class="header title-large section-title">Project runner for ${root.info.renderLabel}</div>
        <form name="run-config" class="run-config surface1">
          <div class="section-title title-medium">Run configuration</div>
          ${this.renderRecursiveOption()} ${this.renderParallelOption()} ${this.renderIterationOption()}
          ${this.renderIterationDelayOption()} ${this.renderSubmit()}
        </form>
        ${this.renderError()} ${this.renderLoader()} ${this.renderReport()}
      </section>
    `
  }

  protected renderRecursiveOption(): TemplateResult {
    const { config = {} } = this
    const value = typeof config.recursive === 'boolean' ? config.recursive : false
    return html`
      <div class="form-row">
        <ui-checkbox
          id="recursive"
          .checked="${value}"
          name="recursive"
          aria-labelledby="recursiveLabel"
          aria-describedby="recursiveDescription"
          @change="${this.handleRecursiveChecked}"
        ></ui-checkbox>
        <label class="label-large" for="recursive" id="recursiveLabel"> Recursive </label>
        <p class="body-medium" id="recursiveDescription">
          Runs all requests from the current folder and its sub-folders
        </p>
      </div>
    `
  }

  protected renderParallelOption(): TemplateResult {
    const { config = {} } = this
    const value = typeof config.parallel === 'boolean' ? config.parallel : false
    return html`
      <div class="form-row">
        <ui-checkbox
          id="parallel"
          .checked="${value}"
          name="parallel"
          aria-labelledby="parallelLabel"
          aria-describedby="parallelDescription"
          @change="${this.handleParallelChecked}"
        ></ui-checkbox>
        <label class="label-large" for="parallel" id="parallelLabel"> Parallel </label>
        <p class="body-medium" id="parallelDescription">
          Runs each iteration in parallel. The number of actual execution depends on the number of cores on your device.
          This is ignored when number of iterations is 1.
        </p>
      </div>
    `
  }

  protected renderIterationOption(): TemplateResult {
    const { config = {} } = this
    const value = typeof config.iterations === 'number' ? config.iterations : 1
    return html`
      <div class="form-row">
        <ui-filled-text-field
          required
          label="Iterations"
          aria-describedby="iterationsDescription"
          name="iterations"
          type="number"
          min="1"
          step="1"
          .value="${String(value)}"
          @change="${this.handleIterationsChange}"
        ></ui-filled-text-field>
        <p class="body-medium" id="iterationsDescription">The number of times the execution should be repeated.</p>
      </div>
    `
  }

  protected renderIterationDelayOption(): TemplateResult {
    const { config = {} } = this
    const value = typeof config.iterationDelay === 'number' ? config.iterationDelay : 0
    return html`
      <div class="form-row">
        <ui-filled-text-field
          required
          label="Iteration delay"
          aria-describedby="iterationDelayDescription"
          name="iterationDelay"
          type="number"
          min="0"
          step="1"
          .value="${String(value)}"
          @change="${this.handleIterationDelayChange}"
        ></ui-filled-text-field>
        <p class="body-medium" id="iterationDelayDescription">
          The number of milliseconds to wait between each iteration.
        </p>
      </div>
    `
  }

  protected renderSubmit(): TemplateResult {
    return html`
      <div class="submit-row">
        <ui-button color="filled" @click="${this.handleRun}" ?disabled="${this.running}">Run</ui-button>
      </div>
    `
  }

  protected renderLoader(): TemplateResult | string {
    if (!this.running) {
      return ''
    }
    return html` <ui-progress indeterminate></ui-progress> `
  }

  protected renderError(): TemplateResult | string {
    const { lastError } = this
    if (!lastError) {
      return ''
    }
    return html`<div class="error on-error-text body-large last-error">${lastError}</div>`
  }

  protected renderReport(): TemplateResult | typeof nothing {
    const { renderedReport, history } = this
    if (!history.length) {
      return nothing
    }
    return html`
      <section aria-label="Latest report" class="report surface1">
        <div class="section-title title-medium">Run reports</div>
        ${this.renderHistorySelector()}
        <project-run-report .report="${renderedReport}"></project-run-report>
      </section>
    `
  }

  protected renderHistorySelector(): TemplateResult {
    const { history } = this
    const disabled = !history.length
    const selected = history.find((i) => i.key === this.selectedReport)
    return html`
      <div class="history-selector">
        <ui-dropdown-list
          verticalAlign="top"
          class="history-dropdown"
          closeOnOutsideClick
          @select="${this.handleHistorySelection}"
        >
          <ui-button color="tonal" ?disabled="${disabled}">
            <ui-icon slot="icon" icon="arrowDropDown"></ui-icon>
            ${selected
              ? html`<relative-time datetime="${new Date(selected.ended).toISOString()}"></relative-time>`
              : 'History'}
          </ui-button>
          <ui-list slot="dropdown" role="menu" aria-label="History list">
            ${history.map((current) => this.renderHistoryOption(current))}
          </ui-list>
        </ui-dropdown-list>
      </div>
    `
  }

  renderHistoryOption(item: IProjectExecution): TemplateResult {
    const { ended, key } = item
    const classes = {
      'history-option': true,
      'highlight': this.selectedReport === key,
    }
    const de = new Date(ended)
    return html`
      <ui-list-item role="menuitem" data-key="${key}" class="${classMap(classes)}">
        <relative-time datetime="${de.toISOString()}"></relative-time>
      </ui-list-item>
    `
  }
}
