/* eslint-disable @typescript-eslint/no-explicit-any */

import type { UpdateRequest } from '../core/Application.js'

interface UpdatingInstance {
  requestUpdate(...args: any[]): void
}

export interface PropertyDecorator {
  // accessor decorator signature
  <C extends UpdatingInstance, V>(
    target: ClassAccessorDecoratorTarget<C, V>,
    context: ClassAccessorDecoratorContext<C, V>
  ): ClassAccessorDecoratorResult<C, V>
  // setter decorator signature
  <C extends UpdatingInstance, V>(
    target: (value: V) => void,
    context: ClassSetterDecoratorContext<C, V>
  ): (this: C, value: V) => void
}

type StandardPropertyContext<C, V> = ClassAccessorDecoratorContext<C, V> | ClassSetterDecoratorContext<C, V>
type StandardPropertyTarget<C, V> = ClassAccessorDecoratorTarget<C, V> | ((value: V) => void)
type StateOptions = UpdateRequest

/**
 * A field setter or accessor decorator that automatically calls `requestUpdate()` when a new value is set.
 *
 * @param options Update request options.
 */
export default function state(options: StateOptions = {}): PropertyDecorator {
  return <C extends UpdatingInstance, V>(
    target: StandardPropertyTarget<C, V>,
    context: StandardPropertyContext<C, V>
  ): any => {
    const { kind } = context
    if (kind === 'accessor') {
      const accessorTarget = target as ClassAccessorDecoratorTarget<C, V>
      return {
        set(value: V): void {
          const oldValue = accessorTarget.get.call(this as unknown as C)
          if (value === oldValue) {
            return
          }
          accessorTarget.set.call(this as unknown as C, value)
          if (this.requestUpdate) {
            this.requestUpdate(options)
          }
        },

        get(): V {
          return accessorTarget.get.call(this)
        },
      }
    } else if (kind === 'setter') {
      const { name } = context
      return function (this: UpdatingInstance, value: V): void {
        const oldValue = this[name as keyof UpdatingInstance]
        if (value === oldValue) {
          return
        }
        ;(target as (value: V) => void).call(this, value)
        if (this.requestUpdate) {
          this.requestUpdate(options)
        }
      }
    }
    throw new Error(`Unsupported decorator location: ${kind}`)
  }
}
