// openrouter.jsx — OpenRouter client. Two modes:
//   1) Proxy mode (preferred): a Cloudflare Pages Function at /api/chat
//      forwards to OpenRouter with the key from server env. No key in browser.
//      Detected automatically via /api/health.
//   2) Direct mode (fallback): browser calls openrouter.ai with a user-supplied
//      key from localStorage. Won't work for Claude (Anthropic blocks
//      browser-origin requests), but works for Gemini/GPT/Llama.

const OR_BASE = 'https://openrouter.ai/api/v1';
const OR_KEY_STORE = 'chopstick.or.key';
const OR_MODEL_STORE = 'chopstick.or.model';
const PROXY_HEALTH = '/api/health';
const PROXY_CHAT = '/api/chat';

// Server-proxy detection — lazy, cached after first probe.
let _proxyChecked = false;
let _proxyAvailable = false;
let _proxyHasKey = false;

async function _checkProxy() {
  if (_proxyChecked) return _proxyAvailable;
  try {
    const res = await fetch(PROXY_HEALTH, { method: 'GET', cache: 'no-store' });
    if (res.ok) {
      const j = await res.json();
      if (j?.proxy === true) {
        _proxyAvailable = true;
        _proxyHasKey = !!j.hasKey;
      }
    }
  } catch { /* offline / 404 / not deployed */ }
  _proxyChecked = true;
  return _proxyAvailable;
}

// Force a recheck — useful when settings flip.
function orResetProxyCheck() {
  _proxyChecked = false;
  _proxyAvailable = false;
  _proxyHasKey = false;
}

async function orProxyAvailable() { return _checkProxy(); }
function orProxyHasKey() { return _proxyHasKey; }

const DEFAULT_MODEL = 'anthropic/claude-sonnet-4.5';
const MODEL_CHOICES = [
  { id: 'anthropic/claude-sonnet-4.5', label: 'Claude Sonnet 4.5' },
  { id: 'anthropic/claude-haiku-4.5',  label: 'Claude Haiku 4.5 (cheap & fast)' },
  { id: 'openai/gpt-4o-mini',          label: 'GPT-4o mini' },
  { id: 'openai/gpt-4o',               label: 'GPT-4o' },
  { id: 'google/gemini-2.0-flash-001', label: 'Gemini 2.0 Flash' },
  { id: 'meta-llama/llama-3.3-70b-instruct', label: 'Llama 3.3 70B' },
];

function orGetKey() {
  try { return localStorage.getItem(OR_KEY_STORE) || ''; } catch { return ''; }
}
function orSetKey(k) {
  try { localStorage.setItem(OR_KEY_STORE, k || ''); } catch {}
}
function orGetModel() {
  try { return localStorage.getItem(OR_MODEL_STORE) || DEFAULT_MODEL; } catch { return DEFAULT_MODEL; }
}
function orSetModel(m) {
  try { localStorage.setItem(OR_MODEL_STORE, m || DEFAULT_MODEL); } catch {}
}
// hasKey — true if EITHER the server proxy is available with a key,
// OR the user has stored a direct key in localStorage.
function orHasKey() {
  if (_proxyAvailable && _proxyHasKey) return true;
  return !!orGetKey();
}

