// === AI Service ===
// En producción llama al proxy /api/ai-complete (la key vive en env vars de Vercel).
// En dev local sin backend, como fallback usa window.__PROTOTYPE_SECRETS.OPENAI_KEY
// directo a OpenAI. Este fallback se activa si el fetch al proxy devuelve 404 o no responde.

(function() {
  const PROXY_URL = '/api/ai-complete';
  const OPENAI_URL = 'https://api.openai.com/v1/chat/completions';
  const MODEL = 'gpt-4o-mini';
  const TIMEOUT_MS = 20000;

  let proxyWorks = null; // null=unknown, true=usando proxy, false=fallback a directo

  async function aiCompleteViaProxy(prompt, options, questionId) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
    try {
      const res = await fetch(PROXY_URL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ prompt, options, questionId }),
        signal: controller.signal,
      });
      clearTimeout(timer);
      if (res.status === 404) return { ok: false, error: 'proxy_404' }; // señal para fallback
      if (res.status === 429) {
        const j = await res.json().catch(() => ({}));
        return { ok: false, error: 'rate_limited', retryAfter: j.retryAfter };
      }
      if (!res.ok) {
        console.warn('ai-complete proxy HTTP', res.status);
        return { ok: false, error: `http_${res.status}` };
      }
      const j = await res.json();
      return { ok: !!j.ok, data: j.data, error: j.error };
    } catch (err) {
      clearTimeout(timer);
      console.warn('ai-complete proxy error:', err?.message || err);
      return { ok: false, error: 'proxy_unreachable' };
    }
  }

  async function aiCompleteDirect(prompt, options) {
    const key = window.__PROTOTYPE_SECRETS?.OPENAI_KEY;
    if (!key) return { ok: false, error: 'missing_key' };

    const body = {
      model: MODEL,
      messages: [{ role: 'user', content: prompt }],
      temperature: options.temperature ?? 0.7,
      max_tokens: options.maxTokens ?? 800,
    };
    if (options.jsonMode) body.response_format = { type: 'json_object' };

    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
    try {
      const res = await fetch(OPENAI_URL, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${key}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(body),
        signal: controller.signal,
      });
      clearTimeout(timer);
      if (!res.ok) return { ok: false, error: `http_${res.status}` };
      const data = await res.json();
      const content = data.choices?.[0]?.message?.content || '';
      return { ok: true, data: content };
    } catch (err) {
      clearTimeout(timer);
      return { ok: false, error: String(err?.message || err) };
    }
  }

  async function aiComplete(prompt, options = {}, questionId) {
    // Primer intento: proxy
    if (proxyWorks !== false) {
      const r = await aiCompleteViaProxy(prompt, options, questionId);
      if (r.error === 'proxy_404' || r.error === 'proxy_unreachable') {
        // Cambiar a modo directo SOLO si hay key local (dev sin backend).
        // Sin key local el modo directo no puede funcionar nunca; mejor
        // seguir reintentando el proxy en las próximas llamadas — un fetch
        // fallido puntual (red inestable, cold start) no debe matar la IA
        // por el resto de la sesión.
        const hasLocalKey = !!window.__PROTOTYPE_SECRETS?.OPENAI_KEY;
        if (proxyWorks === null && hasLocalKey) {
          console.info('ai_service: proxy /api/ai-complete no disponible, usando fallback directo a OpenAI (dev local).');
          proxyWorks = false;
        } else if (!hasLocalKey) {
          console.warn('ai_service: proxy no disponible y sin key local; se reintentará el proxy.');
          return r;
        }
      } else {
        proxyWorks = true;
        return r;
      }
    }
    // Fallback
    return aiCompleteDirect(prompt, options);
  }

  // Parse JSON array from a model response that may be wrapped in markdown or prose
  function parseJsonArray(text) {
    if (!text) return null;
    const cleaned = text.replace(/```json|```/g, '').trim();
    const match = cleaned.match(/\[[\s\S]*\]/);
    if (!match) return null;
    try {
      const parsed = JSON.parse(match[0]);
      return Array.isArray(parsed) && parsed.length > 0 ? parsed : null;
    } catch (err) {
      return null;
    }
  }

  // === Cache ===
  const CACHE = new Map(); // key = `${questionId}::${contextHash}` → { data, ts }
  const INFLIGHT = new Map(); // same key → Promise

  function cacheKey(questionId, contextHash) {
    return `${questionId}::${contextHash}`;
  }

  function getCached(questionId, contextHash) {
    return CACHE.get(cacheKey(questionId, contextHash));
  }

  function setCached(questionId, contextHash, data) {
    CACHE.set(cacheKey(questionId, contextHash), { data, ts: Date.now() });
  }

  function invalidateCache(predicate) {
    // predicate(key) → true to delete
    for (const k of Array.from(CACHE.keys())) {
      if (predicate(k)) CACHE.delete(k);
    }
    for (const k of Array.from(INFLIGHT.keys())) {
      if (predicate(k)) INFLIGHT.delete(k);
    }
  }

  // Build summary of context for prompt (mirrors buildContextSummary in question_inputs.jsx)
  function buildContextSummary(answers) {
    const lines = [];
    if (answers.business_name) lines.push(`Negocio: ${answers.business_name}`);
    if (answers.business_description) lines.push(`Descripción: ${answers.business_description}`);
    if (answers.business_differentiator) lines.push(`Diferenciador: ${answers.business_differentiator}`);
    // Identity type — crucial for greeting generation
    const idType = answers.agent_identity_type?.label;
    if (idType === 'Persona con nombre') {
      const d = answers.agent_identity_details || {};
      const name = d.name || '[nombre]';
      const role = d.role || 'asesor/a';
      lines.push(`Identidad del agente: PERSONA llamada "${name}", rol "${role}". El agente se presenta con este nombre, NO como el negocio.`);
    } else if (idType === 'La marca como tal') {
      lines.push(`Identidad del agente: LA MARCA "${answers.business_name || '[negocio]'}". El agente habla en nombre de la empresa, NO se presenta con un nombre personal. Ej: "Hola, por acá [negocio]" o "Te contactamos desde [negocio]".`);
    }
    // Tono y reglas de estilo (sección 4) — los textos generados después
    // (saludos, pitches) deben respetarlos.
    if (answers.tone?.sample) lines.push(`Tono elegido (ejemplo de referencia): ${answers.tone.sample}`);
    const ws = answers.writing_style;
    if (ws && typeof ws === 'object') {
      const wsMeta = (window.QUESTIONNAIRE || []).find(q => q.id === 'writing_style');
      const rules = [];
      for (const t of wsMeta?.toggles || []) {
        const side = ws[t.id];
        if (side === 'left' || side === 'right') rules.push(`${t.title}: ${side === 'left' ? t.left : t.right}`);
      }
      if (rules.length) lines.push(`Reglas de estilo (OBLIGATORIAS en cualquier texto generado): ${rules.join(' · ')}`);
    }
    if (answers.ideal_customer?.sample) lines.push(`Cliente ideal: ${answers.ideal_customer.sample}`);
    if (answers.customer_problem) {
      const cp = Array.isArray(answers.customer_problem)
        ? answers.customer_problem.map(x => x.label || x).join(', ')
        : (answers.customer_problem.label || answers.customer_problem);
      if (cp) lines.push(`Problema principal: ${cp}`);
    }
    if (Array.isArray(answers.agent_types) && answers.agent_types.length) {
      lines.push(`Tipos de agente: ${answers.agent_types.join(', ')}`);
    }
    return lines.join('\n') || 'Aún sin descripción de negocio.';
  }

  // Generate for a single aiGenerated question
  async function generateForQuestion(question, answers) {
    if (!question.aiPrompt) return question.fallback || question.options || [];

    const depsRaw = question.aiDeps || ['business_description'];
    // Deps can be prefixed with "?" to mark them as optional (used only for hash/context,
    // not required for generation). Example: ['business_description', '?agent_identity_details']
    const requiredDeps = depsRaw.filter(d => !d.startsWith('?'));
    const allDeps = depsRaw.map(d => d.startsWith('?') ? d.slice(1) : d);
    // Check all REQUIRED deps are present
    for (const d of requiredDeps) {
      const v = answers[d];
      const filled = v !== undefined && v !== null && v !== '' &&
        !(Array.isArray(v) && v.length === 0) &&
        !(typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0);
      if (!filled) return question.fallback || question.options || [];
    }

    const contextHash = allDeps.map(d => {
      try { return JSON.stringify(answers[d]); } catch { return ''; }
    }).join('|').slice(0, 500);

    // Cache hit
    const cached = getCached(question.id, contextHash);
    if (cached) return cached.data;

    // In-flight?
    const inflightKey = cacheKey(question.id, contextHash);
    if (INFLIGHT.has(inflightKey)) return INFLIGHT.get(inflightKey);

    const promise = (async () => {
      const summary = buildContextSummary(answers);
      const prompt = `Contexto del negocio:\n${summary}\n\nTarea: ${question.aiPrompt}\n\nResponde SOLO con un JSON array válido envuelto en un objeto con la clave "items". Ejemplo: {"items": [...]}`;
      const res = await aiComplete(prompt, { jsonMode: true, temperature: 0.7 }, question.id);
      if (!res.ok) {
        INFLIGHT.delete(inflightKey);
        return question.fallback || question.options || [];
      }
      let data = null;
      try {
        const parsed = JSON.parse(res.data);
        data = Array.isArray(parsed) ? parsed : (parsed.items || parsed.options || parsed.data || null);
      } catch {
        // Fallback to old extraction
        data = parseJsonArray(res.data);
      }
      if (!data || !Array.isArray(data) || data.length === 0) {
        INFLIGHT.delete(inflightKey);
        return question.fallback || question.options || [];
      }
      setCached(question.id, contextHash, data);
      INFLIGHT.delete(inflightKey);
      return data;
    })();

    INFLIGHT.set(inflightKey, promise);
    return promise;
  }

  // Preload: iterate over QUESTIONNAIRE and trigger generation for aiGenerated/aiGeneratedChips questions whose deps are satisfied
  function preload(answers) {
    if (!window.QUESTIONNAIRE) return;
    for (const q of window.QUESTIONNAIRE) {
      const isAiQuestion = (q.aiGenerated || q.aiGeneratedChips) && q.aiPrompt;
      if (!isAiQuestion) continue;
      // Skip if condition is defined and fails — we don't need suggestions for hidden questions
      if (q.condition && !q.condition(answers)) continue;
      // Fire-and-forget; generateForQuestion handles cache + in-flight dedup
      generateForQuestion(q, answers).catch(err => console.warn('preload err', q.id, err));
    }
  }

  // Expose
  window.aiComplete = aiComplete;
  window.aiCache = {
    get: getCached,
    set: setCached,
    invalidate: invalidateCache,
    generateForQuestion,
    preload,
    parseJsonArray,
    buildContextSummary,
  };
})();
