/**
 * Low-level wrapper around the WASM engine
 * Node.js / Bundler target
 */

// WASM module types
import { WasmEngine } from "../wasm-node/hypen_engine.js";

export type Patch = {
  type: "create" | "setProp" | "setText" | "insert" | "move" | "remove" | "attachEvent" | "detachEvent";
  id?: string;
  elementType?: string;
  props?: Record<string, any>;
  name?: string;
  value?: any;
  text?: string;
  parentId?: string;
  beforeId?: string;
  eventName?: string;
};

export type Action = {
  name: string;
  payload?: any;
  sender?: string;
};

export type RenderCallback = (patches: Patch[]) => void;
export type ActionHandler = (action: Action) => void | Promise<void>;

export type ResolvedComponent = {
  source: string;
  path: string;
};

export type ComponentResolver = (
  componentName: string,
  contextPath: string | null
) => ResolvedComponent | null;

/**
 * Engine wraps the WASM engine and provides a TypeScript-friendly API
 */
export class Engine {
  private wasmEngine: WasmEngine | null = null;
  private initialized = false;

  /**
   * Initialize the WASM module
   */
  async init(): Promise<void> {
    if (this.initialized) return;

    // For bundler target, WASM is auto-initialized
    this.wasmEngine = new WasmEngine();
    this.initialized = true;
  }

  /**
   * Ensure the engine is initialized before operations
   */
  private ensureInitialized(): WasmEngine {
    if (!this.wasmEngine) {
      throw new Error("Engine not initialized. Call init() first.");
    }
    return this.wasmEngine;
  }

  /**
   * Set the render callback that receives patches
   */
  setRenderCallback(callback: RenderCallback): void {
    const engine = this.ensureInitialized();
    engine.setRenderCallback((patches: Patch[]) => {
      callback(patches);
    });
  }

  /**
   * Set the component resolver for dynamic component composition
   */
  setComponentResolver(resolver: ComponentResolver): void {
    const engine = this.ensureInitialized();
    engine.setComponentResolver((componentName: string, contextPath: string | null) => {
      const result = resolver(componentName, contextPath);
      return result;
    });
  }

  /**
   * Parse and render Hypen DSL source code
   */
  renderSource(source: string): void {
    const engine = this.ensureInitialized();
    engine.renderSource(source);
  }

  /**
   * Render a lazy component (for lazy route loading)
   */
  renderLazyComponent(source: string): void {
    const engine = this.ensureInitialized();
    engine.renderLazyComponent(source);
  }

  /**
   * Render a component into a specific parent node (subtree rendering)
   */
  renderInto(source: string, parentNodeId: string, state: Record<string, any>): void {
    const engine = this.ensureInitialized();
    const safeState = JSON.parse(JSON.stringify(state));
    engine.renderInto(source, parentNodeId, safeState);
  }

  /**
   * Notify the engine of state changes using sparse updates
   */
  notifyStateChange(paths: string[], values: Record<string, any>): void {
    const engine = this.ensureInitialized();

    if (paths.length === 0) {
      return;
    }

    // Clone values to prevent proxy objects from causing issues
    const plainValues = JSON.parse(JSON.stringify(values));
    engine.updateStateSparse(paths, plainValues);
    console.debug("[Hypen] State changed (sparse):", paths);
  }

  /**
   * Notify the engine of state changes using full state replacement
   * @deprecated Use notifyStateChange with sparse values instead
   */
  notifyStateChangeFull(paths: string[], currentState: Record<string, any>): void {
    const engine = this.ensureInitialized();

    const plainObject = JSON.parse(JSON.stringify(currentState));
    engine.updateState(plainObject);

    if (paths.length > 0) {
      console.debug("[Hypen] State changed (full):", paths);
    }
  }

  /**
   * Update state (triggers re-render of affected nodes)
   * @deprecated Use notifyStateChange instead
   */
  updateState(statePatch: Record<string, any>): void {
    const engine = this.ensureInitialized();
    const plainObject = JSON.parse(JSON.stringify(statePatch));
    engine.updateState(plainObject);
  }

  /**
   * Dispatch an action
   */
  dispatchAction(name: string, payload?: any): void {
    const engine = this.ensureInitialized();
    engine.dispatchAction(name, payload ?? null);
  }

  /**
   * Register an action handler
   */
  onAction(actionName: string, handler: ActionHandler): void {
    const engine = this.ensureInitialized();
    engine.onAction(actionName, (action: Action) => {
      Promise.resolve(handler(action)).catch(console.error);
    });
  }

  /**
   * Initialize a module
   */
  setModule(
    name: string,
    actions: string[],
    stateKeys: string[],
    initialState: Record<string, any>
  ): void {
    const engine = this.ensureInitialized();
    engine.setModule(name, actions, stateKeys, initialState);
  }

  /**
   * Get the current revision number
   */
  getRevision(): number {
    const engine = this.ensureInitialized();
    return Number(engine.getRevision());
  }

  /**
   * Clear the engine tree
   */
  clearTree(): void {
    const engine = this.ensureInitialized();
    engine.clearTree();
  }

  /**
   * Debug method to inspect parsed components
   */
  debugParseComponent(source: string): string {
    const engine = this.ensureInitialized();
    return engine.debugParseComponent(source);
  }
}
