'use strict';

const {
  Confidence,
  EventStatus,
  Provenance,
  createLaneEvent,
  eventFromTranscriptPart,
  isTerminalEvent,
} = require('./lane-events');

const CONFIDENCE_RANK = Object.freeze({
  [Confidence.LOW]: 1,
  [Confidence.MEDIUM]: 2,
  [Confidence.HIGH]: 3,
  [Confidence.CERTAIN]: 4,
});

function reconcileEvents(input = [], opts = {}) {
  const events = normalizeEvents(input);
  events.sort(compareEvents);

  const seenTerminal = new Set();
  const applied = [];
  const ignored = [];
  const failures = [];
  const blocked = [];
  let state = createInitialState(opts);

  for (const event of events) {
    if (isTerminalEvent(event) && event.terminalFingerprint) {
      if (seenTerminal.has(event.terminalFingerprint)) {
        ignored.push({ event, reason: 'duplicate-terminal' });
        continue;
      }
      seenTerminal.add(event.terminalFingerprint);
    }
    if (!shouldApply(state, event)) {
      ignored.push({ event, reason: 'older-or-weaker-than-terminal' });
      continue;
    }
    state = applyEvent(state, event);
    applied.push(event);
    if (event.status === EventStatus.FAILED || event.failureClass) failures.push(event);
    if (event.status === EventStatus.BLOCKED) blocked.push(event);
  }

  return {
    ...state,
    events: applied,
    ignored,
    failures,
    blocked,
    isTerminal: state.status === EventStatus.FINISHED || state.status === EventStatus.FAILED,
    running: state.status === EventStatus.RUNNING || state.status === EventStatus.SPAWNING,
    waiting: state.status === EventStatus.WAITING || state.status === EventStatus.READY,
    idle: state.status === EventStatus.IDLE,
  };
}

function normalizeEvents(input) {
  const list = Array.isArray(input) ? input : [];
  const out = [];
  for (const item of list) {
    if (!item) continue;
    const fromPart = item.partType === 'lane_event' ? eventFromTranscriptPart(item) : null;
    out.push(fromPart || createLaneEvent(item));
  }
  return out;
}

function createInitialState(opts = {}) {
  return {
    status: opts.initialStatus || EventStatus.IDLE,
    liveness: opts.initialStatus || EventStatus.IDLE,
    sessionId: opts.sessionId || '',
    agentSessionId: '',
    runId: '',
    provider: '',
    model: '',
    cwd: '',
    reason: '',
    confidence: Confidence.LOW,
    lastEvent: null,
    terminalEvent: null,
    updatedAt: null,
  };
}

function shouldApply(state, event) {
  if (!state.terminalEvent) return true;
  if (event.status === EventStatus.RECONCILED) return true;
  if (!isTerminalEvent(event)) {
    return event.provenance === Provenance.LIVE
      && compareEvents(event, state.terminalEvent) > 0
      && confidenceRank(event) > confidenceRank(state.terminalEvent);
  }
  if (event.provenance === Provenance.TRANSPORT && state.terminalEvent.provenance !== Provenance.TRANSPORT) {
    return false;
  }
  return compareEvents(event, state.terminalEvent) >= 0;
}

function applyEvent(state, event) {
  const next = {
    ...state,
    status: event.status === EventStatus.RECONCILED ? state.status : event.status,
    liveness: event.status === EventStatus.RECONCILED ? state.liveness : event.status,
    sessionId: event.sessionId || state.sessionId,
    agentSessionId: event.agentSessionId || state.agentSessionId,
    runId: event.runId || state.runId,
    provider: event.provider || state.provider,
    model: event.model || state.model,
    cwd: event.cwd || state.cwd,
    reason: event.reason || state.reason,
    confidence: event.confidence || state.confidence,
    lastEvent: event,
    updatedAt: event.timestamp,
  };
  if (isTerminalEvent(event)) {
    next.terminalEvent = event;
  }
  return next;
}

function compareEvents(a, b) {
  const aSeq = Number.isFinite(a?.seq) ? a.seq : null;
  const bSeq = Number.isFinite(b?.seq) ? b.seq : null;
  if (aSeq !== null && bSeq !== null && aSeq !== bSeq) return aSeq - bSeq;
  if (aSeq !== null && bSeq === null) return 1;
  if (aSeq === null && bSeq !== null) return -1;
  const aTime = Date.parse(a?.timestamp || 0) || 0;
  const bTime = Date.parse(b?.timestamp || 0) || 0;
  if (aTime !== bTime) return aTime - bTime;
  return String(a?.id || '').localeCompare(String(b?.id || ''));
}

function confidenceRank(event) {
  return CONFIDENCE_RANK[event?.confidence] || 0;
}

module.exports = {
  reconcileEvents,
  compareEvents,
};
