// recordWorldLifecycle — the single broadcast helper every host-cp
// surface uses to emit a lifecycle transition.
//
// Emits TWO event types on the host-stream:
//
//   1. event: 'world.lifecycle'  → live SSE consumers (SPA, MCP, etc.).
//      Shape: { worldId, phase, at, evidence?, failureKind? }
//
//   2. event: 'span'             → NDJSON trace sink (PR #915 + follow-ups).
//      Shape: { name: 'world.lifecycle', startedAt: at, endedAt: at,
//               attributes: { worldId, phase, evidence?, failureKind? },
//               exit: { _tag: 'Success' | 'Failure', reason? } }
//
// The dual-emit keeps live consumers and trace consumers on the same
// substrate without either path coupling to the other. The README jq
// example `select(.name == "world.lifecycle" ...)` matches the span
// emission; the SPA's `useHostStream().subscribe('world.lifecycle', ...)`
// matches the live emission.
//
// Failed transitions auto-classify via classifyStartupFailure(evidence)
// when caller passes evidence but omits an explicit failureKind. Callers
// MAY provide their own failureKind to override the inference (e.g.
// docker SIGKILL — the caller knows it was ProviderProcessGone before
// the classifier could trip its time-thresholds).

import { TERMINAL_PHASES, WorldLifecyclePhase } from './phases.mjs';
import { classifyStartupFailure } from './classify.mjs';
import { redactSensitive } from '../observability/redactor.mjs';

/**
 * @typedef {object} HostStreamLike
 * @property {(eventType: string, payload: unknown) => unknown} broadcast
 */

/**
 * @typedef {object} WorldLifecycleEvent
 * @property {string}                   worldId
 * @property {import('./phases.mjs').WorldLifecyclePhase} phase
 * @property {number}                   at
 * @property {import('./evidence.mjs').WorldStartupEvidence} [evidence]
 * @property {import('./failure-kinds.mjs').WorldStartupFailureKind} [failureKind]
 */

/**
 * Emit a world lifecycle transition on both `world.lifecycle` and `span`
 * host-stream channels.
 *
 * @param {HostStreamLike} hostStream
 * @param {object}         args
 * @param {string}         args.worldId
 * @param {import('./phases.mjs').WorldLifecyclePhase} args.phase
 * @param {number}         [args.at]
 * @param {import('./evidence.mjs').WorldStartupEvidence} [args.evidence]
 * @param {import('./failure-kinds.mjs').WorldStartupFailureKind} [args.failureKind]
 * @returns {WorldLifecycleEvent} the payload that was broadcast (test convenience)
 */
export function recordWorldLifecycle(hostStream, args) {
  if (!hostStream || typeof hostStream.broadcast !== 'function') {
    throw new TypeError('recordWorldLifecycle: hostStream.broadcast is required');
  }
  if (typeof args?.worldId !== 'string' || args.worldId.length === 0) {
    throw new TypeError('recordWorldLifecycle: worldId is required');
  }
  if (typeof args?.phase !== 'string') {
    throw new TypeError('recordWorldLifecycle: phase is required');
  }

  const at = typeof args.at === 'number' ? args.at : Date.now();

  // Resolve failureKind: explicit override > classifier inference > undefined.
  let failureKind = args.failureKind;
  if (
    failureKind === undefined &&
    args.phase === WorldLifecyclePhase.Failed &&
    args.evidence !== undefined
  ) {
    failureKind = classifyStartupFailure(args.evidence);
  }

  /** @type {WorldLifecycleEvent} */
  const livePayload = {
    worldId: args.worldId,
    phase: args.phase,
    at,
  };
  if (args.evidence !== undefined) livePayload.evidence = redactSensitive(args.evidence);
  if (failureKind !== undefined) livePayload.failureKind = failureKind;

  hostStream.broadcast('world.lifecycle', livePayload);

  // Mirror as a span so the NDJSON trace sink (PR #915) records it.
  // Lifecycle transitions are point-in-time events — startedAt === endedAt.
  /** @type {Record<string, unknown>} */
  const spanAttributes = {
    worldId: args.worldId,
    phase: args.phase,
  };
  if (args.evidence !== undefined) spanAttributes.evidence = redactSensitive(args.evidence);
  if (failureKind !== undefined) spanAttributes.failureKind = failureKind;

  /** @type {{ _tag: 'Success' | 'Failure', reason?: string }} */
  const exit =
    args.phase === WorldLifecyclePhase.Failed
      ? { _tag: 'Failure', reason: failureKind ?? 'unclassified' }
      : { _tag: 'Success' };

  hostStream.broadcast('span', {
    name: 'world.lifecycle',
    startedAt: at,
    endedAt: at,
    attributes: spanAttributes,
    exit,
  });

  return livePayload;
}

/** Re-export so callers don't need to import both modules. */
export { WorldLifecyclePhase, TERMINAL_PHASES };
