import { Merge, SetOptional } from "type-fest"
import { IComputedValueOptions } from "mobx"
import { PrependArgument } from "../../utils/PrependArgument"
import { nodeTypeKey, NodeTypeKey, NodeTypeValue } from "./nodeType"
import { Dispose } from "../../utils/disposable"

/**
 * Base interface for node types with type-specific capabilities.
 *
 * @template TNode - The object type representing a node
 * @template TCapabilities - Node type capabilities, can be "typed" and/or "keyed"
 * @template TOptional - Optional properties of the node type
 * @template TKey - Property key used for node identification (if keyed)
 * @template TOther - Additional type information
 */
interface BaseNodeTypeWithType<
  TNode extends object,
  TCapabilities extends "typed" | "keyed",
  TOptional extends keyof TNode,
  TKey extends keyof TNode | never,
  TOther,
> {
  /**
   * Unique identifier for this node type
   */
  typeId: TNode extends {
    [nodeTypeKey]: NodeTypeValue
  }
    ? TNode[NodeTypeKey]
    : undefined

  /**
   * Checks if the node is of a specific type
   *
   * @param node - Node to check
   * @returns true if the node type matches, false otherwise
   */
  nodeIsOfType(node: object): node is TNode

  /**
   * Unregisters this node type
   */
  unregister(): void

  /**
   * Unregisters this node type (disposable pattern)
   */
  [Symbol.dispose](): void

  /**
   * Registers a callback to run when nodes of this type are initialized
   *
   * @param callback - Function to execute when a node is initialized
   *
   * @returns The same node type with the added initialization callback
   */
  onInit(
    callback: (node: TNode) => void
  ): BaseNodeType<TNode, TCapabilities, TOptional, TKey, TOther>

  _initNode(node: TNode): void

  _addOnInit(callback: (node: TNode) => void): Dispose

  /**
   * Configures this type to use a specific property as the node key
   *
   * @template TKey - Property key in the node type
   * @param key - Property name to use as the node key
   * @returns A keyed node type using the specified property as key
   */
  withKey<TKey extends keyof TNode>(
    key: TKey
  ): BaseNodeType<TNode, TCapabilities | "keyed", TOptional, TKey, TOther>

  /**
   * Makes this node type immutable.
   *
   * Immutable nodes cannot be modified after creation and sub-objects are not turned into nodes.
   * This is useful for creating read-only nodes or for performance optimizations.
   */
  frozen(): BaseNodeType<TNode, TCapabilities, TOptional, TKey, TOther>

  /**
   * true if the node type is frozen, false otherwise
   */
  isFrozen: boolean
}

/**
 * Interface that represents a node type with a key property.
 * Provides functionality for accessing and finding nodes by their unique keys.
 *
 * @template TNode - The node type
 * @template TKey - The key of the node's unique identifier property
 */
interface BaseNodeTypeWithKey<TNode, TKey extends keyof TNode> {
  /**
   * Property name containing the node's unique key
   */
  key: TKey

  /**
   * Gets the unique key value for a node
   *
   * @param node - Node to get the key from
   * @returns The node's key value or undefined
   */
  getKey(node: TNode): TNode[TKey] | undefined

  /**
   * Retrieves a node by its key (if it exists)
   *
   * @param key - Key to search for
   * @returns The node with the specified key or undefined
   */
  findByKey(key: TNode[TKey]): TNode | undefined
}

/**
 * Base node type definition with core functionality
 *
 * @template TNode - Node structure that adheres to this type
 * @template TOptional - Optional keys in the node structure
 * @template TCapabilities - Type of capabilities (untyped, typed or keyed)
 * @template TKey - Key field in the node structure (if any)
 * @template TOther - Additional properties and methods
 */
export type BaseNodeType<
  TNode extends object,
  TCapabilities extends "untyped" | "typed" | "keyed",
  TOptional extends keyof TNode,
  TKey extends keyof TNode | never,
  TOther,
