'use strict';

const Confidence = Object.freeze({
  HIGH: 'high',
});

const EventName = Object.freeze({
  PROMPT_MISDELIVERED: 'prompt.misdelivered',
  SESSION_FAILED: 'session.failed',
  SESSION_FINISHED: 'session.finished',
  SESSION_READY: 'session.ready',
  SESSION_RUNNING: 'session.running',
  SESSION_WAITING: 'session.waiting',
});

const FailureClass = Object.freeze({
  INFRA: 'infra',
  PROMPT_DELIVERY: 'prompt_delivery',
  PROVIDER: 'provider',
  TOOL_PERMISSION: 'tool_permission',
  TRUST_GATE: 'trust_gate',
});

const Provenance = Object.freeze({
  LIVE: 'live',
});

const WorkerStatus = Object.freeze({
  SPAWNING: 'spawning',
  TRUST_REQUIRED: 'trust_required',
  TOOL_PERMISSION_REQUIRED: 'tool_permission_required',
  READY_FOR_PROMPT: 'ready_for_prompt',
  PROMPT_IN_FLIGHT: 'prompt_in_flight',
  RUNNING: 'running',
  FINISHED: 'finished',
  FAILED: 'failed',
});

const WorkerEvent = Object.freeze({
  SPAWNING: 'worker.spawning',
  TRUST_REQUIRED: 'worker.trust_required',
  TOOL_PERMISSION_REQUIRED: 'worker.tool_permission_required',
  TRUST_RESOLVED: 'worker.trust_resolved',
  READY_FOR_PROMPT: 'worker.ready_for_prompt',
  PROMPT_SENT: 'worker.prompt_sent',
  PROMPT_ACCEPTED: 'worker.prompt_accepted',
  PROMPT_MISDELIVERED: 'worker.prompt_misdelivered',
  RUNNING: 'worker.running',
  FINISHED: 'worker.finished',
  FAILED: 'worker.failed',
  STARTUP_NO_EVIDENCE: 'worker.startup_no_evidence',
});

const StartupFailure = Object.freeze({
  TRUST_GATE: 'trust_gate',
  TOOL_PERMISSION_GATE: 'tool_permission_gate',
  PROMPT_DELIVERY: 'prompt_delivery',
  PROTOCOL: 'protocol',
  PROVIDER: 'provider',
  STARTUP_NO_EVIDENCE: 'startup_no_evidence',
});

class WorkerBootTracker {
  constructor({
    sessionId = '',
    runnerId = '',
    providerType = '',
    model = '',
    cwd = '',
    clock = () => new Date().toISOString(),
  } = {}) {
    this.sessionId = sessionId;
    this.runnerId = runnerId;
    this.providerType = providerType;
    this.model = model;
    this.cwd = cwd;
    this.clock = clock;
    this.status = WorkerStatus.SPAWNING;
    this.failureKind = '';
    this.reason = '';
    this.events = [];
    this.evidence = {
      spawned: false,
      trustPrompt: false,
      toolPermissionPrompt: false,
      readyForPrompt: false,
      promptSent: false,
      promptAccepted: false,
      promptMisdelivered: false,
      transportHealthy: false,
      mcpHealthy: null,
    };
  }

  observe(input = {}) {
    const type = String(input.type || input.event || '');
    switch (type) {
      case WorkerEvent.SPAWNING:
        return this._record(type, WorkerStatus.SPAWNING, input, { spawned: true });
      case WorkerEvent.TRUST_REQUIRED:
        return this._record(type, WorkerStatus.TRUST_REQUIRED, input, { trustPrompt: true });
      case WorkerEvent.TOOL_PERMISSION_REQUIRED:
        return this._record(type, WorkerStatus.TOOL_PERMISSION_REQUIRED, input, { toolPermissionPrompt: true });
      case WorkerEvent.TRUST_RESOLVED:
      case WorkerEvent.READY_FOR_PROMPT:
        return this._record(type, WorkerStatus.READY_FOR_PROMPT, input, { readyForPrompt: true, transportHealthy: true });
      case WorkerEvent.PROMPT_SENT:
        return this._record(type, WorkerStatus.PROMPT_IN_FLIGHT, input, { promptSent: true });
      case WorkerEvent.PROMPT_ACCEPTED:
        return this._record(type, WorkerStatus.RUNNING, input, { promptAccepted: true, transportHealthy: true });
      case WorkerEvent.PROMPT_MISDELIVERED:
        this.failureKind = StartupFailure.PROMPT_DELIVERY;
        return this._record(type, WorkerStatus.FAILED, input, { promptMisdelivered: true });
      case WorkerEvent.RUNNING:
        return this._record(type, WorkerStatus.RUNNING, input, { transportHealthy: true });
      case WorkerEvent.FINISHED:
        return this._record(type, WorkerStatus.FINISHED, input, {});
      case WorkerEvent.STARTUP_NO_EVIDENCE:
        this.failureKind = StartupFailure.STARTUP_NO_EVIDENCE;
        return this._record(type, WorkerStatus.FAILED, input, {});
      case WorkerEvent.FAILED:
      default:
        if (type === WorkerEvent.FAILED || input.error || input.reason) {
          this.failureKind = input.failureKind || classifyFailure(input);
          return this._record(type || WorkerEvent.FAILED, WorkerStatus.FAILED, input, {});
        }
        return null;
    }
  }

