'use strict';

class HookBus {
  constructor() {
    this.hooks = new Map();
  }

  register(name, handler, opts = {}) {
    if (!name) throw new Error('hook name is required');
    if (typeof handler !== 'function') throw new Error('hook handler must be a function');
    const entry = {
      id: opts.id || `${name}:${Date.now()}:${Math.random().toString(36).slice(2)}`,
      name,
      handler,
      priority: Number.isFinite(opts.priority) ? opts.priority : 0,
      once: Boolean(opts.once),
    };
    const list = this.hooks.get(name) || [];
    list.push(entry);
    list.sort((a, b) => b.priority - a.priority);
    this.hooks.set(name, list);
    return () => this.unregister(name, entry.id);
  }

  unregister(name, id) {
    const list = this.hooks.get(name);
    if (!list) return false;
    const next = list.filter((entry) => entry.id !== id);
    if (next.length) this.hooks.set(name, next);
    else this.hooks.delete(name);
    return next.length !== list.length;
  }

  has(name) {
    return (this.hooks.get(name) || []).length > 0;
  }

  list(name = '') {
    const entries = name ? (this.hooks.get(name) || []) : [...this.hooks.values()].flat();
    return entries.map(({ id, name: hookName, priority, once }) => ({ id, name: hookName, priority, once }));
  }

  async run(name, ctx = {}, value = undefined) {
    const list = [...(this.hooks.get(name) || [])];
    let current = value;
    const results = [];
    const errors = [];
    for (const entry of list) {
      try {
        const next = await entry.handler(ctx, current);
        results.push({ id: entry.id, changed: next !== undefined });
        if (next !== undefined) current = next;
      } catch (error) {
        errors.push({ id: entry.id, error });
      } finally {
        if (entry.once) this.unregister(name, entry.id);
      }
    }
    return { value: current, results, errors };
  }
}

module.exports = { HookBus };