// Build the request endpoint + headers. If the proxy is up, route through it
// (no Authorization header — the Function adds it server-side). Otherwise call
// OpenRouter directly with the localStorage key.
async function _buildFetch({ messages, model, temperature, max_tokens, stream }) {
  await _checkProxy();
  const useProxy = _proxyAvailable && _proxyHasKey;

  if (useProxy) {
    return {
      url: PROXY_CHAT,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        model: model || orGetModel(),
        messages, temperature, max_tokens,
        ...(stream ? { stream: true } : {}),
      }),
      useProxy: true,
    };
  }

  const key = orGetKey();
  if (!key) throw new Error('No OpenRouter key. Either deploy with OPENROUTER_API_KEY set, or paste a key in Settings.');
  return {
    url: `${OR_BASE}/chat/completions`,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${key}`,
      'HTTP-Referer': location.origin,
      'X-Title': 'Chopstick',
    },
    body: JSON.stringify({
      model: model || orGetModel(),
      messages, temperature, max_tokens,
      ...(stream ? { stream: true } : {}),
    }),
    useProxy: false,
  };
}

// Non-streaming chat completion. Returns { content, raw } or throws.
async function orChat({ messages, model, temperature = 0.4, max_tokens = 600, signal }) {
  const { url, headers, body, useProxy } = await _buildFetch({ messages, model, temperature, max_tokens, stream: false });
  const res = await fetch(url, { method: 'POST', signal, headers, body });
  if (!res.ok) {
    let detail = '';
    try { detail = (await res.json()).error?.message || (await res.json()).error || ''; } catch {}
    throw new Error(`${useProxy ? 'Proxy' : 'OpenRouter'} ${res.status}: ${detail || res.statusText}`);
  }
  const json = await res.json();
  const content = json?.choices?.[0]?.message?.content || '';
  return { content, raw: json };
}

// Streaming chat. Calls onToken(text) as deltas arrive. Resolves with full text.
async function orChatStream({ messages, model, temperature = 0.5, max_tokens = 800, onToken, signal }) {
  const { url, headers, body, useProxy } = await _buildFetch({ messages, model, temperature, max_tokens, stream: true });
  const res = await fetch(url, { method: 'POST', signal, headers, body });
  if (!res.ok || !res.body) {
    let detail = '';
    try { detail = (await res.json()).error?.message || (await res.json()).error || ''; } catch {}
    throw new Error(`${useProxy ? 'Proxy' : 'OpenRouter'} ${res.status}: ${detail || res.statusText}`);
  }

  const reader = res.body.getReader();
  const dec = new TextDecoder();
  let buf = '';
  let full = '';
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buf += dec.decode(value, { stream: true });
    const lines = buf.split('\n');
    buf = lines.pop() || '';
    for (const line of lines) {
      const trimmed = line.trim();
      if (!trimmed.startsWith('data:')) continue;
      const data = trimmed.slice(5).trim();
      if (data === '[DONE]') return full;
      try {
        const j = JSON.parse(data);
        const delta = j?.choices?.[0]?.delta?.content || '';
        if (delta) {
          full += delta;
          onToken?.(delta, full);
        }
      } catch { /* keep-alive lines etc. */ }
    }
  }
  return full;
}

// ────────────────────────────────────────────────
// Pre-baked prompts for the app.
// ────────────────────────────────────────────────

const TUTOR_SYSTEM = `You are Chopstick — a friendly, encouraging Mandarin Chinese tutor for an English-speaking learner (beginner to intermediate, roughly HSK 1–3).

Rules:
- Reply in MIXED format. Each Chinese sentence on its own line, immediately followed by:
  · pinyin with tone marks
  · a short English translation
- Keep replies SHORT (under ~120 words). One concept at a time.
- If the user writes pinyin without tones, gently add the tones.
- If they ask "how do I say X", give the Chinese, pinyin, and 1 example sentence.
- Don't show traditional characters unless asked; simplified only.
- Be playful and warm. You're a chopstick mascot — feel free to use one or two emojis.
- Never break character or mention you're an AI.`;

const TRANSLATE_GRADER_SYSTEM = `You grade Mandarin translation attempts. The user is shown an English sentence and types a Chinese (simplified) translation.

Output STRICT JSON with this shape and nothing else:
{
  "correct": true | false,
  "score": 0..100,
  "model_answer": "<best natural translation in simplified Chinese>",
  "pinyin": "<pinyin with tone marks for model_answer>",
  "feedback": "<one short encouraging sentence — what was right, what to fix>"
}

Acceptable means the meaning is conveyed and grammar is plausible. Minor word-order or particle issues = correct:true but lower score. Wrong meaning = correct:false.`;

const STORY_SYSTEM = `Write a TINY (4–6 sentence) original story for a Mandarin learner at the level given. Format each sentence on its own line as:
<simplified chinese>
<pinyin with tone marks>
<english>

No preamble. No title. Use only HSK 1–3 vocabulary unless asked for higher level.`;

const VOCAB_SYSTEM = `Generate 6 useful Mandarin vocabulary words on the given topic, at the requested level. Output STRICT JSON only:
{
  "words": [
    { "cn": "<simplified>", "py": "<pinyin>", "en": "<english>", "example": "<short example sentence in chinese>", "example_py": "<pinyin>", "example_en": "<english>" }
  ]
}`;

// Used by the `ai-respond` exercise kind. A Chinese question is shown, learner
// types a Chinese reply, grader checks topical relevance + grammar.
const RESPOND_GRADER_SYSTEM = `You grade a learner's free-form Chinese reply to a Chinese question.

The learner is HSK 1–2 level. They may type pinyin instead of characters — that's fine, treat it as the Chinese reply.

Output STRICT JSON with this shape and nothing else:
{
  "on_topic": true | false,
  "score": 0..100,
  "model_answer": "<one natural reply in simplified Chinese>",
  "pinyin": "<pinyin with tone marks for model_answer>",
  "feedback": "<one short encouraging sentence — what was right, what to fix>",
  "follow_up": "<one short Chinese question to keep the conversation going>"
}

Be GENEROUS — if the learner's reply is topical and intelligible, on_topic is true even with grammar slips. Reserve on_topic:false for replies that don't address the question or that are nonsense.`;

// Used by the role-play screen. Filled with scenario context at runtime.
const ROLEPLAY_SYSTEM = `You are role-playing a Chinese-speaker in a specific scenario. Stay in character at all times — never break the fourth wall, never mention being an AI.

Reply with ONE Chinese line at a time (1–2 sentences max). Format EVERY reply EXACTLY like this, on three separate lines:

<simplified chinese>
<pinyin with tone marks>
((english translation))

Vocab level: HSK 1–2. Use only words a beginner is likely to know. Keep sentences short.

If the learner switches to English, gently reply in Chinese + pinyin + ((english)) anyway — don't switch to English yourself.

If the learner makes a mistake, don't correct them explicitly — just reply naturally as your character would, modeling the correct form in your response.`;

// Used when learner asks for a hint during role-play.
const HINT_SYSTEM = `Suggest ONE short Chinese reply a beginner learner could say in this role-play context. Format on three lines:

<simplified chinese>
<pinyin with tone marks>
((english translation))

Use only HSK 1–2 vocabulary. Keep the reply under 8 words.`;

// Used by the in-lesson "🎧 Hear in Chinese" hint button. Returns STRICT JSON.
const SAMPLE_ANSWER_SYSTEM = `You give a single natural Chinese translation as a hint for a learner who's stuck. Output STRICT JSON only:
{
  "cn": "<simplified chinese answer, 1 short sentence>",
  "py": "<pinyin with tone marks>",
  "en": "<english>"
}
Use simple HSK 1–2 vocabulary. The translation should be natural, not literal.`;

// Used when learner ends a role-play. AI gives a 3-line recap.
const RECAP_SYSTEM = `The role-play just ended. Give a kind, brief recap in this exact format, no preamble:

★ DID WELL: <one phrase, 5–10 English words>
★ REMEMBER: <one Chinese word/phrase + pinyin + english that came up>
★ NEXT: <one Chinese phrase + pinyin + english worth practicing next>`;

// Pre-baked role-play scenarios.
const SCENARIOS = [
  {
    id: 'restaurant',
    title: 'Ordering Noodles',
    cn: '在面店',
    emoji: '🍜',
    color: 'primary',
    persona: 'You are a friendly server at a small noodle shop in Taipei. Greet the customer, ask what they want, take their order, and confirm.',
    opener: 'You walk into the shop. The server smiles and greets you.',
  },
  {
    id: 'taxi',
    title: 'Catching a Taxi',
    cn: '坐出租车',
    emoji: '🚖',
    color: 'yellow',
    persona: 'You are an older taxi driver. The passenger just got in. Ask where they want to go, confirm the address, and chat briefly about the weather or the city.',
    opener: 'You hail a taxi and get in. The driver turns around to ask where to.',
  },
  {
    id: 'meeting-friend',
    title: 'Meeting Someone New',
    cn: '公园偶遇',
    emoji: '👋',
    color: 'cyan',
    persona: 'You are a curious peer your own age, meeting the learner for the first time in a park. Ask their name, where they\'re from, what they do, and what they like.',
    opener: 'You\'re sitting on a park bench. Someone friendly comes over to say hi.',
  },
  {
    id: 'shopping',
    title: 'At the Market',
    cn: '买东西',
    emoji: '🛍️',
    color: 'red',
    persona: 'You are a vendor at a Taiwanese night market selling fruit. Be friendly, name your prices, accept light bargaining (knock 5–10% off if they push), and wish them well.',
    opener: 'You arrive at a stall piled with mangoes and lychees. The vendor calls out a price.',
  },
  {
    id: 'clinic',
    title: 'At the Clinic',
    cn: '看医生',
    emoji: '🏥',
    color: 'green',
    persona: 'You are a patient nurse at a clinic. The patient looks unwell. Ask what hurts, how long, take basic info, and tell them the doctor will see them soon.',
    opener: 'You walk into the clinic. The nurse at the front desk asks how you\'re feeling.',
  },
  {
    id: 'custom',
    title: 'Custom Scenario',
    cn: '自定义',
    emoji: '✨',
    color: 'primary',
    persona: null,
    opener: null,
    custom: true,
  },
];

Object.assign(window, {
  orGetKey, orSetKey, orHasKey, orGetModel, orSetModel,
  orChat, orChatStream,
  orProxyAvailable, orProxyHasKey, orResetProxyCheck,
  MODEL_CHOICES, DEFAULT_MODEL,
  TUTOR_SYSTEM, TRANSLATE_GRADER_SYSTEM, STORY_SYSTEM, VOCAB_SYSTEM,
  RESPOND_GRADER_SYSTEM, ROLEPLAY_SYSTEM, HINT_SYSTEM, RECAP_SYSTEM,
  SAMPLE_ANSWER_SYSTEM, SCENARIOS,
});
