/**
 * DeepSeek Service - Script Generation
 * 
 * Uses OpenAI-compatible SDK with DeepSeek API for comic script generation
 */

import OpenAI from 'openai';
import type { ComicScript, GenerateScriptOptions } from '@/types';

const DEEPSEEK_API_KEY = import.meta.env.VITE_DEEPSEEK_API_KEY;

if (!DEEPSEEK_API_KEY) {
  console.warn('VITE_DEEPSEEK_API_KEY not set. DeepSeek functionality will not work.');
}

const deepseek = new OpenAI({
  apiKey: DEEPSEEK_API_KEY || 'dummy-key',
  baseURL: 'https://api.deepseek.com',
  dangerouslyAllowBrowser: true,
});

/**
 * System prompt for comic script generation
 * Produces structured, panel-by-panel comic scripts optimized for AI image generation
 */
const SCRIPT_SYSTEM_PROMPT = `You are an expert comic book writer and storyboard artist.
Your task is to take a user's story idea and transform it into a detailed comic book script optimized for AI image generation.

OUTPUT FORMAT: Return ONLY valid JSON in this exact structure:
{
  "title": "Comic Title",
  "synopsis": "One-paragraph summary",
  "characters": [
    {
      "id": "char_001",
      "name": "Character Name",
      "role": "protagonist|antagonist|supporting",
      "description": "Detailed visual description for image generation: age, gender, hair color/style, eye color, skin tone, body type, clothing, distinguishing features. Be extremely specific.",
      "personality": "Brief personality notes for dialogue",
      "firstAppearancePanel": "panel_001"
    }
  ],
  "pages": [
    {
      "pageNumber": 1,
      "layoutType": "grid|manga|western|action|dialogue|splash",
      "panels": [
        {
          "panelId": "panel_001",
          "panelNumber": 1,
          "shotType": "establishing|wide|medium|close-up|extreme-close-up|over-shoulder|aerial",
          "description": "Detailed scene description for image generation. Include: setting, lighting, mood, character positions, action, camera angle. 2-3 sentences.",
          "charactersPresent": ["char_001"],
          "dialogue": [
            {
              "speakerId": "char_001",
              "text": "Dialogue text",
              "bubbleType": "normal|thought|shout|whisper",
              "emotion": "happy|sad|angry|surprised|neutral|determined"
            }
          ],
          "caption": "Optional narration caption",
          "soundEffects": ["BANG!", "CRASH!"],
          "transitionFromPrevious": "none|fade|wipe|dissolve|action-lines"
        }
      ]
    }
  ]
}

RULES:
- 4-8 panels per page for standard pages, 1 panel for splash pages
- Vary shot types across panels for visual interest
- Include at least one establishing shot per scene
- Dialogue should be concise (1-3 lines per bubble)
- Description fields must be IMAGE-GENERATION-READY: vivid, specific, include art style keywords
- Character descriptions must be EXTREMELY detailed and consistent for all panels
- Include mood and lighting keywords in every panel description`;

/**
 * Build user prompt for script generation
 */
function buildUserPrompt(options: GenerateScriptOptions): string {
  const { storyIdea, genre, artStyle, numPages, audience } = options;
  
  return `Create a ${numPages}-page comic script based on this idea: "${storyIdea}".
Genre: ${genre}
Target Art Style: ${artStyle} (incorporate style keywords into panel descriptions)
Target Audience: ${audience}`;
}

/**
 * Validate and normalize the generated script
 * Ensures all required fields are present and properly formatted
 */
