/**
 * Hypen App Builder API
 * Implements the stateful module system from RFC-0001
 */

import type { Action } from "./engine.js";

// Interface for engine compatibility (works with both engine.js and engine.browser.js)
export interface IEngine {
  setModule(name: string, actions: string[], stateKeys: string[], initialState: unknown): void;
  onAction(actionName: string, handler: (action: Action) => void | Promise<void>): void;
  notifyStateChange(paths: string[], changedValues: Record<string, unknown>): void;
}

import { createObservableState, type StateChange, getStateSnapshot } from "./state.js";
import type { HypenRouter } from "./router.js";
import type { HypenGlobalContext, ModuleReference } from "./context.js";

export type RouterContext = {
  root: HypenRouter;
  current?: HypenRouter;
  parent?: HypenRouter;
};

export type ActionContext = {
  name: string;
  payload?: unknown;
  sender?: string;
};

export type ActionNext = {
  router: HypenRouter; // Direct access to router instance (from nearest parent)
};

export type GlobalContext = {
  getModule: <T = unknown>(id: string) => ModuleReference<T>;
  hasModule: (id: string) => boolean;
  getModuleIds: () => string[];
  getGlobalState: () => Record<string, unknown>;
  emit: (event: string, payload?: unknown) => void;
  on: (event: string, handler: (payload?: unknown) => void) => () => void;
  __router?: unknown; // Internal: router instance for built-in Router component
};

export type LifecycleHandler<T> = (
  state: T,
  context?: GlobalContext
) => void | Promise<void>;

/**
 * Action handler context - all parameters available explicitly
 */
export interface ActionHandlerContext<T> {
  action: ActionContext;
  state: T;
  next: ActionNext;
  context: GlobalContext;
}

/**
 * Action handler - receives all context in a single object
 */
export type ActionHandler<T> = (ctx: ActionHandlerContext<T>) => void | Promise<void>;

export interface HypenModuleDefinition<T = unknown> {
  name?: string;
  actions: string[];
  stateKeys: string[];
  persist?: boolean;
  version?: number;
  initialState: T;
  handlers: {
    onCreated?: LifecycleHandler<T>;
    onAction: Map<string, ActionHandler<T>>;
    onDestroyed?: LifecycleHandler<T>;
  };
}

/**
 * Alias for HypenModuleDefinition for backward compatibility
 */
export type HypenModule<T = unknown> = HypenModuleDefinition<T>;

/**
 * Builder for creating Hypen app modules
 */
export class HypenAppBuilder<T> {
  private initialState: T;
  private options: { persist?: boolean; version?: number; name?: string };
  private createdHandler?: LifecycleHandler<T>;
  private actionHandlers: Map<string, ActionHandler<T>> = new Map();
  private destroyedHandler?: LifecycleHandler<T>;

  constructor(
    initialState: T,
    options?: { persist?: boolean; version?: number; name?: string }
  ) {
    this.initialState = initialState;
    this.options = options || {};
  }

  /**
   * Register a handler for module creation
   */
  onCreated(fn: LifecycleHandler<T>): this {
    this.createdHandler = fn;
    return this;
  }

  /**
   * Register a handler for a specific action
   */
  onAction(name: string, fn: ActionHandler<T>): this {
    this.actionHandlers.set(name, fn);
    return this;
  }

  /**
   * Register a handler for module destruction
   */
  onDestroyed(fn: LifecycleHandler<T>): this {
    this.destroyedHandler = fn;
    return this;
  }

  /**
   * Build the module definition
   */
  build(): HypenModuleDefinition<T> {
    // Safe way to get keys from initialState
    const stateKeys = this.initialState !== null && typeof this.initialState === 'object'
      ? Object.keys(this.initialState)
      : [];

    return {
      name: this.options.name,
      actions: Array.from(this.actionHandlers.keys()),
      stateKeys,
      persist: this.options.persist,
      version: this.options.version,
      initialState: this.initialState,
      handlers: {
        onCreated: this.createdHandler,
        onAction: this.actionHandlers,
        onDestroyed: this.destroyedHandler,
      },
    };
  }
}

/**
 * Hypen App API
 */
export class HypenApp {
  /**
   * Define the initial state for a module
   */
  defineState<T>(
    initial: T,
    options?: { persist?: boolean; version?: number; name?: string }
  ): HypenAppBuilder<T> {
    return new HypenAppBuilder(initial, options);
  }
}

/**
 * The main app instance for creating modules
 */
