/**
 * Browser-compatible wrapper around the WASM engine
 * Uses web target for browser environments
 */

// Dynamic import path - will be configured at build time
// For browser, the WASM needs to be served and initialized explicitly
let wasmInit: ((path?: string) => Promise<void>) | null = null;
let WasmEngineClass: any = null;

export type Patch = {
  type: "create" | "setProp" | "setText" | "insert" | "move" | "remove" | "attachEvent" | "detachEvent";
  id?: string;
  element_type?: string;
  props?: Record<string, any> | Map<string, any>;
  name?: string;
  value?: any;
  text?: string;
  parent_id?: string;
  before_id?: string;
  event_name?: 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;

export interface EngineInitOptions {
  /**
   * URL to the WASM binary file.
   * Default: loads from unpkg CDN
   * For production, consider serving from your own domain for better performance.
   */
  wasmUrl?: string;

  /**
   * URL to the WASM JS glue code.
   * Default: loads from unpkg CDN
   * For self-hosting, point to your own copy of hypen_engine.js
   */
  jsUrl?: string;
}

/**
 * Recursively convert Maps and nested structures to plain objects
 */
function mapToObject(value: any): any {
  if (value instanceof Map) {
    const obj: Record<string, any> = {};
    for (const [key, val] of value.entries()) {
      obj[key] = mapToObject(val);
    }
    return obj;
  } else if (Array.isArray(value)) {
    return value.map(mapToObject);
  } else if (value && typeof value === 'object' && value.constructor === Object) {
    const obj: Record<string, any> = {};
    for (const [key, val] of Object.entries(value)) {
      obj[key] = mapToObject(val);
    }
    return obj;
  }
  return value;
}

/**
 * Engine wraps the WASM engine and provides a TypeScript-friendly API
 * Browser version with explicit WASM initialization
 */
export class Engine {
  private wasmEngine: any = null;
  private initialized = false;

  /**
   * Initialize the WASM module
   * @param options - Initialization options including wasmUrl and jsUrl
   */
  async init(options: EngineInitOptions = {}): Promise<void> {
    if (this.initialized) return;

    // Default to CDN for zero-config experience
    const cdnBase = "https://unpkg.com/@hypen-space/core@0.2.10/wasm-browser";
    const jsUrl = options.jsUrl ?? `${cdnBase}/hypen_engine.js`;
    const wasmUrl = options.wasmUrl ?? `${cdnBase}/hypen_engine_bg.wasm`;

    // Dynamically import the WASM JS glue code from CDN (or custom URL)
    // Using dynamic import with variable URL to avoid bundler resolution
    try {
      const wasmModule: any = await import(/* @vite-ignore */ jsUrl);
      wasmInit = wasmModule.default;
      WasmEngineClass = wasmModule.WasmEngine;

      // Initialize WASM with explicit path
      await wasmInit!(wasmUrl);

      this.wasmEngine = new WasmEngineClass();
      this.initialized = true;
    } catch (error) {
      console.error("[Hypen] Failed to initialize WASM engine:", error);
      throw error;
    }
  }

  /**
   * Ensure the engine is initialized before operations
   */
  private ensureInitialized(): any {
    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
   */
  notifyStateChange(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:", 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();
    console.log(`[Engine] Action dispatched: ${name}`);
    engine.dispatchAction(name, payload ?? null);
  }

  /**
   * Register an action handler
   */
  onAction(actionName: string, handler: ActionHandler): void {
    const engine = this.ensureInitialized();
    engine.onAction(actionName, (action: Action) => {
      const normalizedAction: Action = {
        ...action,
        payload: action.payload ? mapToObject(action.payload) : action.payload,
      };
      Promise.resolve(handler(normalizedAction)).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(): bigint {
    const engine = this.ensureInitialized();
    return 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);
  }
}
