import {
  SUBAGENT_WORKER_PROTOCOL_VERSION,
  SubagentParentMessage,
  SubagentTaskEnvelope,
  SubagentWorkerEvent,
} from './process_protocol.js';
import { runAgent } from '../runtime/agent.js';
import { initDefaultProviders } from '../runtime/model_selection.js';
import { resolveWorkerTools } from './worker_tools.js';
import { buildToolManifestItem } from '../tools/manifest.js';

const now = () => new Date().toISOString();
const MAX_CAPTURED_TOOL_RESULTS = 6;
const MAX_CAPTURED_TOOL_RESULT_CHARS = 1200;

const emit = (event: SubagentWorkerEvent) => {
  process.stdout.write(`${JSON.stringify(event)}\n`);
};

const parentMessages = new Map<string, {
  resolve: (value: string) => void;
  reject: (error: Error) => void;
}>();

let stdinBuffer = '';
let envelopeResolver: ((value: SubagentTaskEnvelope) => void) | null = null;
let envelopeRejecter: ((error: Error) => void) | null = null;
let envelopeLoaded = false;

const readEnvelope = async (): Promise<SubagentTaskEnvelope> => new Promise((resolve, reject) => {
  envelopeResolver = resolve;
  envelopeRejecter = reject;
  process.stdin.setEncoding('utf8');
  process.stdin.on('data', chunk => {
    stdinBuffer += chunk;
    drainStdinLines();
  });
  process.stdin.on('error', reject);
  process.stdin.on('end', () => {
    if (!envelopeLoaded) {
      reject(new Error('Subagent worker stdin ended before envelope was received'));
    }
  });
});

const drainStdinLines = () => {
  const lines = stdinBuffer.split(/\r?\n/);
  stdinBuffer = lines.pop() ?? '';
  for (const line of lines) {
    const trimmed = line.trim();
    if (!trimmed) {
      continue;
    }

    try {
      const message = JSON.parse(trimmed);
      if (!envelopeLoaded) {
        envelopeLoaded = true;
        envelopeResolver?.(message as SubagentTaskEnvelope);
      } else {
        handleParentMessage(message as SubagentParentMessage);
      }
    } catch (error) {
      const parsedError = new Error(`Failed to parse subagent worker stdin JSONL: ${toErrorMessage(error)}`);
      if (!envelopeLoaded) {
        envelopeRejecter?.(parsedError);
      } else {
        for (const pending of parentMessages.values()) {
          pending.reject(parsedError);
        }
        parentMessages.clear();
      }
    }
  }
};

const handleParentMessage = (message: SubagentParentMessage) => {
  const pending = parentMessages.get(message.requestId);
  if (!pending) {
    return;
  }
  parentMessages.delete(message.requestId);

  if (message.type === 'proxy_tool_result') {
    pending.resolve(message.result);
  } else {
    pending.reject(new Error(message.error));
  }
};

