import i18n from '../i18n';
import { isNativePlatform } from './capacitor';

// Mirrors the Java-side LANG_KEY + SupportedLang clamp in PushStrings.java
// and the normalizeLang() in sw.ts. Keep the three implementations in sync:
// supported set = {en, ru}, anything else collapses to en (i18n.ts sets
// fallbackLng: 'en'). Normalising on the WRITE side means readers never see
// an unsupported tag — for a user whose device locale is e.g. 'fr', the
// resource-qualifier picker would otherwise land on values/ (English) while
// i18next on the main thread rendered some other default; we make both
// surfaces agree on English as the lingua franca fallback.
const PREFS_LANG_KEY = 'vojo.appLanguage';

type SupportedLang = 'en' | 'ru';

const normalize = (raw: string | undefined | null): SupportedLang => {
  if (!raw) return 'en';
  const lower = raw.toLowerCase();
  if (lower.startsWith('ru')) return 'ru';
  return 'en';
};

const writeNativePref = async (lang: SupportedLang): Promise<void> => {
  if (!isNativePlatform()) return;
  try {
    const { Preferences } = await import('@capacitor/preferences');
    await Preferences.set({ key: PREFS_LANG_KEY, value: lang });
  } catch {
    /* non-fatal — PushStrings falls back to device locale, normalised
       to {en, ru} with en as the default for anything else */
  }
};

// Post to the active Service Worker so its push-fallback loader picks
// the same language the main app is rendering. The SW side persists in
// IndexedDB for cold wake; see sw.ts setLanguage handler.
//
// First-load race: on the very first page visit after SW install,
// `navigator.serviceWorker.controller` is null — the SW has just
// registered and hasn't taken over yet. A direct postMessage would
// silently drop. We keep the latest language in `pendingSwLang` and
// re-attempt when (a) the registration becomes ready and (b) on every
// `controllerchange` event. Both paths no-op once the controller is
// set and the post has already landed, so duplicates are cheap.
let pendingSwLang: SupportedLang | undefined;
let swControllerWatchInstalled = false;

const trySendSwLang = (): void => {
  if (pendingSwLang === undefined) return;
  if (typeof navigator === 'undefined') return;
  const sw = navigator.serviceWorker;
  if (!sw?.controller) return;
  try {
    sw.controller.postMessage({ type: 'setLanguage', lang: pendingSwLang });
  } catch {
    /* ignore — next languageChanged / controllerchange re-posts */
  }
};

const installSwControllerWatch = (): void => {
  if (swControllerWatchInstalled) return;
  if (typeof navigator === 'undefined') return;
  const sw = navigator.serviceWorker;
  if (!sw) return;
  swControllerWatchInstalled = true;
  // `ready` resolves when there is an active registration — usually
  // around install/activate. Our SW calls `self.clients.claim()` in
  // its activate handler, so shortly after `ready` the page gains a
  // controller and `controllerchange` fires; we listen to both so the
  // first-install race window is covered regardless of which fires
  // first.
  sw.ready.then(trySendSwLang).catch(() => undefined);
  sw.addEventListener('controllerchange', trySendSwLang);
};

const postToServiceWorker = (lang: SupportedLang): void => {
  pendingSwLang = lang;
  trySendSwLang();
};

const broadcast = (raw: string | undefined | null): void => {
  const lang = normalize(raw);
  writeNativePref(lang).catch(() => undefined);
  postToServiceWorker(lang);
};

// One-way JS → {Android FCM, web SW} bridge for the in-app i18next
// language. Idempotent in practice: the i18next listener API dedupes
// equal-valued changes. Intended to be invoked exactly once from App
// bootstrap; we never detach the listener for the process lifetime.
export const installPushLanguageBridge = (): void => {
  installSwControllerWatch();
  // `resolvedLanguage` is what i18next actually serves — it already
  // applies `fallbackLng`/`supportedLngs` so a detector result of `fr`
  // arrives here as `en`. Falling back to `language` covers the narrow
  // window before the first `languageChanged` where `resolvedLanguage`
  // may still be undefined (i18next API guidance).
  const initial = i18n.resolvedLanguage ?? i18n.language;
  if (initial) broadcast(initial);
  i18n.on('languageChanged', (lng) => {
    broadcast(lng);
  });
};