export function validateAndNormalizeScript(script: unknown): ComicScript {
  if (!script || typeof script !== 'object') {
    throw new Error('Invalid script: not an object');
  }

  const s = script as Record<string, unknown>;

  // Validate required fields
  if (!s.title || typeof s.title !== 'string') {
    throw new Error('Invalid script: missing or invalid title');
  }

  if (!s.synopsis || typeof s.synopsis !== 'string') {
    throw new Error('Invalid script: missing or invalid synopsis');
  }

  if (!Array.isArray(s.characters)) {
    throw new Error('Invalid script: characters must be an array');
  }

  if (!Array.isArray(s.pages)) {
    throw new Error('Invalid script: pages must be an array');
  }

  // Normalize characters
  const characters: ComicScript['characters'] = s.characters.map((char: unknown, index: number) => {
    const c = char as Record<string, unknown>;
    return {
      id: String(c.id || `char_${String(index + 1).padStart(3, '0')}`),
      name: String(c.name || `Character ${index + 1}`),
      role: (c.role as 'protagonist' | 'antagonist' | 'supporting' | 'extra') || 'supporting',
      description: String(c.description || ''),
      promptTemplate: (c.promptTemplate as ComicScript['characters'][0]['promptTemplate']) || {
        age: '',
        gender: '',
        hairColor: '',
        hairStyle: '',
        skinTone: '',
        eyeColor: '',
        bodyType: '',
        outfit: '',
        accessories: '',
        distinguishingFeatures: '',
      },
      referenceImageUrl: String(c.referenceImageUrl || ''),
      characterSheetUrls: Array.isArray(c.characterSheetUrls) ? c.characterSheetUrls.map(String) : [],
      seed: Number(c.seed || Math.floor(Math.random() * 2147483647)),
      colorPalette: (c.colorPalette as ComicScript['characters'][0]['colorPalette']) || {
        hair: '',
        eyes: '',
        skin: '',
        outfit: '',
      },
      appearanceCount: Number(c.appearanceCount || 0),
      firstAppearancePanel: c.firstAppearancePanel ? String(c.firstAppearancePanel) : undefined,
    };
  });

  // Normalize pages and panels
  const pages: ComicScript['pages'] = s.pages.map((page: unknown, pageIndex: number) => {
    const p = page as Record<string, unknown>;
    const panels: ComicScript['pages'][0]['panels'] = Array.isArray(p.panels) 
      ? p.panels.map((panel: unknown, panelIndex: number) => {
          const pan = panel as Record<string, unknown>;
          return {
            panelId: String(pan.panelId || `panel_${String(pageIndex + 1).padStart(3, '0')}_${String(panelIndex + 1).padStart(3, '0')}`),
            panelNumber: Number(pan.panelNumber || panelIndex + 1),
            shotType: (pan.shotType as 'establishing' | 'wide' | 'medium' | 'close-up' | 'extreme-close-up' | 'over-shoulder' | 'aerial') || 'medium',
            description: String(pan.description || ''),
            charactersPresent: Array.isArray(pan.charactersPresent) ? pan.charactersPresent.map(String) : [],
            dialogue: Array.isArray(pan.dialogue) ? pan.dialogue.map((d: unknown) => {
              const dia = d as Record<string, unknown>;
              return {
                speakerId: String(dia.speakerId || ''),
                text: String(dia.text || ''),
                bubbleType: (dia.bubbleType as 'normal' | 'thought' | 'shout' | 'whisper') || 'normal',
                emotion: (dia.emotion as 'happy' | 'sad' | 'angry' | 'surprised' | 'neutral' | 'determined') || 'neutral',
              };
            }) : [],
            caption: pan.caption ? String(pan.caption) : undefined,
            soundEffects: Array.isArray(pan.soundEffects) ? pan.soundEffects.map(String) : [],
            transitionFromPrevious: (pan.transitionFromPrevious as 'none' | 'fade' | 'wipe' | 'dissolve' | 'action-lines') || 'none',
          };
        })
      : [];

    return {
      pageNumber: Number(p.pageNumber || pageIndex + 1),
      layoutType: (p.layoutType as 'grid' | 'manga' | 'western' | 'action' | 'dialogue' | 'splash') || 'grid',
      panels,
    };
  });

  return {
    title: s.title,
    synopsis: s.synopsis,
    characters,
    pages,
  };
}

/**
 * Generate comic script from story idea
 * Returns complete structured script with characters, pages, and panels
 */
export async function generateComicScript(
  options: GenerateScriptOptions
): Promise<ComicScript> {
  if (!DEEPSEEK_API_KEY) {
    throw new Error('DeepSeek API key not configured. Please set VITE_DEEPSEEK_API_KEY in .env.local');
  }

  const userPrompt = buildUserPrompt(options);

  const response = await deepseek.chat.completions.create({
    model: 'deepseek-chat',
    messages: [
      { role: 'system', content: SCRIPT_SYSTEM_PROMPT },
      { role: 'user', content: userPrompt },
    ],
    response_format: { type: 'json_object' },
    temperature: 0.8,
    max_tokens: 8000,
  });

  const content = response.choices[0]?.message?.content;
  if (!content) {
    throw new Error('Empty response from DeepSeek API');
  }

  try {
    const script = JSON.parse(content);
    return validateAndNormalizeScript(script);
  } catch (error) {
    console.error('Failed to parse script:', error);
    console.error('Raw content:', content);
    throw new Error('Failed to parse generated script. Please try again.');
  }
}

/**
 * Stream comic script generation for real-time preview (Professional mode)
 * Yields partial script chunks as they are generated
 */
export async function* streamComicScript(
  options: GenerateScriptOptions
): AsyncGenerator<{ content: string; partial: string }> {
  if (!DEEPSEEK_API_KEY) {
    throw new Error('DeepSeek API key not configured');
  }

  const userPrompt = buildUserPrompt(options);

  const stream = await deepseek.chat.completions.create({
    model: 'deepseek-chat',
    messages: [
      { role: 'system', content: SCRIPT_SYSTEM_PROMPT },
      { role: 'user', content: userPrompt },
    ],
    stream: true,
    temperature: 0.8,
    max_tokens: 8000,
  });

  let buffer = '';
  for await (const chunk of stream) {
    const content = chunk.choices[0]?.delta?.content || '';
    buffer += content;
    yield { content, partial: buffer };
  }
}

/**
 * Generate script with retry logic
 * Automatically retries on transient failures
 */
export async function generateComicScriptWithRetry(
  options: GenerateScriptOptions,
  retries: number = 3,
  delay: number = 1000
): Promise<ComicScript> {
  for (let i = 0; i < retries; i++) {
    try {
      return await generateComicScript(options);
    } catch (error) {
      if (i === retries - 1) throw error;
      
      // Check if it's a rate limit error
      if (error instanceof Error && error.message.includes('429')) {
        const waitTime = delay * Math.pow(2, i);
        console.log(`Rate limited. Retrying in ${waitTime}ms...`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
      } else {
        throw error;
      }
    }
  }
  
  throw new Error('Max retries exceeded');
}
