import { render, type TemplateResult } from 'lit'
import { bound } from '../../decorators/bound.js'
import type { UiCheckboxElement } from '../../md/checkbox/ui-checkbox.js'
import type { DomainProperty } from '@api-client/core/modeling/DomainProperty.js'
import type { DomainEntity } from '@api-client/core/modeling/DomainEntity.js'
import deepClone from '@api-client/json/core/Clone.js'
import deepEqual from '@api-client/json/core/Equal.js'
import formStyles from '../internals/styles/form_styles.js'
import commonStyles from './common.js'
import { adoptStyles } from '../../lib/Dom.js'
import { DialogBuilder } from '../../builders/DialogBuilder.js'

import '../../md/checkbox/ui-checkbox.js'
import '../../md/text-field/ui-outlined-text-field.js'
import '../../md/select/ui-select.js'
import '../../md/select/ui-option.js'

export abstract class TaxonomyDialog<T extends object = Record<string, unknown>> extends DialogBuilder {
  /**
   * A reference to the form element
   */
  declare renderRoot?: HTMLFormElement

  static override title = 'Configure Taxonomy'

  /**
   * The configuration object for the taxonomy.
   * This object is used to store the state of the form.
   */
  config: T

  /**
   * The original configuration object used to track changes.
   * This is a deep clone of the config passed to the constructor.
   */
  private originalConfig: T

  constructor(config: T) {
    super()
    this.config = config
    this.modal = true
    this.originalConfig = deepClone(config)
    this.renderRoot = this.createForm()
    this.body = document.createElement('div')
    this.body.classList.add('form-inputs')
    this.classes = ['taxonomy-dialog', 'message', this.constructor.name.toLocaleLowerCase()]
    this.positiveAction = {
      label: 'Save',
      value: 'confirm',
      type: 'submit',
    }
    this.negativeAction = {
      label: 'Cancel',
      value: 'dismiss',
    }
    this.title = (this.constructor as typeof TaxonomyDialog).title || 'Configure Taxonomy'

    // Set up unsaved changes detection
    this.setUnsavedChangesDetection({
      hasUnsavedChanges: () => this.hasUnsavedChanges(),
      warningTitle: 'Unsaved Changes',
      warningMessage:
        'You have unsaved changes that will be lost if you continue. Are you sure you want to discard your changes?',
      discardLabel: 'Discard Changes',
      keepEditingLabel: 'Keep Editing',
    })
  }

  /**
   * Checks if there are unsaved changes by comparing the current config
   * with the original config.
   */
  protected hasUnsavedChanges(): boolean {
    return !deepEqual(this.config, this.originalConfig)
  }

  protected createForm(): HTMLFormElement {
    const form = document.createElement('form')
    form.classList.add('taxonomy-form')
    form.addEventListener('submit', this.formCallback)
    form.addEventListener('reset', this.handleDialogClosed)
    return form
  }

  @bound
  protected async formCallback(event: Event): Promise<void> {
    event.preventDefault()
    this.dispose()
  }

  /**
   * Removes the dialog from the DOM and removes all listeners.
   * The dialog does not call any lifecycle methods.
   */
  override dispose(): void {
    super.dispose()
    this.renderRoot?.removeEventListener('submit', this.formCallback)
    this.renderRoot?.removeEventListener('reset', this.handleDialogClosed)
    this.renderRoot?.remove()
    this.renderRoot = undefined
  }

  async run(): Promise<void> {
    if (!this.renderRoot) {
      throw new Error('The form was disposed and cannot be run again.')
    }
    adoptStyles(formStyles, commonStyles)
    document.body.appendChild(this.renderRoot)
    this.open()
    this.requestUpdate()
  }

  requestUpdate(): void {
    if (this.body) {
      render(this.render(), this.body, {
        host: this,
      })
    }
  }

  /**
   * A handler for any of the checkboxes in the form.
   * Updates the `config`
   */
  @bound
  protected handleBooleanChange(e: Event): void {
    const checkbox = e.target as UiCheckboxElement
    const { name = '', checked } = checkbox
    const cnf = this.config as Record<string, boolean>
    cnf[name] = !!checked
    this.requestUpdate()
  }

  @bound
  protected handleTextChange(e: Event): void {
    const input = e.target as HTMLInputElement
    const { name, value, type } = input
    const cnf = this.config as Record<string, string | number>
    if (type === 'number') {
      cnf[name] = Number(value)
    } else {
      cnf[name] = value
    }
    this.requestUpdate()
  }

  @bound
  protected handleSelectChange(e: Event): void {
    const select = e.target as HTMLSelectElement
    const { name, value } = select
    if (!name || !value) {
      return
    }
    const cnf = this.config as Record<string, string>
    cnf[name] = value
    this.requestUpdate()
  }

  listComputedProperties(entity: DomainEntity, opts: { exclude?: string[]; types?: string[] } = {}): DomainProperty[] {
    const result: DomainProperty[] = []
    const { exclude = [], types = [] } = opts
    for (const prop of entity.properties) {
      if (exclude.includes(prop.key)) {
        // Skip the current property itself and excluded properties
        continue
      }
      // Only include properties that match the specified types
      if (!types.length || types.includes(prop.type)) {
        result.push(prop)
      }
    }
    for (const parent of entity.parents) {
      for (const prop of parent.properties) {
        if (!types.length || types.includes(prop.type)) {
          result.push(prop)
        }
      }
    }
    return result
  }

  abstract render(): TemplateResult
}