export const app = new HypenApp();

/**
 * Module Instance - manages a running module with typed state
 */
export class HypenModuleInstance<T extends object = any> {
  private engine: IEngine;
  private definition: HypenModuleDefinition<T>;
  private state: T;
  private isDestroyed = false;
  private routerContext?: RouterContext;
  private globalContext?: HypenGlobalContext;
  private stateChangeCallbacks: Array<() => void> = [];

  constructor(
    engine: IEngine,
    definition: HypenModuleDefinition<T>,
    routerContext?: RouterContext,
    globalContext?: HypenGlobalContext
  ) {
    this.engine = engine;
    this.definition = definition;
    this.routerContext = routerContext;
    this.globalContext = globalContext;

    // Create observable state that sends only changed paths and values to engine
    this.state = createObservableState<T>(definition.initialState as T & object, {
      onChange: (change: StateChange) => {
        // Send only the changed paths and their new values (not the whole state)
        this.engine.notifyStateChange(change.paths, change.newValues);
        // Notify all registered callbacks
        this.stateChangeCallbacks.forEach(cb => cb());
      },
    });

    // Register with engine (initial state registration)
    this.engine.setModule(
      definition.name || "AnonymousModule",
      definition.actions,
      definition.stateKeys,
      getStateSnapshot(this.state)
    );

    // Register action handlers with flexible parameter support
    for (const [actionName, handler] of definition.handlers.onAction) {
      console.log(`[ModuleInstance] Registering action handler: ${actionName} for module ${definition.name}`);
      this.engine.onAction(actionName, async (action: Action) => {
        console.log(`[ModuleInstance] Action handler fired: ${actionName}`, action);

        const actionCtx: ActionContext = {
          name: action.name,
          payload: action.payload,
          sender: action.sender,
        };

        const next: ActionNext = {
          router: this.routerContext?.root || (null as any),
        };

        const context: GlobalContext | undefined = this.globalContext
          ? this.createGlobalContextAPI()
          : undefined;

        try {
          await handler({
            action: actionCtx,
            state: this.state,
            next,
            context: context!,
          });
          console.log(`[ModuleInstance] Action handler completed: ${actionName}`);
        } catch (error) {
          console.error(`[ModuleInstance] Action handler error for ${actionName}:`, error);
          throw error; // Re-throw to allow callers to handle the error
        }
      });
    }

    // Call onCreated
    this.callCreatedHandler();
  }

  /**
   * Create the global context API for this module
   */
  private createGlobalContextAPI(): GlobalContext {
    if (!this.globalContext) {
      throw new Error("Global context not available");
    }

    const ctx = this.globalContext;
    const api: GlobalContext = {
      getModule: (id: string) => ctx.getModule(id),
      hasModule: (id: string) => ctx.hasModule(id),
      getModuleIds: () => ctx.getModuleIds(),
      getGlobalState: () => ctx.getGlobalState(),
      emit: (event: string, payload?: any) => ctx.emit(event, payload),
      on: (event: string, handler: (payload?: any) => void) =>
        ctx.on(event, handler),
    };

    // Expose router and hypen engine for built-in components (if available)
    if ((ctx as any).__router) {
      api.__router = (ctx as any).__router;
    }
    if ((ctx as any).__hypenEngine) {
      (api as any).__hypenEngine = (ctx as any).__hypenEngine;
    }

    return api;
  }

  /**
   * Call the onCreated handler
   */
  private async callCreatedHandler(): Promise<void> {
    if (this.definition.handlers.onCreated) {
      const context = this.globalContext ? this.createGlobalContextAPI() : undefined;
      await this.definition.handlers.onCreated(this.state, context);
    }
  }

  /**
   * Register a callback to be notified when state changes
   */
  onStateChange(callback: () => void): void {
    this.stateChangeCallbacks.push(callback);
  }

  /**
   * Destroy the module instance
   */
  async destroy(): Promise<void> {
    if (this.isDestroyed) return;

    if (this.definition.handlers.onDestroyed) {
      await this.definition.handlers.onDestroyed(this.state);
    }

    this.isDestroyed = true;
  }

  /**
   * Get the current state (returns a snapshot)
   */
  getState(): T {
    return getStateSnapshot(this.state);
  }

  /**
   * Get the live observable state
   */
  getLiveState(): T {
    return this.state;
  }

  /**
   * Update state directly (merges with existing state)
   */
  updateState(patch: Partial<T>): void {
    Object.assign(this.state, patch);
  }
}