> = {
  /**
   * Node constructor.
   * Requires all keys from TNode except those in TOptional (which may be omitted).
   */
  (data: SetOptional<TNode, TOptional | TKey>): TNode

  /**
   * Returns a snapshot based on the provided data.
   */
  snapshot(data: SetOptional<TNode, TOptional | TKey>): TNode

  /**
   * Adds volatile state properties to nodes of this type
   *
   * Volatile state is not persisted in snapshots and is local to each node instance.
   *
   * @template TVolatiles - Record of volatile property getter functions
   * @param volatile - Object where each key defines a getter function for volatile state
   * @returns The same NodeType with added accessor methods for the volatile state
   */
  volatile<TVolatiles extends Record<string, () => any>>(
    volatile: TVolatiles
  ): BaseNodeType<
    TNode,
    TCapabilities,
    TOptional,
    TKey,
    Merge<TOther, VolatileAccessors<TVolatiles, TNode>>
  >

  /**
   * Registers action methods for nodes of this type
   *
   * Actions are methods that can modify the node state and are automatically
   * wrapped in MobX actions for proper state tracking.
   *
   * @template TActions - Record of action methods
   *
   * @param actions - An object of action methods
   *
   * @returns The same NodeType with added action methods that accept a node as their first parameter
   */
  actions<TActions extends Record<string, (this: TNode, ...args: any[]) => any>>(
    actions: TActions
  ): BaseNodeType<
    TNode,
    TCapabilities,
    TOptional,
    TKey,
    Merge<
      TOther,
      {
        [k in keyof TActions]: PrependArgument<TActions[k], TNode>
      }
    >
  >

  /**
   * Registers getter methods for nodes of this type
   *
   * Getters are methods that derive values from the node state without modifying it.
   *
   * @template TGetters - Record of getter methods
   *
   * @param getters - An object of getter methods
   *
   * @returns The same NodeType with added getter methods that accept a node as their first parameter
   */
  getters<TGetters extends Record<string, (this: TNode, ...args: any[]) => any>>(
    getters: TGetters
  ): BaseNodeType<
    TNode,
    TCapabilities,
    TOptional,
    TKey,
    Merge<
      TOther,
      {
        [k in keyof TGetters]: PrependArgument<TGetters[k], TNode>
      }
    >
  >

  /**
   * Registers computed methods for nodes of this type
   *
   * Computed methods derive values from the node state and are automatically
   * memoized by MobX for performance optimization.
   *
   * @template TComputeds - Record of computed properties
   * @param computeds - Function that receives a node and returns an object of computed accessor methods
   * @returns The same NodeType with added computed methods that accept a node as their first parameter
   */
  computeds<TComputeds extends Record<string, ComputedEntry<TNode, any>>>(
    computeds: TComputeds
  ): BaseNodeType<
    TNode,
    TCapabilities,
    TOptional,
    TKey,
    Merge<
      TOther,
      {
        [k in keyof TComputeds]: TComputeds[k] extends () => any
          ? PrependArgument<TComputeds[k], TNode>
          : TComputeds[k] extends ComputedFnWithOptions<TNode, any>
            ? PrependArgument<TComputeds[k]["get"], TNode>
            : never
      }
    >
  >

  /**
   * Generates setter methods for specified properties
   *
   * @param properties - Names of properties to create setters for
   * @returns The same NodeType with added setter methods
   */
  settersFor<K extends keyof TNode & string>(
    ...properties: readonly K[]
  ): BaseNodeType<
    TNode,
    TCapabilities,
    TOptional,
    TKey,
    Merge<
      TOther,
      {
        [P in K as `set${Capitalize<P>}`]: (node: TNode, value: Readonly<TNode[P]>) => void
      }
    >
  >

  /**
   * Define default values for keys in TOptional.
   * When omitted, those properties are filled with the results of these generators.
   *
   * @template TGen - Record of default value generators
   */
  defaults<TGen extends { [K in keyof TNode]?: () => TNode[K] }>(
    defaultGenerators: TGen
  ): BaseNodeType<TNode, TCapabilities, TOptional | (keyof TGen & keyof TNode), TKey, TOther>

  /**
   * Default generators defined so far.
   */
  defaultGenerators?: { [K in keyof TNode]?: () => TNode[K] }

  /**
   * Extend this type from another untyped node type.
   * The base node type can be a subset of the current node type.
   *
   * @template TBaseNode - Base node type (must be a subset of TNode)
   * @template TExtendedOther - Additional properties and methods from the base type
   * @param nodeType - Node type to extend from
   */
  extends<
    TExtendedNode extends object,
    TExtendedOptional extends keyof TExtendedNode,
    TExtendedOther,
  >(
    nodeType: BaseNodeType<TExtendedNode, "untyped", TExtendedOptional, never, TExtendedOther>
  ): TNode extends TExtendedNode
    ? BaseNodeType<
        TNode,
        TCapabilities,
        TOptional | TExtendedOptional,
        TKey,
        Merge<TOther, TExtendedOther>
      >
    : never

  _extendsKeys: Set<string>
} & (TCapabilities extends "typed" | "keyed" // is it a typed node?
  ? BaseNodeTypeWithType<TNode, TCapabilities, TOptional, TKey, TOther> &
      (TCapabilities extends "keyed" // is it a keyed node?
        ? BaseNodeTypeWithKey<TNode, TKey>
        : unknown)
  : unknown) &
  TOther

/**
 * Configuration for a computed property with options
 *
 * @template TThis - This type for the computed function
 * @template T - Return type of the computed value
 */
export type ComputedFnWithOptions<TThis, T> = { get: (this: TThis) => T } & Omit<
  IComputedValueOptions<T>,
  "get" | "set"
>

/**
 * Computed property definition that can be a function or configuration object
 *
 * @template TThis - This type for the computed function
 * @template T - Return type of the computed value
 */
export type ComputedEntry<TThis, T> = ((this: TThis) => T) | ComputedFnWithOptions<TThis, T>

/**
 * Generates accessor methods for volatile properties
 *
 * @template T - Record of volatile property getter functions
 * @template TNode - The node type these accessors operate on
 */
export type VolatileAccessors<T extends Record<string, () => any>, TNode> = {
  [K in keyof T as `set${Capitalize<string & K>}`]: (n: TNode, value: ReturnType<T[K]>) => void
} & {
  [K in keyof T as `get${Capitalize<string & K>}`]: (n: TNode) => ReturnType<T[K]>
} & {
  [K in keyof T as `reset${Capitalize<string & K>}`]: (n: TNode) => void
}
