/**
 * Claude Code Stream Parser
 *
 * Parses Claude Code CLI `--output-format stream-json` NDJSON output
 * into standardized StreamEvents.
 *
 * Key line types handled:
 * - system (subtype=init): session_id
 * - stream_event (content_block_delta, text_delta): delta.text
 * - stream_event (content_block_start, tool_use): content_block.name, .id
 * - stream_event (message_delta): delta.stop_reason
 * - result: result, session_id, total_cost_usd
 * - any with is_error: true: error message
 */

import type { StreamEvent, StreamParser } from '../stream-types.js';

interface ClaudeSystemEvent {
  type: 'system';
  subtype?: string;
  session_id?: string;
}

interface ClaudeStreamEvent {
  type: 'stream_event';
  event?: {
    type: string;
    index?: number;
    delta?: {
      type?: string;
      text?: string;
      stop_reason?: string;
    };
    content_block?: {
      type?: string;
      id?: string;
      name?: string;
    };
  };
}

interface ClaudeAssistantEvent {
  type: 'assistant';
  message?: {
    content?: Array<{
      type: string;
      text?: string;
      id?: string;
      name?: string;
    }>;
  };
}

interface ClaudeResultEvent {
  type: 'result';
  result?: string;
  session_id?: string;
  total_cost_usd?: number;
  is_error?: boolean;
}

type ClaudeEvent = ClaudeSystemEvent | ClaudeStreamEvent | ClaudeAssistantEvent | ClaudeResultEvent | { type: string; is_error?: boolean; result?: string };

export class ClaudeStreamParser implements StreamParser {
  readonly provider = 'claude';

  parseLine(line: string): StreamEvent[] {
    const trimmed = line.trim();
    if (!trimmed) return [];

    let parsed: ClaudeEvent;
    try {
      parsed = JSON.parse(trimmed);
    } catch {
      // Not valid JSON, ignore
      return [];
    }

    // Check for error on non-result events (e.g. stream errors)
    // Result events with is_error are handled in the 'result' case below
    if ('is_error' in parsed && parsed.is_error && 'result' in parsed && parsed.type !== 'result') {
      return [{ type: 'error', message: String(parsed.result) }];
    }

    const events: StreamEvent[] = [];

    switch (parsed.type) {
      case 'system': {
        const sysEvent = parsed as ClaudeSystemEvent;
        if (sysEvent.subtype === 'init' && sysEvent.session_id) {
          events.push({ type: 'init', sessionId: sysEvent.session_id });
        }
        break;
      }

      case 'stream_event': {
        const streamEvent = parsed as ClaudeStreamEvent;
        const inner = streamEvent.event;
        if (!inner) break;

        switch (inner.type) {
          case 'content_block_delta': {
            if (inner.delta?.type === 'text_delta' && inner.delta.text) {
              events.push({ type: 'text_delta', text: inner.delta.text });
            }
            break;
          }

          case 'content_block_start': {
            if (inner.content_block?.type === 'tool_use') {
              const name = inner.content_block.name || 'unknown';
              const id = inner.content_block.id || '';
              events.push({ type: 'tool_use_start', name, id });
            }
            break;
          }

          case 'message_delta': {
            if (inner.delta?.stop_reason) {
              events.push({ type: 'turn_end', stopReason: inner.delta.stop_reason });
            }
            break;
          }
        }
        break;
      }

      case 'assistant': {
        // Claude CLI stream-json now emits complete assistant messages
        // instead of granular stream_event deltas
        const assistantEvent = parsed as ClaudeAssistantEvent;
        const content = assistantEvent.message?.content;
        if (Array.isArray(content)) {
          for (const block of content) {
            if (block.type === 'text' && block.text) {
              events.push({ type: 'text_delta', text: block.text });
            } else if (block.type === 'tool_use' && block.name) {
              events.push({ type: 'tool_use_start', name: block.name, id: block.id || '' });
            }
          }
        }
        break;
      }

      case 'result': {
        const resultEvent = parsed as ClaudeResultEvent;
        events.push({
          type: 'result',
          text: resultEvent.result || '',
          sessionId: resultEvent.session_id,
          costUsd: resultEvent.total_cost_usd,
          isError: resultEvent.is_error === true,
        });
        break;
      }

      // Ignore: message_start, content_block_stop, message_stop, user
    }

    return events;
  }

  end(): StreamEvent[] {
    // Claude emits a result event, so nothing needed at end
    return [];
  }
}