const executeProxyTool = (
  envelope: SubagentTaskEnvelope,
  name: string,
  toolCallId: string,
  params: Record<string, unknown>
): Promise<string> => {
  const requestId = `${toolCallId || name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  emit({
    type: 'proxy_tool_call',
    runId: envelope.runId,
    timestamp: now(),
    requestId,
    name,
    toolCallId,
    args: params,
  });

  return new Promise((resolve, reject) => {
    parentMessages.set(requestId, { resolve, reject });
  });
};

const fail = (runId: string, error: string, exitCode = 1) => {
  emit({
    type: 'run_error',
    runId,
    timestamp: now(),
    error,
  });
  process.exitCode = exitCode;
};

const main = async () => {
  let envelope: SubagentTaskEnvelope;
  try {
    envelope = await readEnvelope();
  } catch (error) {
    fail('unknown', `Failed to parse subagent worker envelope: ${toErrorMessage(error)}`);
    return;
  }

  emit({
    type: 'run_start',
    runId: envelope.runId,
    timestamp: now(),
    envelope: {
      sessionId: envelope.sessionId,
      parentMessageId: envelope.parentMessageId,
      parentToolCallId: envelope.parentToolCallId,
      parentAgentId: envelope.parentAgentId,
      parentEntryId: envelope.parentEntryId,
    },
  });

  if (envelope.protocolVersion !== SUBAGENT_WORKER_PROTOCOL_VERSION) {
    fail(
      envelope.runId,
      `Unsupported subagent worker protocol version ${envelope.protocolVersion}`
    );
    return;
  }

  const manifest = envelope.toolManifest ?? buildLegacyManifest(envelope.toolNames ?? []);
  const resolvedTools = resolveWorkerTools(
    manifest,
    envelope.policy,
    {
      proxyToolExecutor: (manifest, toolCallId, params) =>
        executeProxyTool(envelope, manifest.name, toolCallId, params),
    }
  );
  if (resolvedTools.unsupportedToolNames.length > 0) {
    fail(
      envelope.runId,
      `Subagent subprocess worker does not support tools: ${resolvedTools.unsupportedToolNames.join(', ')}`
    );
    return;
  }
  if (resolvedTools.pendingProxyToolNames.length > 0) {
    emit({
      type: 'log',
      runId: envelope.runId,
      timestamp: now(),
      text: `Subagent subprocess worker will proxy tools through the main process: ${resolvedTools.pendingProxyToolNames.join(', ')}`,
    });
  }

  try {
    initDefaultProviders();
    const capturedToolResults: Array<{
      name: string;
      label?: string;
      result: string;
      toolCallId?: string;
    }> = [];
    const result = await runAgent({
      agentConfig: envelope.agentConfig,
      tools: resolvedTools.tools,
      toolNames: manifest.map(tool => tool.name),
      userMessage: envelope.task.goal,
      history: [],
      onToken: text => emit({
        type: 'token',
        runId: envelope.runId,
        timestamp: now(),
        text,
      }),
      onThinking: text => emit({
        type: 'thinking',
        runId: envelope.runId,
        timestamp: now(),
        text,
      }),
      beforeToolCall: async ({ name, label, args, toolCallId }) => emit({
        type: 'tool_call',
        runId: envelope.runId,
        timestamp: now(),
        name,
        label,
        args,
        toolCallId,
      }),
      onToolCall: () => {},
      afterToolCall: async ({ name, label, result: resultText, toolCallId }) => {
        if (capturedToolResults.length < MAX_CAPTURED_TOOL_RESULTS) {
          capturedToolResults.push({
            name,
            label,
            result: truncateToolResult(resultText),
            toolCallId,
          });
        }
        emit({
          type: 'tool_result',
          runId: envelope.runId,
          timestamp: now(),
          name,
          label,
          result: resultText,
          toolCallId,
        });
      },
      onToolResult: () => {},
    });

    emit({
      type: 'run_end',
      runId: envelope.runId,
      timestamp: now(),
      result: {
        finalContent: result.finalContent,
        finalOutput: extractFinalOutput(result.messages),
        toolResults: capturedToolResults,
        usage: result.usage,
        toolCallCount: result.toolCallCount,
        metadata: {
          messageCount: result.messages.length,
          proxiedToolNames: resolvedTools.pendingProxyToolNames,
        },
      },
    });
  } catch (error) {
    fail(envelope.runId, toErrorMessage(error));
  }
};

const buildLegacyManifest = (toolNames: string[]) => toolNames.map(name => buildToolManifestItem(name));

const truncateToolResult = (value: string): string => {
  const text = typeof value === 'string' ? value : String(value ?? '');
  if (text.length <= MAX_CAPTURED_TOOL_RESULT_CHARS) {
    return text;
  }
  return `${text.slice(0, MAX_CAPTURED_TOOL_RESULT_CHARS)}\n...[truncated]`;
};

const extractFinalOutput = (messages: Array<{ role: string; content?: unknown }>): string => {
  for (let index = messages.length - 1; index >= 0; index -= 1) {
    const message = messages[index];
    if (message?.role !== 'assistant') {
      continue;
    }

    if (typeof message.content === 'string') {
      const text = message.content.trim();
      if (text) {
        return text;
      }
      continue;
    }

    if (Array.isArray(message.content)) {
      for (const block of message.content) {
        if (!block || typeof block !== 'object') {
          continue;
        }
        const text = typeof (block as { text?: unknown }).text === 'string'
          ? (block as { text: string }).text.trim()
          : '';
        if (text) {
          return text;
        }
      }
    }
  }

  return '';
};

const toErrorMessage = (error: unknown): string => {
  if (error instanceof Error) {
    return error.message;
  }
  if (typeof error === 'string') {
    return error;
  }
  return 'Unknown error';
};

main().catch(error => {
  fail('unknown', `Unhandled subagent worker failure: ${toErrorMessage(error)}`);
});
