/**
 * Stream transformer — provider 별 SSE 형식을 *OpenAI 호환* SSE 로 변환.
 *
 * 이렇게 하면 agent.ts 의 기존 SSE 파서 (`data: {...delta...}` 형식 가정) 가 변경 없이
 * 모든 provider 출력을 같은 코드 경로로 소비할 수 있다. 신규 provider 가 들어와도
 * adapter 하나만 추가하면 됨 (consumer 무손상).
 *
 * 변환 후 출력 형식 (OpenAI Chat Completions stream):
 *   data: {"choices":[{"delta":{"content":"안녕"},"index":0}]}\n\n
 *   data: {"choices":[{"delta":{"content":" 반가"},"index":0}]}\n\n
 *   data: [DONE]\n\n
 */

/**
 * Anthropic Messages API streaming → OpenAI SSE.
 *
 * Anthropic 이벤트 형식 (SSE):
 *   event: message_start\ndata: {...}
 *   event: content_block_start\ndata: {...}
 *   event: content_block_delta\ndata: {"delta":{"type":"text_delta","text":"안녕"}}
 *   event: content_block_stop\ndata: {...}
 *   event: message_delta\ndata: {"usage": {...}}
 *   event: message_stop\ndata: {...}
 *
 * 변환:
 *   - content_block_delta 의 text → OpenAI delta.content 한 chunk
 *   - message_stop → [DONE]
 *   - 그 외 이벤트 무시 (tool_use 등은 향후)
 */
export function transformAnthropicStream(input: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> {
    return _transformSse(input, _anthropicEventToOpenAI);
}

/**
 * Gemini streamGenerateContent (alt=sse) → OpenAI SSE.
 *
 * Gemini SSE chunk 형식:
 *   data: {"candidates":[{"content":{"parts":[{"text":"안녕"}],"role":"model"}}]}
 *   data: {"candidates":[{"content":{"parts":[{"text":" 반가"}],"role":"model"}}]}
 *   ... (stream 끝나면 connection close — 별도 [DONE] 없음)
 *
 * 변환:
 *   - candidates[0].content.parts[*].text → OpenAI delta.content 로 합쳐 emit
 *   - 스트림 종료 후 [DONE] 마커 1회 emit (consumer 호환)
 */
export function transformGeminiStream(input: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> {
    return _transformSse(input, _geminiEventToOpenAI);
}

// ────────────── 내부 헬퍼 ──────────────

type EventToOpenAI = (eventName: string | null, dataLine: string) => string | null;

/**
 * 공통 SSE 파서 + 변환기. line 단위로 input stream 을 읽어
 *   `event: X` + `data: {...}` 블록 단위로 모아서 변환기에 전달.
 * 변환기가 null 이 아닌 OpenAI-format SSE 문자열을 돌려주면 output 으로 emit.
 * 스트림 종료 시 'data: [DONE]' 자동 추가.
 */
function _transformSse(
    input: ReadableStream<Uint8Array>,
    convert: EventToOpenAI,
): ReadableStream<Uint8Array> {
    const decoder = new TextDecoder();
    const encoder = new TextEncoder();
    let buffer = '';
    let pendingEvent: string | null = null;
    const reader = input.getReader();

    return new ReadableStream<Uint8Array>({
        async pull(controller) {
            try {
                const { value, done } = await reader.read();
                if (done) {
                    // 종료 시 [DONE] 마커.
                    controller.enqueue(encoder.encode('data: [DONE]\n\n'));
                    controller.close();
                    return;
                }
                buffer += decoder.decode(value, { stream: true });
                let idx: number;
                while ((idx = buffer.indexOf('\n')) >= 0) {
                    const line = buffer.slice(0, idx).replace(/\r$/, '');
                    buffer = buffer.slice(idx + 1);
                    if (line === '') {
                        // blank line 은 event 종료 — 아무것도 안 함 (data 누적 X — 본 구현은 1줄 1이벤트 가정)
                        pendingEvent = null;
                        continue;
                    }
                    if (line.startsWith(':')) continue; // comment
                    if (line.startsWith('event:')) {
                        pendingEvent = line.slice(6).trim();
                        continue;
                    }
                    if (line.startsWith('data:')) {
                        const dataLine = line.slice(5).trim();
                        if (!dataLine) continue;
                        const out = convert(pendingEvent, dataLine);
                        if (out) controller.enqueue(encoder.encode(out));
                        // pendingEvent 는 blank line 이 와야 reset — Anthropic 처럼 한 event 에 여러 data 가능
                    }
                }
            } catch (e) {
                controller.error(e);
            }
        },
        cancel() {
            reader.cancel().catch(() => { /* ignore */ });
        },
    });
}

function _anthropicEventToOpenAI(eventName: string | null, dataLine: string): string | null {
    if (eventName !== 'content_block_delta') return null;
    try {
        const obj = JSON.parse(dataLine);
        const text = obj?.delta?.text;
        if (typeof text !== 'string' || !text) return null;
        const openai = { choices: [{ delta: { content: text }, index: 0 }] };
        return `data: ${JSON.stringify(openai)}\n\n`;
    } catch {
        return null;
    }
}

function _geminiEventToOpenAI(_eventName: string | null, dataLine: string): string | null {
    try {
        const obj = JSON.parse(dataLine);
        const parts = obj?.candidates?.[0]?.content?.parts;
        if (!Array.isArray(parts)) return null;
        const text = parts.map((p: any) => (typeof p?.text === 'string' ? p.text : '')).join('');
        if (!text) return null;
        const openai = { choices: [{ delta: { content: text }, index: 0 }] };
        return `data: ${JSON.stringify(openai)}\n\n`;
    } catch {
        return null;
    }
}

// 단위테스트용 — 변환 helper 들을 그대로 노출.
export const _internals = {
    anthropicEventToOpenAI: _anthropicEventToOpenAI,
    geminiEventToOpenAI: _geminiEventToOpenAI,
};
