import { PlatformError } from './errors.mjs';

const EVENT_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;

export class EventBus {
  #listeners = new Map();
  #maxListeners;

  constructor({ maxListenersPerEvent = 50 } = {}) {
    this.#maxListeners = Math.max(1, Number(maxListenersPerEvent) || 50);
  }

  on(eventName, handler, { owner = 'core', once = false } = {}) {
    if (!EVENT_PATTERN.test(String(eventName))) {
      throw new PlatformError(`Ungültiger Eventname: ${eventName}`, { code: 'EVENT_NAME_INVALID', status: 500 });
    }
    if (typeof handler !== 'function') {
      throw new PlatformError('Event-Handler muss eine Funktion sein.', { code: 'EVENT_HANDLER_INVALID', status: 500 });
    }
    const listeners = this.#listeners.get(eventName) || [];
    if (listeners.length >= this.#maxListeners) {
      throw new PlatformError(`Zu viele Listener für ${eventName}.`, { code: 'EVENT_LISTENER_LIMIT', status: 500 });
    }
    const entry = Object.freeze({ handler, owner: String(owner || 'core'), once: Boolean(once) });
    listeners.push(entry);
    this.#listeners.set(eventName, listeners);
    return () => this.off(eventName, handler);
  }

  once(eventName, handler, options = {}) {
    return this.on(eventName, handler, { ...options, once: true });
  }

  off(eventName, handler) {
    const listeners = this.#listeners.get(eventName) || [];
    const remaining = listeners.filter(entry => entry.handler !== handler);
    if (remaining.length) this.#listeners.set(eventName, remaining);
    else this.#listeners.delete(eventName);
    return remaining.length !== listeners.length;
  }

  removeOwner(owner) {
    let removed = 0;
    for (const [eventName, listeners] of this.#listeners) {
      const remaining = listeners.filter(entry => {
        const match = entry.owner === owner;
        if (match) removed += 1;
        return !match;
      });
      if (remaining.length) this.#listeners.set(eventName, remaining);
      else this.#listeners.delete(eventName);
    }
    return removed;
  }

  async emit(eventName, payload = null, context = {}) {
    const listeners = [...(this.#listeners.get(eventName) || [])];
    const results = [];
    for (const entry of listeners) {
      try {
        results.push({ owner: entry.owner, ok: true, value: await entry.handler(payload, Object.freeze({ ...context, eventName })) });
      } catch (error) {
        results.push({ owner: entry.owner, ok: false, error });
      } finally {
        if (entry.once) this.off(eventName, entry.handler);
      }
    }
    return results;
  }

  snapshot() {
    return [...this.#listeners.entries()].map(([event, listeners]) => ({
      event,
      listeners: listeners.map(entry => ({ owner: entry.owner, once: entry.once })),
    }));
  }
}
