'use strict';

const { StreamProcessor } = require('./stream-processor');
const { applyProviderCompatibility } = require('./provider-compat');

class CodingParityHarness {
  constructor({ now = fixedClock() } = {}) {
    this.now = now;
  }

  async runScenario(scenario = {}) {
    if (!scenario.id) throw new Error('scenario.id is required');
    const records = [];
    const provider = createScriptedProvider(scenario);
    const transcript = {
      appendPart: (record) => records.push(record),
      appendLaneEvent: (event) => records.push({
        type: 'walle_part',
        sessionId: event.sessionId,
        cwd: event.cwd,
        partType: 'lane_event',
        timestamp: event.timestamp,
        data: event,
      }),
    };
    const processor = new StreamProcessor({
      provider,
      model: scenario.model || 'mock-coding',
      transcript,
      permissionService: scenario.permissionService || permissionServiceForScenario(scenario),
      toolExecutor: scenario.toolExecutor || toolExecutorForScenario(scenario),
      now: this.now,
    });

    const result = await processor.runTurn({
      sessionId: scenario.sessionId || scenario.id,
      cwd: scenario.cwd || '/repo',
      messages: scenario.messages || [{ role: 'user', content: scenario.prompt || scenario.id }],
      tools: scenario.tools || [],
      model: scenario.model || 'mock-coding',
      provider,
    });

    return {
      id: scenario.id,
      result,
      records,
      laneEvents: records.filter((record) => record.partType === 'lane_event').map((record) => record.data),
      toolParts: records.filter((record) => record.partType === 'tool'),
      text: result.text,
    };
  }

  preflightProvider(request = {}, opts = {}) {
    return applyProviderCompatibility(request, opts);
  }
}

function createScriptedProvider(scenario = {}) {
  const events = scenario.events || [{ type: 'text_delta', data: { text: scenario.answer || 'ok' } }, finishEvent(scenario)];
  return {
    type: scenario.provider || 'mock',
    async *chatStream() {
      for (const event of events) yield event;
    },
  };
}

function toolExecutorForScenario(scenario = {}) {
  const byName = scenario.toolResults || {};
  return async (call) => {
    if (typeof byName[call.name] === 'function') return byName[call.name](call);
    if (Object.prototype.hasOwnProperty.call(byName, call.name)) return byName[call.name];
    return { ok: true, content: `${call.name}(${JSON.stringify(call.input || {})})` };
  };
}

function permissionServiceForScenario(scenario = {}) {
  if (!scenario.permissions) return null;
  return {
    authorize: async ({ tool }) => {
      const decision = scenario.permissions[tool] || scenario.permissions.default || 'allow';
      if (decision === 'allow') return { decision: 'allow' };
      return { decision: 'deny', reason: `${tool} denied by parity scenario` };
    },
  };
}

function createDefaultParityScenarios() {
  return [
    {
      id: 'streaming_text',
      prompt: 'answer',
      events: [
        { type: 'text_delta', data: { text: 'hello ' } },
        { type: 'text_delta', data: { text: 'world' } },
        finishEvent({ stopReason: 'end_turn' }),
      ],
    },
    {
      id: 'read_file_roundtrip',
      prompt: 'read file',
      tools: [{ name: 'read_file', input_schema: { type: 'object' } }],
      events: [
        { type: 'tool_call', data: { id: 'tc-read', name: 'read_file', input: { file_path: 'README.md' } } },
        finishEvent({ stopReason: 'tool_use' }),
      ],
      toolResults: { read_file: { content: 'readme text' } },
    },
    {
      id: 'shell_permission_denied',
      prompt: 'run shell',
      tools: [{ name: 'run_shell', input_schema: { type: 'object' } }],
      events: [
        { type: 'tool_call', data: { id: 'tc-shell', name: 'run_shell', input: { command: 'npm test' } } },
        finishEvent({ stopReason: 'tool_use' }),
      ],
      permissions: { run_shell: 'deny' },
    },
  ];
}

function finishEvent({ stopReason = 'end_turn', usage = { input: 1, output: 1 }, model = 'mock-coding', provider = 'mock' } = {}) {
  return { type: 'finish', data: { stopReason, usage, model, provider } };
}

function fixedClock() {
  let n = 0;
  return () => `2026-05-08T12:00:${String(n++).padStart(2, '0')}.000Z`;
}

module.exports = {
  CodingParityHarness,
  createDefaultParityScenarios,
  createScriptedProvider,
  finishEvent,
};
