/**
 * SSE Event Stream Parser — parses Server-Sent Events from the Go workflow engine
 *
 * The Go engine returns events in the format:
 *   data: {"run_id":"wf_xxx","seq":1,"ts":"...","type":"workflow_start","step_id":null,"payload":{}}
 *
 * This module provides:
 *   - parseSSEChunk: parse a raw text chunk into structured event objects
 *   - streamSSEEvents: async generator from a ReadableStream
 *   - streamSSEFromProcess: async generator from a child process stdout
 *   - mapEngineEvent: map Go engine event types to VL-Code callback names
 */

/**
 * Parse a raw SSE text chunk into an array of structured event objects.
 * Handles multiple "data: " lines in a single chunk.
 * @param {string} chunk - Raw SSE text (may contain multiple lines)
 * @returns {Array<Object>} Parsed event objects
 */
export function parseSSEChunk(chunk) {
  const events = [];
  const lines = chunk.split('\n');
  for (const line of lines) {
    const trimmed = line.trim();
    if (trimmed.startsWith('data: ')) {
      try {
        events.push(JSON.parse(trimmed.slice(6)));
      } catch {
        // Non-JSON data line — keep as raw
        events.push({ type: 'raw', raw: trimmed.slice(6) });
      }
    }
  }
  return events;
}

/**
 * Create an async generator of parsed SSE events from a fetch Response body.
 * @param {ReadableStream} readableStream - Response.body from fetch()
 * @yields {Object} Parsed SSE event objects
 */
export async function* streamSSEEvents(readableStream) {
  const reader = readableStream.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      buffer += decoder.decode(value, { stream: true });
      const lines = buffer.split('\n');
      // Keep the last (potentially incomplete) line in the buffer
      buffer = lines.pop() || '';

      for (const line of lines) {
        const trimmed = line.trim();
        if (trimmed.startsWith('data: ')) {
          try {
            yield JSON.parse(trimmed.slice(6));
          } catch {
            yield { type: 'raw', raw: trimmed.slice(6) };
          }
        }
      }
    }

    // Flush remaining buffer
    if (buffer.trim().startsWith('data: ')) {
      try {
        yield JSON.parse(buffer.trim().slice(6));
      } catch {
        yield { type: 'raw', raw: buffer.trim().slice(6) };
      }
    }
  } finally {
    reader.releaseLock();
  }
}

/**
 * Create an async generator of parsed SSE events from a child process stdout.
 * Used for CLI mode fallback.
 * @param {import('child_process').ChildProcess} proc - Spawned child process
 * @yields {Object} Parsed SSE event objects
 */
export async function* streamSSEFromProcess(proc) {
  let buffer = '';

  for await (const chunk of proc.stdout) {
    buffer += chunk.toString();
    const lines = buffer.split('\n');
    buffer = lines.pop() || '';

    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed.startsWith('data: ')) {
        try {
          yield JSON.parse(trimmed.slice(6));
        } catch {
          yield { type: 'raw', raw: trimmed.slice(6) };
        }
      }
    }
  }

  // Flush remaining
  if (buffer.trim().startsWith('data: ')) {
    try {
      yield JSON.parse(buffer.trim().slice(6));
    } catch {
      yield { type: 'raw', raw: buffer.trim().slice(6) };
    }
  }
}

/**
 * Map a Go engine event to a VL-Code callback name and normalized data.
 *
 * Go engine events:
 *   workflow_start, workflow_done, workflow_failed, workflow_cancelled
 *   step_start, step_done, step_error, step_skipped, step_print
 *   llm_token, llm_done
 *   file_start, file_done
 *   pause_start, pause_resumed, pause_timeout, pause_rejected
 *
 * @param {Object} event - Parsed SSE event from Go engine
 * @returns {{ callback: string, data: Object }}
 */
export function mapEngineEvent(event) {
  const payload = event.payload || {};

  switch (event.type) {
    case 'workflow_start':
      return {
        callback: 'onWorkflowStart',
        data: { runId: event.run_id, ts: event.ts },
      };

    case 'step_start':
      return {
        callback: 'onNodeStart',
        data: {
          nodeId: event.step_id,
          title: payload.title || event.step_id,
          type: event.step_id?.split('_')[0] || 'unknown',
        },
      };

    case 'step_done':
      return {
        callback: 'onNodeDone',
        data: {
          nodeId: event.step_id,
          title: payload.title || event.step_id,
        },
      };

    case 'step_error':
      return {
        callback: 'onNodeError',
        data: {
          nodeId: event.step_id,
          error: payload.error || payload.message || 'Unknown step error',
        },
      };

    case 'step_skipped':
      return {
        callback: 'onNodeSkipped',
        data: { nodeId: event.step_id },
      };

    case 'step_print':
      return {
        callback: 'onPrint',
        data: {
          nodeId: event.step_id,
          text: payload.text || payload.message || '',
        },
      };

    case 'llm_token':
      return {
        callback: 'onToken',
        data: { token: payload.token || payload.text || '' },
      };

    case 'llm_done':
      return {
        callback: 'onLLMDone',
        data: {
          nodeId: event.step_id,
          model: payload.model,
          latencyMs: payload.latency_ms,
          usage: payload.usage,
        },
      };

    case 'file_start':
      return {
        callback: 'onFileStart',
        data: { nodeId: event.step_id, path: payload.path },
      };

    case 'file_done':
      return {
        callback: 'onFileWritten',
        data: {
          nodeId: event.step_id,
          path: payload.path,
          size: payload.size,
        },
      };

    case 'workflow_done':
      return {
        callback: 'onDone',
        data: {
          durationMs: payload.duration_ms,
          status: 'completed',
        },
      };

    case 'workflow_failed':
      return {
        callback: 'onError',
        data: {
          message: payload.error || payload.message || 'Workflow failed',
          status: 'failed',
        },
      };

    case 'workflow_cancelled':
      return {
        callback: 'onError',
        data: { message: 'Workflow cancelled', status: 'cancelled' },
      };

    case 'pause_start':
      return {
        callback: 'onPause',
        data: {
          nodeId: event.step_id,
          waitToken: payload.wait_token,
          reason: payload.reason,
          expireAt: payload.expire_at,
        },
      };

    case 'pause_resumed':
      return {
        callback: 'onResumed',
        data: { nodeId: event.step_id },
      };

    case 'pause_timeout':
      return {
        callback: 'onPauseTimeout',
        data: { nodeId: event.step_id },
      };

    default:
      return {
        callback: 'onEvent',
        data: event,
      };
  }
}
