/**
 * Local Mode - Direct API calls to Gemini & Groq
 * Handles key rotation, validation, and AI processing
 */

const LocalMode = (() => {
  // --- Storage helpers ---
  function localSet(key, val) {
    return new Promise(resolve => {
      const o = {}; o[key] = val;
      chrome.storage.local.set(o, () => resolve(val));
    });
  }
  function localGet(key, fallback = '') {
    return new Promise(resolve => {
      const o = {}; o[key] = fallback;
      chrome.storage.local.get(o, r => resolve(r[key]));
    });
  }

  // --- Constants ---
  const GEMINI_MODEL = 'gemini-2.5-flash';
  const GROQ_MODEL = 'meta-llama/llama-4-scout-17b-16e-instruct';
  const GEMINI_BASE = 'https://generativelanguage.googleapis.com/v1beta/models';
  const GROQ_BASE = 'https://api.groq.com/openai/v1/chat/completions';

  // --- Prompts ---
  function buildKeywordPrompt(imgName, maxKeywords = 35, language = 'id', keyConcept = '') {
    const langMap = { 'en': 'English', 'id': 'Indonesian', 'th': 'Thai', 'ja': 'Japanese', 'ko': 'Korean', 'zh': 'Chinese', 'es': 'Spanish' };
    const langName = langMap[language] || 'English';
    const langNote = language !== 'en' ? ` Respond in ${langName} language.` : '';
    const conceptNote = keyConcept ? ` The main concept/topic is "${keyConcept}". Ensure keywords strongly relate to this concept.` : '';
    return `(important! please answer without conversation with me.) Use your stock photos websites knowledge and imagine the sentences "${imgName}" as a picture and make ${maxKeywords} unique ${langName} tags in total which describe this picture.${conceptNote} 

=== KEYWORD RULES (ADOBE STOCK SEO BEST PRACTICES) ===
- Generate EXACTLY ${maxKeywords} unique ${langName} keyword tags, separated by commas only. No numbering, no extra text.
- MIX OF BROAD & SPECIFIC: Ensure a healthy balance: 60-70% MUST be single-word tags for broad searches, and 30-40% can be high-quality 2-3 word natural phrases for targeted searches.
- ORDER OF IMPORTANCE: Rank keywords by strict relevance. Place the absolute most relevant, subject-descriptive terms in the first 10 tags. This is critical for Adobe Stock SEO.
- TAG FORMAT: Use singular nouns and infinitive verbs in ${langName} language.
- NO MERGING: NEVER combine/merge multiple words without spaces. You MUST use proper spaces.
- NO FILLER OR META WORDS: Do NOT use meta words (e.g., "stock photo", "image", "vector", "illustration", "wallpaper", "picture"). Never include conjunctions or prepositions (e.g., "and", "with", "or", "in", "for", "of").
- NO BRANDS/CAMERA NAMES: Never include brand names (e.g. iPhone, Nike), camera equipment/specs (e.g. Canon, Nikon, 4k, 85mm), or specific names of people.
- NO PEOPLE RULE: If the image has NO people in it, you MUST include the tags for "no people" and "nobody" in ${langName} language.

No order number or no any sign in front of tags, just use comma sign to separate tags, no other sentence just the tags only, arrange tags by most popular tags first, no image or no link or addition information sentences at the end. The result must be a string with exactly ${maxKeywords} comma-separated tags.${langNote}`;
  }

  function buildTitlePrompt(titleSuggest, keySuggest, language = 'id', keyConcept = '', minTL = 70) {
    const langMap = { 'en': 'English', 'id': 'Indonesian', 'th': 'Thai', 'ja': 'Japanese', 'ko': 'Korean', 'zh': 'Chinese', 'es': 'Spanish' };
    const langName = langMap[language] || 'English';
    const langNote = language !== 'en' ? ` Respond in ${langName} language.` : '';
    const conceptNote = keyConcept ? ` The title should strongly reflect the concept/topic: "${keyConcept}".` : '';

    // Calculate targeted optimal length
    let targetMax = 200;
    let targetMin = minTL || 70;

    return `(important! answer ONLY the title string, no other text, no quotes, no explanation.)

You are an Adobe Stock SEO title expert. Create ONE highly descriptive, search-optimized title for a stock photo in ${langName} language from context clues: "${titleSuggest}" and keywords: "${keySuggest}".
${conceptNote}

=== TITLE RULES (ADOBE STOCK SEO BEST PRACTICES) ===
- LENGTH: The title MUST be between ${targetMin} and ${targetMax} characters long. Aim as close to ${targetMax} characters as possible. Use the full character budget.
- NATURAL LANGUAGE: Use clear, natural descriptive phrases instead of a formal sentence or a comma-separated list of keywords.
- FORMAT: Do NOT use quotes, periods at the end, colons, or any special characters.
- DETAILED: Include specific details: who/what the subject is, what action is happening, where/the setting, the mood, and the visual style.
- NO BRANDS/CAMERA NAMES: Never include brand names (e.g. iPhone, Nike), camera equipment (e.g. Canon, Nikon, 4k, 85mm), or names of real people.
- CAPITALIZATION: Capitalize only the first letter of the first word (Sentence case) or use standard Title Case.
- Output ONLY the title string, nothing else.${langNote}`;
  }

  // --- API calls ---
  async function callGemini(apiKey, prompt, imageBase64 = null) {
    const parts = [];
    if (imageBase64) {
      // Extract mime type and base64 data
      const match = imageBase64.match(/^data:(.+?);base64,(.+)$/);
      if (match) {
        parts.push({
          inline_data: {
            mime_type: match[1],
            data: match[2]
          }
        });
      }
    }
    parts.push({ text: prompt });

    const url = `${GEMINI_BASE}/${GEMINI_MODEL}:generateContent?key=${apiKey}`;
    const body = {
      contents: [{ parts }],
      generationConfig: {
        temperature: 0.7,
        maxOutputTokens: 1024,
        topP: 0.95,
      },
      safetySettings: [
        { category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_NONE' },
        { category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_NONE' },
        { category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold: 'BLOCK_NONE' },
        { category: 'HARM_CATEGORY_DANGEROUS_CONTENT', threshold: 'BLOCK_NONE' },
      ]
    };

    const resp = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    });

    if (!resp.ok) {
      const err = await resp.json().catch(() => ({}));
      const code = resp.status;
      throw { code, message: err?.error?.message || `HTTP ${code}`, retryable: code === 429 || code === 500 || code === 503 };
    }

    const data = await resp.json();
    const text = data?.candidates?.[0]?.content?.parts?.[0]?.text;
    if (!text) throw { code: 0, message: 'Empty response from Gemini', retryable: false };
    return text.trim();
  }

  async function callGroq(apiKey, prompt, imageBase64 = null) {
    const messages = [];
    
    if (imageBase64) {
      // Groq with vision - use content array
      messages.push({
        role: 'user',
        content: [
          { type: 'image_url', image_url: { url: imageBase64 } },
          { type: 'text', text: prompt }
        ]
      });
    } else {
      messages.push({ role: 'user', content: prompt });
    }

    const body = {
      model: GROQ_MODEL,
      messages,
      temperature: 0.7,
      max_tokens: 1024,
      top_p: 0.95,
    };

    const resp = await fetch(GROQ_BASE, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`,
      },
      body: JSON.stringify(body),
    });

    if (!resp.ok) {
      const err = await resp.json().catch(() => ({}));
      const code = resp.status;
      throw { code, message: err?.error?.message || `HTTP ${code}`, retryable: code === 429 || code === 500 || code === 503 };
    }

    const data = await resp.json();
    const text = data?.choices?.[0]?.message?.content;
    if (!text) throw { code: 0, message: 'Empty response from Groq', retryable: false };
    return text.trim();
  }

  async function callMistral(apiKey, prompt, imageBase64 = null) {
    const messages = [];
    if (imageBase64) {
      messages.push({
        role: 'user',
        content: [
          { type: 'image_url', image_url: { url: imageBase64 } },
          { type: 'text', text: prompt }
        ]
      });
    } else {
      messages.push({ role: 'user', content: prompt });
    }

    const body = {
      model: imageBase64 ? 'pixtral-12b-2409' : 'mistral-tiny-latest',
      messages,
      temperature: 0.7,
      max_tokens: 1024,
      top_p: 0.95,
    };

    const resp = await fetch('https://api.mistral.ai/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`,
      },
      body: JSON.stringify(body),
    });

    if (!resp.ok) {
      const err = await resp.json().catch(() => ({}));
      const code = resp.status;
      throw { code, message: err?.error?.message || `HTTP ${code}`, retryable: code === 429 || code === 500 || code === 503 };
    }

    const data = await resp.json();
    const text = data?.choices?.[0]?.message?.content;
    if (!text) throw { code: 0, message: 'Empty response from Mistral', retryable: false };
    return text.trim();
  }

  // --- Key Rotation ---
  async function callWithRotation(provider, prompt, imageBase64 = null) {
    const keys = await localGet(`localKeys_${provider}`, []);
    if (!keys || keys.length === 0) {
      throw new Error(`No ${provider} API keys configured. Please add keys in Local Mode settings.`);
    }

    let indexData = await localGet('localKeyIndex', {});
    let startIndex = (indexData[provider] || 0) % keys.length;
    let attempts = 0;
    let lastError = null;

    while (attempts < keys.length) {
      const idx = (startIndex + attempts) % keys.length;
      const key = keys[idx];
      try {
        let result;
        if (provider === 'gemini') {
          result = await callGemini(key, prompt, imageBase64);
        } else if (provider === 'groq') {
          result = await callGroq(key, prompt, imageBase64);
        } else {
          result = await callMistral(key, prompt, imageBase64);
        }
        // Success - update index to next key for round-robin
        indexData[provider] = (idx + 1) % keys.length;
        await localSet('localKeyIndex', indexData);
        return result;
      } catch (err) {
        lastError = err;
        console.warn(`[LocalMode] Key #${idx + 1} for ${provider} failed:`, err.message || err);
        if (err.retryable || err.code === 429 || err.code === 401) {
          attempts++;
          continue;
        }
        // Non-retryable error
        throw err;
      }
    }

    throw new Error(`All ${provider} keys exhausted. Last error: ${lastError?.message || 'Unknown'}`);
  }

  // --- Key Validation ---
  async function validateGeminiKey(apiKey) {
    try {
      const url = `${GEMINI_BASE}/${GEMINI_MODEL}:generateContent?key=${apiKey}`;
      const body = {
        contents: [{ parts: [{ text: 'Say "ok"' }] }],
        generationConfig: { maxOutputTokens: 5 },
      };
      const resp = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });
      if (resp.ok) return { valid: true };
      const err = await resp.json().catch(() => ({}));
      return { valid: false, error: err?.error?.message || `HTTP ${resp.status}` };
    } catch (e) {
      return { valid: false, error: e.message };
    }
  }

  async function validateGroqKey(apiKey) {
    try {
      const resp = await fetch(GROQ_BASE, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${apiKey}`,
        },
        body: JSON.stringify({
          model: GROQ_MODEL,
          messages: [{ role: 'user', content: 'Say "ok"' }],
          max_tokens: 5,
        }),
      });
      if (resp.ok) return { valid: true };
      const err = await resp.json().catch(() => ({}));
      return { valid: false, error: err?.error?.message || `HTTP ${resp.status}` };
    } catch (e) {
      return { valid: false, error: e.message };
    }
  }

  async function validateMistralKey(apiKey) {
    try {
      const resp = await fetch('https://api.mistral.ai/v1/chat/completions', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${apiKey}`,
        },
        body: JSON.stringify({
          model: 'mistral-tiny-latest',
          messages: [{ role: 'user', content: 'Say "ok"' }],
          max_tokens: 5,
        }),
      });
      if (resp.ok) return { valid: true };
      const err = await resp.json().catch(() => ({}));
      return { valid: false, error: err?.error?.message || `HTTP ${resp.status}` };
    } catch (e) {
      return { valid: false, error: e.message };
    }
  }

  // --- Main entry point ---
  async function processImage(imageUrl, searchType) {
    const settings = await localGet('localSettings', {});
    const provider = await localGet('localProvider', 'gemini');
    const maxKeywords = settings.maxKeywords || 35;
    const language = settings.language || 'id';
    const minTL = settings.minTitleLength || 70;

    let prompt;
    let imageBase64 = null;

    // Fetch image and convert to base64 for vision models
    if (imageUrl && (searchType === 'localAi' || searchType === 'localAiImg')) {
      try {
        const resp = await fetch(imageUrl);
        const blob = await resp.blob();
        imageBase64 = await blobToBase64(blob);
      } catch (e) {
        console.warn('[LocalMode] Could not fetch image, proceeding text-only:', e);
      }
    }

    const imgName = await localGet('imgName', '');
    const titleSuggest = await localGet('titleSuggest', '');
    const keySuggest = await localGet('keySuggest', '');
    const keyConcept = settings.keyConcept || '';

    switch (searchType) {
      case 'localAi':
      case 'localAiImg':
        prompt = buildKeywordPrompt(imgName, maxKeywords, language, keyConcept);
        break;
      case 'localTitle':
        prompt = buildTitlePrompt(titleSuggest, keySuggest, language, keyConcept, minTL);
        break;
      default:
        prompt = buildKeywordPrompt(imgName, maxKeywords, language, keyConcept);
    }

    return await callWithRotation(provider, prompt, imageBase64);
  }

  function blobToBase64(blob) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onloadend = () => resolve(reader.result);
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    });
  }

  // --- Public API ---
  return {
    processImage,
    validateGeminiKey,
    validateGroqKey,
    validateMistralKey,
    callWithRotation,
    buildKeywordPrompt,
    buildTitlePrompt,
    GEMINI_MODEL,
    GROQ_MODEL,
  };
})();

// Export for use in background.js
if (typeof self !== 'undefined') {
  self.LocalMode = LocalMode;
}
