import type { PluginContext, NotificationOptions } from './PluginInterfaces.js';
import type { IPluginEventClient } from './interfaces.js';
import type { PluginRegistry } from './PluginRegistry.js';

/**
 * Options for constructing a PluginContext via `createPluginContext`.
 */
export interface PluginContextOptions {
  /** The plugin's unique identifier. */
  pluginId: string;

  /**
   * Optional event client for the `context.events` API.
   * Any object implementing `on(event, handler)` / `off(event, handler)` works —
   * e.g. a MatrixClient, Node.js EventEmitter, or socket.io socket.
   */
  eventClient?: IPluginEventClient;

  /**
   * Called when a plugin invokes `context.notify(...)`.
   * The host is responsible for displaying the notification however it wants.
   */
  onNotify?: (options: NotificationOptions) => void | Promise<void>;
}

/**
 * Creates a PluginContext wired to the given registry.
 * Call this before registering and loading a plugin.
 *
 * @param options - Host-specific dependencies for the plugin.
 * @param registry - The shared PluginRegistry instance.
 * @returns A fully-wired PluginContext ready to be passed to `plugin.onLoad`.
 */
export function createPluginContext(
  options: PluginContextOptions,
  registry: PluginRegistry
): PluginContext {
  const { pluginId, eventClient, onNotify } = options;

  return {
    pluginId,

    commands: {
      register: (command) => {
        registry.registerCommand(pluginId, command);
        registry.addLog(pluginId, 'log', [`Registered command: /${command.name}`]);
      },
      unregister: (name) => {
        registry.unregisterCommand(name);
      },
      execute: async (name, args) => {
        return registry.executeCommand(name, JSON.stringify(args));
      },
    },

    messages: {
      onBeforeSend: (interceptor) => {
        registry.addBeforeSendInterceptor(pluginId, interceptor);
        registry.addLog(pluginId, 'log', ['Registered beforeSend interceptor']);
      },
      onReceive: (interceptor) => {
        registry.addReceiveInterceptor(pluginId, interceptor);
        registry.addLog(pluginId, 'log', ['Registered receive interceptor']);
      },
    },

    ui: {
      registerRenderer: (type, renderer) => {
        registry.registerRenderer(pluginId, type, renderer);
        registry.addLog(pluginId, 'log', [`Registered renderer: ${type}`]);
      },
      unregisterRenderer: (type) => {
        registry.unregisterRenderer(type);
      },
    },

    settings: {
      define: (schema) => {
        registry.defineSettings(pluginId, schema);
        registry.addLog(pluginId, 'log', ['Defined settings schema']);
      },
      get: <T = unknown>(key: string): T | undefined => {
        return registry.getPluginSetting<T>(pluginId, key);
      },
      set: (key, value) => {
        registry.setPluginSetting(pluginId, key, value);
      },
    },

    themes: {
      register: (theme) => {
        registry.registerTheme(pluginId, theme);
        registry.addLog(pluginId, 'log', [`Registered theme: ${theme.name}`]);
      },
      unregister: (themeId) => {
        registry.unregisterTheme(pluginId, themeId);
      },
    },

    events: {
      on: (eventType, handler) => {
        if (!eventClient) {
          console.warn(`[Plugin ${pluginId}] No event client provided — events.on() is a no-op`);
          return;
        }
        registry.addEventHandler(pluginId, eventType, handler);
        eventClient.on(eventType, handler);
        registry.addLog(pluginId, 'log', [`Listening to event: ${eventType}`]);
      },
      off: (eventType, handler) => {
        if (!eventClient) return;
        registry.removeEventHandler(pluginId, eventType, handler);
        eventClient.off(eventType, handler);
      },
    },

    timers: {
      setInterval: (callback, ms) => {
        const id = setInterval(() => {
          try {
            callback();
          } catch (err) {
            registry.addLog(pluginId, 'error', ['Interval error:', err]);
          }
        }, ms) as unknown as number;
        registry.addTimer(pluginId, id);
        return id;
      },
      setTimeout: (callback, ms) => {
        const id = setTimeout(() => {
          try {
            callback();
          } catch (err) {
            registry.addLog(pluginId, 'error', ['Timeout error:', err]);
          }
          registry.removeTimer(pluginId, id);
        }, ms) as unknown as number;
        registry.addTimer(pluginId, id);
        return id;
      },
      clearInterval: (id) => {
        clearInterval(id);
        registry.removeTimer(pluginId, id);
      },
      clearTimeout: (id) => {
        clearTimeout(id);
        registry.removeTimer(pluginId, id);
      },
    },

    notify: async (options) => {
      const opts: NotificationOptions =
        typeof options === 'string' ? { title: 'Plugin', body: options } : options;
      registry.addLog(pluginId, 'log', ['Notification:', opts]);
      if (onNotify) {
        try {
          await onNotify(opts);
        } catch (err) {
          console.error(`[Plugin ${pluginId}] Notification handler error:`, err);
        }
      }
    },

    log: (...args) => {
      console.log(`[Plugin ${pluginId}]`, ...args);
      registry.addLog(pluginId, 'log', args);
    },
    warn: (...args) => {
      console.warn(`[Plugin ${pluginId}]`, ...args);
      registry.addLog(pluginId, 'warn', args);
    },
    error: (...args) => {
      console.error(`[Plugin ${pluginId}]`, ...args);
      registry.addLog(pluginId, 'error', args);
    },

    require: (requiredPluginId) => {
      const exports = registry.getPluginExports(requiredPluginId);
      if (exports === undefined) {
        throw new Error(`Plugin ${requiredPluginId} not found or has no exports`);
      }
      registry.addLog(pluginId, 'log', [`Required plugin: ${requiredPluginId}`]);
      return exports;
    },
  };
}
