'use strict';

const crypto = require('node:crypto');

const VERSION = 1;

const EventName = Object.freeze({
  SESSION_STARTED: 'session.started',
  SESSION_READY: 'session.ready',
  SESSION_RUNNING: 'session.running',
  SESSION_WAITING: 'session.waiting',
  SESSION_IDLE: 'session.idle',
  SESSION_BLOCKED: 'session.blocked',
  SESSION_FINISHED: 'session.finished',
  SESSION_FAILED: 'session.failed',
  SESSION_RECONCILED: 'session.reconciled',
  PROMPT_SENT: 'prompt.sent',
  PROMPT_ACCEPTED: 'prompt.accepted',
  PROMPT_MISDELIVERED: 'prompt.misdelivered',
  TOOL_STARTED: 'tool.started',
  TOOL_FINISHED: 'tool.finished',
  TOOL_FAILED: 'tool.failed',
  MCP_DEGRADED: 'mcp.degraded',
  PROVIDER_INCOMPATIBLE: 'provider.incompatible',
});

const EventStatus = Object.freeze({
  SPAWNING: 'spawning',
  READY: 'ready',
  RUNNING: 'running',
  WAITING: 'waiting',
  IDLE: 'idle',
  BLOCKED: 'blocked',
  FINISHED: 'finished',
  FAILED: 'failed',
  RECONCILED: 'reconciled',
});

const Provenance = Object.freeze({
  LIVE: 'live',
  REPLAY: 'replay',
  HEALTHCHECK: 'healthcheck',
  TEST: 'test',
  TRANSPORT: 'transport',
});

const FailureClass = Object.freeze({
  PROMPT_DELIVERY: 'prompt_delivery',
  TRUST_GATE: 'trust_gate',
  TOOL_PERMISSION: 'tool_permission',
  PROVIDER: 'provider',
  MCP_STARTUP: 'mcp_startup',
  TOOL_RUNTIME: 'tool_runtime',
  WORKSPACE_MISMATCH: 'workspace_mismatch',
  BRANCH_DIVERGENCE: 'branch_divergence',
  INFRA: 'infra',
});

const Confidence = Object.freeze({
  LOW: 'low',
  MEDIUM: 'medium',
  HIGH: 'high',
  CERTAIN: 'certain',
});

const TERMINAL_STATUSES = new Set([
  EventStatus.FINISHED,
  EventStatus.FAILED,
]);

const NAME_TO_STATUS = Object.freeze({
  [EventName.SESSION_STARTED]: EventStatus.SPAWNING,
  [EventName.SESSION_READY]: EventStatus.READY,
  [EventName.SESSION_RUNNING]: EventStatus.RUNNING,
  [EventName.SESSION_WAITING]: EventStatus.WAITING,
  [EventName.SESSION_IDLE]: EventStatus.IDLE,
  [EventName.SESSION_BLOCKED]: EventStatus.BLOCKED,
  [EventName.SESSION_FINISHED]: EventStatus.FINISHED,
  [EventName.SESSION_FAILED]: EventStatus.FAILED,
  [EventName.SESSION_RECONCILED]: EventStatus.RECONCILED,
  [EventName.PROMPT_SENT]: EventStatus.RUNNING,
  [EventName.PROMPT_ACCEPTED]: EventStatus.RUNNING,
  [EventName.PROMPT_MISDELIVERED]: EventStatus.BLOCKED,
  [EventName.TOOL_STARTED]: EventStatus.RUNNING,
  [EventName.TOOL_FINISHED]: EventStatus.RUNNING,
  [EventName.TOOL_FAILED]: EventStatus.BLOCKED,
  [EventName.MCP_DEGRADED]: EventStatus.BLOCKED,
  [EventName.PROVIDER_INCOMPATIBLE]: EventStatus.BLOCKED,
});

