/**
 * Global Context - Cross-module communication and state access
 */

import type { HypenModuleInstance } from "./app.js";
import { getStateSnapshot } from "./state.js";
import { TypedEventEmitter, type HypenFrameworkEvents } from "./events.js";

export type ModuleReference<T = unknown> = {
  state: T; // Live proxy state
  setState: (patch: Partial<T>) => void;
  getState: () => T; // Snapshot
};

/**
 * @deprecated Use TypedEventEmitter from events.ts for type-safe events
 */
export type EventHandler = (payload?: unknown) => void;

/**
 * Global Context - Provides access to all modules and cross-module communication
 *
 * @template TEvents - Custom event map (extends HypenFrameworkEvents)
 */
export class HypenGlobalContext<TEvents extends Record<string, unknown> = HypenFrameworkEvents> {
  private modules = new Map<string, HypenModuleInstance>();
  private typedEvents: TypedEventEmitter<TEvents>;

  /**
   * @deprecated Legacy event bus for backward compatibility
   */
  private legacyEventBus = new Map<string, Set<EventHandler>>();

  constructor() {
    this.typedEvents = new TypedEventEmitter<TEvents>();
  }

  /**
   * Get the typed event emitter for type-safe event handling
   */
  get events(): TypedEventEmitter<TEvents> {
    return this.typedEvents;
  }

  /**
   * Register a module instance with an ID
   */
  registerModule(id: string, instance: HypenModuleInstance) {
    if (this.modules.has(id)) {
      console.warn(`Module "${id}" is already registered. Overwriting.`);
    }
    this.modules.set(id, instance);
    console.log(`Registered module: ${id}`);
  }

  /**
   * Unregister a module
   */
  unregisterModule(id: string) {
    this.modules.delete(id);
    console.log(`Unregistered module: ${id}`);
  }

  /**
   * Get a module by ID with type safety
   */
  getModule<T = unknown>(id: string): ModuleReference<T> {
    const module = this.modules.get(id);
    if (!module) {
      throw new Error(
        `Module "${id}" not found. Available modules: ${Array.from(this.modules.keys()).join(", ")}`
      );
    }

    return {
      state: module.getLiveState() as T,
      setState: (patch: Partial<T>) => module.updateState(patch),
      getState: () => module.getState() as T,
    };
  }

  /**
   * Check if a module exists
   */
  hasModule(id: string): boolean {
    return this.modules.has(id);
  }

  /**
   * Get all registered module IDs
   */
  getModuleIds(): string[] {
    return Array.from(this.modules.keys());
  }

  /**
   * Get the entire app state tree (snapshot)
   */
  getGlobalState(): Record<string, unknown> {
    const state: Record<string, unknown> = {};
    this.modules.forEach((module, id) => {
      state[id] = module.getState();
    });
    return state;
  }

  /**
   * Emit an event to the event bus (legacy API for backward compatibility)
   * @deprecated Use `context.events.emit()` for type-safe events
   */
  emit(event: string, payload?: unknown): void {
    const handlers = this.legacyEventBus.get(event);
    if (!handlers || handlers.size === 0) {
      console.log(`Event "${event}" emitted but no listeners`);
    } else {
      console.log(`Emitting event: ${event}`, payload);
      handlers.forEach((handler) => {
        try {
          handler(payload);
        } catch (error) {
          console.error(`Error in event handler for "${event}":`, error);
        }
      });
    }

    // Also emit to typed event system if the event is registered there
    if (this.typedEvents.listenerCount(event as keyof TEvents) > 0) {
      this.typedEvents.emit(event as keyof TEvents, payload as TEvents[keyof TEvents]);
    }
  }

  /**
   * Subscribe to an event (legacy API for backward compatibility)
   * @deprecated Use `context.events.on()` for type-safe events
   */
  on(event: string, handler: EventHandler): () => void {
    if (!this.legacyEventBus.has(event)) {
      this.legacyEventBus.set(event, new Set());
    }

    const handlers = this.legacyEventBus.get(event)!;
    handlers.add(handler);

    console.log(`Listening to event: ${event}`);

    // Return unsubscribe function
    return () => {
      handlers.delete(handler);
      if (handlers.size === 0) {
        this.legacyEventBus.delete(event);
      }
    };
  }

  /**
   * Unsubscribe from an event (legacy API)
   * @deprecated Use the unsubscribe function returned by `context.events.on()`
   */
  off(event: string, handler: EventHandler): void {
    const handlers = this.legacyEventBus.get(event);
    if (handlers) {
      handlers.delete(handler);
      if (handlers.size === 0) {
        this.legacyEventBus.delete(event);
      }
    }
  }

  /**
   * Clear all event listeners for an event (legacy API)
   * @deprecated Use `context.events.removeAllListeners()`
   */
  clearEvent(event: string): void {
    this.legacyEventBus.delete(event);
  }

  /**
   * Clear all event listeners (legacy API)
   * @deprecated Use `context.events.clearAll()`
   */
  clearAllEvents(): void {
    this.legacyEventBus.clear();
  }

  /**
   * Get debug info about the context
   */
  debug(): {
    modules: string[];
    events: string[];
    typedEvents: Array<keyof TEvents>;
    state: Record<string, unknown>;
  } {
    return {
      modules: this.getModuleIds(),
      events: Array.from(this.legacyEventBus.keys()),
      typedEvents: this.typedEvents.eventNames(),
      state: this.getGlobalState(),
    };
  }
}
