'use strict';

class RuntimeDecisionHookBus {
  constructor({ handlers = {}, telemetry = null } = {}) {
    this.handlers = new Map();
    this.telemetry = telemetry || null;
    for (const [event, list] of Object.entries(handlers || {})) {
      for (const handler of Array.isArray(list) ? list : [list]) {
        this.use(event, handler);
      }
    }
  }

  use(event, handler) {
    if (!event || typeof handler !== 'function') return this;
    const name = String(event);
    if (!this.handlers.has(name)) this.handlers.set(name, []);
    this.handlers.get(name).push(handler);
    return this;
  }

  async run(event, payload = {}, ctx = {}) {
    const name = String(event || '');
    const handlers = this.handlers.get(name) || [];
    let decision = emptyDecision();
    for (const handler of handlers) {
      try {
        const result = normalizeDecision(await handler({
          ...payload,
          decision,
          event: name,
          ctx,
        }));
        decision = mergeDecision(decision, result);
        if (decision.stop || decision.denied) break;
      } catch (error) {
        const hookError = {
          event: name,
          error: error?.message || String(error),
        };
        decision.hookErrors.push(hookError);
        try {
          this.telemetry?.track?.('runtime_hook_error', {
            event: name,
            error_type: error?.name || 'Error',
          });
        } catch {}
      }
    }
    return decision;
  }
}

function emptyDecision() {
  return {
    denied: false,
    stop: false,
    reason: '',
    input: undefined,
    result: undefined,
    output: undefined,
    additionalContext: [],
    metadata: {},
    hookErrors: [],
  };
}

function normalizeDecision(value) {
  if (!value) return emptyDecision();
  if (Array.isArray(value)) {
    return value.reduce((acc, item) => mergeDecision(acc, normalizeDecision(item)), emptyDecision());
  }
  if (typeof value !== 'object') return emptyDecision();
  const denied = value.denied === true || value.deny === true || value.behavior === 'deny';
  const stop = value.stop === true || value.preventContinuation === true;
  const reason = String(value.reason || value.message || value.stopReason || '');
  return {
    denied,
    stop,
    reason,
    input: Object.prototype.hasOwnProperty.call(value, 'input') ? value.input
      : Object.prototype.hasOwnProperty.call(value, 'updatedInput') ? value.updatedInput
      : undefined,
    result: Object.prototype.hasOwnProperty.call(value, 'result') ? value.result
      : Object.prototype.hasOwnProperty.call(value, 'output') ? value.output
      : undefined,
    output: Object.prototype.hasOwnProperty.call(value, 'output') ? value.output : undefined,
    additionalContext: Array.isArray(value.additionalContext) ? value.additionalContext
      : value.additionalContext ? [value.additionalContext]
      : [],
    metadata: value.metadata && typeof value.metadata === 'object' ? { ...value.metadata } : {},
    hookErrors: Array.isArray(value.hookErrors) ? value.hookErrors : [],
  };
}

function mergeDecision(a = emptyDecision(), b = emptyDecision()) {
  return {
    denied: a.denied || b.denied,
    stop: a.stop || b.stop,
    reason: b.reason || a.reason || '',
    input: b.input !== undefined ? b.input : a.input,
    result: b.result !== undefined ? b.result : a.result,
    output: b.output !== undefined ? b.output : a.output,
    additionalContext: [...(a.additionalContext || []), ...(b.additionalContext || [])],
    metadata: { ...(a.metadata || {}), ...(b.metadata || {}) },
    hookErrors: [...(a.hookErrors || []), ...(b.hookErrors || [])],
  };
}

module.exports = {
  RuntimeDecisionHookBus,
  emptyDecision,
  mergeDecision,
  normalizeDecision,
};