function createLaneEvent(input = {}) {
  const name = String(input.name || input.event || EventName.SESSION_RECONCILED);
  const status = normalizeStatus(input.status || NAME_TO_STATUS[name] || EventStatus.RUNNING);
  const timestamp = normalizeTimestamp(input.timestamp);
  const event = {
    version: VERSION,
    id: String(input.id || createId()),
    seq: numberOrNull(input.seq),
    name,
    status,
    sessionId: String(input.sessionId || input.session_id || ''),
    agentSessionId: String(input.agentSessionId || input.agent_session_id || ''),
    runId: String(input.runId || input.run_id || ''),
    provider: String(input.provider || ''),
    model: String(input.model || ''),
    cwd: String(input.cwd || ''),
    provenance: normalizeEnum(input.provenance, Provenance, Provenance.LIVE),
    confidence: normalizeEnum(input.confidence, Confidence, Confidence.MEDIUM),
    failureClass: input.failureClass || input.failure_class ? String(input.failureClass || input.failure_class) : '',
    reason: String(input.reason || input.message || ''),
    terminalFingerprint: String(input.terminalFingerprint || input.terminal_fingerprint || ''),
    timestamp,
    data: safeJsonValue(input.data || {}),
  };
  if (!event.terminalFingerprint && isTerminalEvent(event)) {
    event.terminalFingerprint = terminalFingerprint(event);
  }
  return event;
}

function createLaneEventPart(input = {}) {
  const event = createLaneEvent(input);
  return {
    type: 'walle_part',
    provider: 'walle',
    sessionId: event.sessionId,
    cwd: event.cwd,
    timestamp: event.timestamp,
    partType: 'lane_event',
    data: event,
  };
}

function eventFromTranscriptPart(part = {}) {
  if (part.partType !== 'lane_event') return null;
  return createLaneEvent({
    ...(part.data || {}),
    sessionId: part.data?.sessionId || part.sessionId,
    cwd: part.data?.cwd || part.cwd,
    timestamp: part.data?.timestamp || part.timestamp,
  });
}

function statusFromEventName(name) {
  return NAME_TO_STATUS[String(name || '')] || EventStatus.RUNNING;
}

function isTerminalStatus(status) {
  return TERMINAL_STATUSES.has(normalizeStatus(status));
}

function isTerminalEvent(event = {}) {
  return isTerminalStatus(event.status || NAME_TO_STATUS[event.name]);
}

function terminalFingerprint(event = {}) {
  const payload = {
    sessionId: event.sessionId || '',
    agentSessionId: event.agentSessionId || '',
    runId: event.runId || '',
    name: event.name || '',
    status: event.status || '',
    failureClass: event.failureClass || '',
    reason: event.reason || '',
    stopReason: event.data?.stopReason || '',
    error: event.data?.error || '',
  };
  return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex').slice(0, 24);
}

function normalizeStatus(status) {
  const value = String(status || '').toLowerCase();
  return Object.values(EventStatus).includes(value) ? value : EventStatus.RUNNING;
}

function normalizeEnum(value, table, fallback) {
  const text = String(value || '').toLowerCase();
  return Object.values(table).includes(text) ? text : fallback;
}

function normalizeTimestamp(value) {
  if (value instanceof Date) return value.toISOString();
  if (typeof value === 'string' && value.trim()) {
    const parsed = new Date(value);
    if (!Number.isNaN(parsed.getTime())) return parsed.toISOString();
  }
  return new Date().toISOString();
}

function numberOrNull(value) {
  const n = Number(value);
  return Number.isFinite(n) ? n : null;
}

function safeJsonValue(value) {
  if (value === undefined) return {};
  try {
    return JSON.parse(JSON.stringify(value));
  } catch {
    return { value: String(value) };
  }
}

function createId() {
  if (crypto.randomUUID) return `lane-event-${crypto.randomUUID()}`;
  return `lane-event-${crypto.randomBytes(16).toString('hex')}`;
}

module.exports = {
  VERSION,
  Confidence,
  EventName,
  EventStatus,
  FailureClass,
  Provenance,
  createLaneEvent,
  createLaneEventPart,
  eventFromTranscriptPart,
  isTerminalEvent,
  isTerminalStatus,
  statusFromEventName,
  terminalFingerprint,
};