  toLaneEvent(input = {}) {
    const name = laneNameForWorkerStatus(this.status, this.failureKind);
    return {
      kind: 'walle_lane_event',
      version: 1,
      name,
      sessionId: this.sessionId,
      provider: this.providerType,
      model: this.model,
      cwd: this.cwd,
      provenance: Provenance.LIVE,
      confidence: Confidence.HIGH,
      timestamp: input.timestamp || this.clock(),
      failureClass: failureClassForStartup(this.failureKind),
      reason: this.reason,
      data: {
        runnerId: this.runnerId,
        workerStatus: this.status,
        failureKind: this.failureKind,
        evidence: this.evidence,
        ...(input.data || {}),
      },
      ...input,
    };
  }

  snapshot() {
    return {
      sessionId: this.sessionId,
      runnerId: this.runnerId,
      providerType: this.providerType,
      model: this.model,
      cwd: this.cwd,
      status: this.status,
      failureKind: this.failureKind,
      reason: this.reason,
      evidence: { ...this.evidence },
      events: this.events.map((event) => ({ ...event })),
      ready: this.status === WorkerStatus.READY_FOR_PROMPT || this.status === WorkerStatus.RUNNING || this.status === WorkerStatus.FINISHED,
      terminal: this.status === WorkerStatus.FINISHED || this.status === WorkerStatus.FAILED,
    };
  }

  _record(type, status, input, evidencePatch) {
    this.status = status;
    this.reason = String(input.reason || input.message || input.error || this.reason || '');
    Object.assign(this.evidence, evidencePatch || {});
    const event = {
      type,
      status,
      timestamp: input.timestamp || this.clock(),
      reason: this.reason,
      failureKind: this.failureKind,
      data: input.data || {},
    };
    this.events.push(event);
    return event;
  }
}

function classifyFailure(input = {}) {
  const text = String(input.error || input.reason || input.message || '').toLowerCase();
  if (/trust|untrusted/.test(text)) return StartupFailure.TRUST_GATE;
  if (/permission|approval/.test(text)) return StartupFailure.TOOL_PERMISSION_GATE;
  if (/prompt|misdeliver/.test(text)) return StartupFailure.PROMPT_DELIVERY;
  if (/provider|model|rate|quota|api/.test(text)) return StartupFailure.PROVIDER;
  return StartupFailure.PROTOCOL;
}

function laneNameForWorkerStatus(status, failureKind = '') {
  if (status === WorkerStatus.READY_FOR_PROMPT) return EventName.SESSION_READY;
  if (status === WorkerStatus.PROMPT_IN_FLIGHT || status === WorkerStatus.RUNNING || status === WorkerStatus.SPAWNING) {
    return EventName.SESSION_RUNNING;
  }
  if (status === WorkerStatus.FINISHED) return EventName.SESSION_FINISHED;
  if (status === WorkerStatus.FAILED && failureKind === StartupFailure.PROMPT_DELIVERY) return EventName.PROMPT_MISDELIVERED;
  if (status === WorkerStatus.FAILED) return EventName.SESSION_FAILED;
  return EventName.SESSION_WAITING;
}

function failureClassForStartup(kind) {
  switch (kind) {
    case StartupFailure.TRUST_GATE:
      return FailureClass.TRUST_GATE;
    case StartupFailure.TOOL_PERMISSION_GATE:
      return FailureClass.TOOL_PERMISSION;
    case StartupFailure.PROMPT_DELIVERY:
      return FailureClass.PROMPT_DELIVERY;
    case StartupFailure.PROVIDER:
      return FailureClass.PROVIDER;
    case StartupFailure.STARTUP_NO_EVIDENCE:
      return FailureClass.INFRA;
    default:
      return '';
  }
}

module.exports = {
  StartupFailure,
  WorkerBootTracker,
  WorkerEvent,
  WorkerStatus,
  classifyFailure,
  failureClassForStartup,
  laneNameForWorkerStatus,
};
