import type { DataDomain } from '@api-client/core/modeling/DataDomain.js'
import { LitElement, PropertyValues } from 'lit'
import { property } from 'lit/decorators.js'
import { fromError } from '../../exceptions/builder.js'

/**
 * Base class for modeling elements
 *
 * @fires change - When something changed
 * @fires error - When an error occurred. The event detail will contain an Exception object.
 */
export class ModelingElement extends LitElement {
  /**
   * The instance of the root DataNamespace.
   */
  @property({ type: Object }) accessor root: DataDomain | undefined
  /**
   * The parent namespace this element belongs to.
   * When rendering a foreign domain, this will be the namespace of the foreign domain.
   * Normally, this is the `key` of the root DataDomain.
   */
  @property({ type: String }) accessor namespace: string | undefined

  /**
   * The selected object that this element is interested in.
   * @attribute
   */
  @property({ type: String }) accessor selected: string | undefined
  /**
   * If true, the element is in read-only mode.
   * This means that the element will not allow any modifications to the data.
   * @attribute
   */
  @property({ type: Boolean }) accessor readOnly: boolean | undefined

  /**
   * Computed property that indicates if the element is a foreign element.
   * A foreign element is one that is not part of the root DataDomain.
   */
  protected get isForeignElement(): boolean {
    return this.root !== undefined && this.namespace !== this.root.key
  }

  /**
   * Computed property that indicates if the element can be edited.
   * An element can be edited if it is not read-only and is not a foreign element.
   */
  protected get canEdit(): boolean {
    return !this.readOnly && !this.isForeignElement
  }

  constructor() {
    super()
    this.handleDomainChange = this.handleDomainChange.bind(this)
  }

  protected override willUpdate(cp: PropertyValues<this>): void {
    if (cp.has('root')) {
      const old = cp.get('root')
      if (old) {
        old.removeEventListener('change', this.handleDomainChange)
      }
      this.root?.addEventListener('change', this.handleDomainChange)
    }
    super.willUpdate(cp)
  }

  protected handleDomainChange(): void {
    this.requestUpdate()
  }

  /**
   * A common way for an element to notify a change.
   */
  notifyChange(): void {
    this.dispatchEvent(new Event('change'))
  }

  /**
   * Reports an error to the element.
   * It will dispatch an 'error' event with the Exception object as the detail.
   * It unifies the error handling across the application.
   * @param error The error to report.
   */
  reportError(error: Error): void {
    const e = fromError(error)
    this.dispatchEvent(
      new CustomEvent('error', {
        bubbles: true,
        composed: true,
        detail: e,
      })
    )
  }
}
