export async function generateWithGemini(apiKey, systemPrompt, userPrompt, isScene, statusMessages, options = {}) {
  const {
    fetchImpl = fetch,
    onStatus = () => {},
    Decoder = TextDecoder,
    AbortControllerImpl = AbortController,
    now = Date.now,
    setTimer = setTimeout,
  } = options;
  const streamUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-preview:streamGenerateContent?alt=sse&key=${apiKey}`;
  
  const response = await fetchImpl(streamUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      contents: [{
        parts: [
          { text: systemPrompt },
          { text: userPrompt }
        ]
      }],
      generationConfig: {
        temperature: isScene ? 0.9 : 0.8,
        topP: 0.95,
        topK: 40,
        maxOutputTokens: 65536
      }
    })
  });
  
  if (!response.ok) {
    const errorText = await response.text();
    let errorMessage = 'Gemini API request failed';
    try {
      const errorJson = JSON.parse(errorText);
      errorMessage = errorJson.error?.message || errorMessage;
    } catch (e) {}
    throw new Error(errorMessage);
  }
  
  const reader = response.body.getReader();
  const decoder = new Decoder();
  let generatedCode = '';
  let buffer = '';
  let chunkCount = 0;
  let lastPreview = '';
  
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    
    buffer += decoder.decode(value, { stream: true });
    
    const lines = buffer.split('\n');
    buffer = lines.pop() || '';
    
    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const jsonStr = line.slice(6);
        if (jsonStr.trim() === '[DONE]') continue;
        
        try {
          const chunk = JSON.parse(jsonStr);
          const text = chunk.candidates?.[0]?.content?.parts?.[0]?.text || '';
          if (text) {
            generatedCode += text;
            chunkCount++;
            
            const progress = Math.min(85, 15 + chunkCount * 2);
            const msgIndex = Math.min(statusMessages.length - 1, Math.floor(chunkCount / 5));
            
            const preview = generatedCode.slice(-300).replace(/\n/g, ' ').trim();
            if (preview !== lastPreview) {
              lastPreview = preview;
              onStatus(statusMessages[msgIndex], 'info', progress, preview.length > 50 ? '...' + preview.slice(-250) : preview);
            }
          }
        } catch (e) {
          // Skip malformed JSON chunks
        }
      }
    }
  }
  
  return generatedCode;
}

export async function generateWithClaude(apiKey, systemPrompt, userPrompt, isScene, statusMessages, options = {}) {
  const {
    fetchImpl = fetch,
    onStatus = () => {},
    Decoder = TextDecoder,
    AbortControllerImpl = AbortController,
    now = Date.now,
    setTimer = setTimeout,
    clearTimer = clearTimeout,
  } = options;
  const startTime = now();
  
  console.log('[CLAUDE] Starting generation request...');
  console.log('[CLAUDE] Prompt length:', userPrompt.length, 'chars');
  
  // Create abort controller (used by chunk timeout, no overall timeout)
  const controller = new AbortControllerImpl();
  
  try {
    // Claude requires a server-side proxy for CORS
    console.log('[CLAUDE] Sending request to /api/ai/claude...');
    onStatus('Connecting to Claude API...', 'info', 8);
    
    const response = await fetchImpl('/api/ai/claude', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        apiKey: apiKey,
        systemPrompt: systemPrompt,
        userPrompt: userPrompt,
        isScene: isScene,
        maxTokens: 65536
      }),
      signal: controller.signal
    });
    
    console.log('[CLAUDE] Response status:', response.status, response.statusText);
    
    if (!response.ok) {
      const errorText = await response.text();
      console.error('[CLAUDE] Error response:', errorText);
      let errorMessage = 'Claude API request failed';
      try {
        const errorJson = JSON.parse(errorText);
        errorMessage = errorJson.error?.message || errorJson.error || errorMessage;
      } catch (e) {}
      throw new Error(errorMessage);
    }
    
    console.log('[CLAUDE] Connection established, starting to stream response...');
    onStatus('Connected! Waiting for Claude response...', 'info', 12);
    
    // Stream the response
    const reader = response.body.getReader();
    const decoder = new Decoder();
    let generatedCode = '';
    let buffer = '';
    let chunkCount = 0;
    let lastPreview = '';
    let lastChunkTime = now();
    const CHUNK_TIMEOUT_MS = 60000; // 60 second timeout between chunks
    
    while (true) {
      // Check for chunk timeout
      if (now() - lastChunkTime > CHUNK_TIMEOUT_MS) {
        console.error('[CLAUDE] Chunk timeout - no data received for', CHUNK_TIMEOUT_MS / 1000, 'seconds');
        throw new Error('Claude stopped responding. Try again or use a shorter prompt.');
      }
      
      // Use a timeout for each read operation
      const readPromise = reader.read();
      let timeoutId;
      const timeoutPromise = new Promise((_, reject) => {
        timeoutId = setTimer(() => reject(new Error('Read timeout')), CHUNK_TIMEOUT_MS);
      });
      
      let readResult;
      try {
        readResult = await Promise.race([readPromise, timeoutPromise]);
      } catch (e) {
        if (e.message === 'Read timeout') {
          console.error('[CLAUDE] Read timeout after', CHUNK_TIMEOUT_MS / 1000, 'seconds');
          throw new Error('Claude stopped responding. The request may have been too complex.');
        }
        throw e;
      } finally {
        clearTimer(timeoutId);
      }
      
      const { done, value } = readResult;
      if (done) {
        console.log('[CLAUDE] Stream complete. Total chunks:', chunkCount, 'Total chars:', generatedCode.length);
        break;
      }
      
      lastChunkTime = now();
      buffer += decoder.decode(value, { stream: true });
      
      // Parse SSE events from our proxy
      const lines = buffer.split('\n');
      buffer = lines.pop() || '';
      
      let streamDone = false;
      for (const line of lines) {
        if (line.startsWith('data: ')) {
          const jsonStr = line.slice(6);
          if (jsonStr.trim() === '[DONE]') {
            console.log('[CLAUDE] Received [DONE] signal');
            streamDone = true;
            break; // Break out of for loop
          }
          
          try {
            const chunk = JSON.parse(jsonStr);
            
            // Check for error in chunk
            if (chunk.error) {
              console.error('[CLAUDE] Error in chunk:', chunk.error);
              throw new Error(chunk.error);
            }
            
            const text = chunk.text || chunk.delta?.text || '';
            if (text) {
              generatedCode += text;
              chunkCount++;
              
              if (chunkCount === 1) {
                console.log('[CLAUDE] First chunk received after', (now() - startTime) / 1000, 'seconds');
              }
              
              const progress = Math.min(85, 15 + chunkCount * 2);
              const msgIndex = Math.min(statusMessages.length - 1, Math.floor(chunkCount / 5));
              
              const preview = generatedCode.slice(-300).replace(/\n/g, ' ').trim();
              if (preview !== lastPreview) {
                lastPreview = preview;
                onStatus(statusMessages[msgIndex], 'info', progress, preview.length > 50 ? '...' + preview.slice(-250) : preview);
              }
            }
          } catch (e) {
            if (e.message && !e.message.includes('JSON')) {
              throw e; // Re-throw non-JSON errors
            }
            // Skip malformed JSON chunks
            console.warn('[CLAUDE] Malformed chunk:', jsonStr.slice(0, 100));
          }
        }
      }
      
      // Break out of while loop if we received [DONE]
      if (streamDone) {
        console.log('[CLAUDE] Stream complete. Total chunks:', chunkCount, 'Total chars:', generatedCode.length);
        break;
      }
    }
    
    if (!generatedCode || generatedCode.trim().length === 0) {
      console.error('[CLAUDE] Empty response received');
      throw new Error('Claude returned an empty response. Please try again with a different prompt.');
    }
    
    console.log('[CLAUDE] Generation complete in', (now() - startTime) / 1000, 'seconds');
    return generatedCode;
    
  } catch (e) {
    if (e.name === 'AbortError') {
      console.error('[CLAUDE] Request aborted');
      throw new Error('Claude request was cancelled. This may happen if Claude stops responding.');
    }
    console.error('[CLAUDE] Error:', e.message);
    throw e;
  }
}
