'use strict';

const { RuntimeEventWriter, createRuntimeEvent } = require('./runtime-events');

class LifecycleHookBus {
  constructor({ events = null, middleware = null, runtimeEvents = null, now = () => new Date().toISOString(), defaults = {} } = {}) {
    this.events = events;
    this.middleware = middleware;
    this.runtimeEvents = runtimeEvents || new RuntimeEventWriter({ events, now, defaults });
    this.now = now;
    this.defaults = defaults;
  }

  async emit(type, payload = {}, defaults = {}) {
    const event = this.runtimeEvents.emit({ type, payload }, defaults);
    if (this.events?.emit) {
      this.events.emit(`lifecycle.${type}`, event);
    }
    if (this.middleware?.run) {
      await this.middleware.run('onEvent', {
        ...(this.defaults || {}),
        ...(defaults || {}),
        lifecycleEvent: type,
      }, {
        ...event,
        type: `lifecycle.${type}`,
      });
    }
    return event;
  }

  async wrap(type, payload, fn, defaults = {}) {
    await this.emit(`${type}_started`, payload, defaults);
    try {
      const result = await fn();
      await this.emit(`${type}_finished`, { ...payload, result: summarizeResult(result) }, defaults);
      return result;
    } catch (err) {
      await this.emit(`${type}_failed`, { ...payload, error: err.message }, defaults);
      throw err;
    }
  }

  child(defaults = {}) {
    return new LifecycleHookBus({
      events: this.events,
      middleware: this.middleware,
      runtimeEvents: this.runtimeEvents.child(defaults),
      now: this.now,
      defaults: { ...this.defaults, ...defaults },
    });
  }
}

function summarizeResult(result) {
  if (result == null) return null;
  if (typeof result === 'string') return { type: 'string', chars: result.length };
  if (typeof result !== 'object') return { type: typeof result };
  return {
    type: 'object',
    keys: Object.keys(result).sort().slice(0, 20),
    ok: result.ok === true,
    error: result.error ? String(result.error) : '',
  };
}

function normalizeLifecycleEvent(input = {}, defaults = {}) {
  return createRuntimeEvent(input, defaults);
}

module.exports = {
  LifecycleHookBus,
  normalizeLifecycleEvent,
  summarizeResult,
};
