// TranslatedText — renders dynamic English text in the active language.
//
// Usage:  <TranslatedText text={apiResult.summary_text}/>
//
// Behaviour:
//   - English language → return text as-is (no API call).
//   - Other language → return cached translation if present, else fire
//     /api/translate in the background and re-render when it lands.
//   - Cache is keyed by `<lang>::<sha-ish-of-text>` and persists across
//     page reloads (localStorage). Long pages with many dynamic strings
//     still only translate each unique string once per language.

const _CACHE_PREFIX = 'aican-tr-';
const _MAX_LEN = 30000;
const _inflight = new Map(); // dedupe concurrent calls for the same text+lang

function _hash(s) {
  // Tiny non-cryptographic hash so cache keys stay short.
  let h = 0;
  for (let i = 0; i < s.length; i++) { h = ((h << 5) - h + s.charCodeAt(i)) | 0; }
  return Math.abs(h).toString(36);
}

function _cacheKey(code, text) {
  return `${_CACHE_PREFIX}${code}-${_hash(text)}-${text.length}`;
}

function _readCache(code, text) {
  try { return localStorage.getItem(_cacheKey(code, text)); } catch { return null; }
}

function _writeCache(code, text, value) {
  try { localStorage.setItem(_cacheKey(code, text), value); } catch {}
}

async function _fetchTranslation(code, name, text) {
  const key = `${code}::${text}`;
  if (_inflight.has(key)) return _inflight.get(key);
  const p = (async () => {
    try {
      const r = await window.AicanAPI.translate(text.slice(0, _MAX_LEN), code, name);
      const out = r?.translated_text || text;
      _writeCache(code, text, out);
      return out;
    } catch {
      return text; // fail open
    } finally {
      _inflight.delete(key);
    }
  })();
  _inflight.set(key, p);
  return p;
}

// Some LLM fallbacks (Groq reasoning models, DeepSeek) emit a chain of
// thought wrapped in <think>...</think> before the real answer. Strip it
// so the UI never shows the model talking to itself. Server-side does the
// same — this is belt-and-braces in case an old cached response slips through.
function _stripThinking(s) {
  if (!s || typeof s !== 'string') return s;
  let out = s.replace(/<think\b[^>]*>[\s\S]*?<\/think\s*>/gi, '');
  // Truncated mid-thought — drop the open tag + everything until first blank line
  if (/<think/i.test(out)) {
    const last = out.toLowerCase().lastIndexOf('</think>');
    if (last >= 0) out = out.slice(last + '</think>'.length);
    else out = out.replace(/<think\b[^>]*>[\s\S]*?(\n\n|$)/gi, '');
  }
  return out.trim();
}

// React hook — returns the (possibly translated) string.
function useTranslated(text) {
  const lang = window.useLanguage();
  const code = lang.current.code;
  const name = lang.current.name;
  const safe = (text == null ? '' : String(text));

  const initial = React.useMemo(() => {
    if (!safe || code === 'en') return safe;
    return _readCache(code, safe) || safe;
  }, [safe, code]);

  const [val, setVal] = React.useState(initial);

  React.useEffect(() => {
    if (!safe || code === 'en') { setVal(safe); return; }
    const cached = _readCache(code, safe);
    if (cached) { setVal(cached); return; }
    let alive = true;
    _fetchTranslation(code, name, safe).then(translated => {
      if (alive) setVal(translated);
    });
    return () => { alive = false; };
  }, [safe, code, name]);

  return val;
}

// Component wrapper — convenient when you don't need the raw value.
const TranslatedText = ({ text, as = 'span', ...rest }) => {
  const value = useTranslated(text);
  return React.createElement(as, rest, _stripThinking(value));
};

window.TranslatedText = TranslatedText;
window.useTranslated  = useTranslated;
window.stripThinking  = _stripThinking;
