import { atom } from 'jotai';
import { atomWithStorage, createJSONStorage, atomFamily } from 'jotai/utils';
import { normalizeLang } from '../utils/translation/langUtils';

export type CachedTranslation = {
  /** `${eventId}:${normalizedTargetLang}` */
  key: string;
  /** detected source language (normalized) */
  fromLang: string;
  translated: string;
};

const STORAGE_KEY = 'cinny_translation_cache_v1';
const MAX_CACHED = 300;

// Persisted, capped LRU of translations so scrollback / re-render never
// re-translates. Device-local (localStorage), mirroring recentGifs.
const internalAtom = atomWithStorage<CachedTranslation[]>(
  STORAGE_KEY,
  [],
  createJSONStorage(() => localStorage),
  { getOnInit: true },
);

export const translationCacheAtom = atom(
  (get): CachedTranslation[] => get(internalAtom),
  (
    _get,
    set,
    updater: CachedTranslation[] | ((prev: CachedTranslation[]) => CachedTranslation[]),
  ) => {
    set(internalAtom, (prev) => {
      const prevList = Array.isArray(prev) ? prev : [];
      return typeof updater === 'function' ? updater(prevList) : updater;
    });
  },
);

// Small, fast, non-cryptographic string hash — just enough to detect that a
// message's text changed (an edit reuses the same event id), so the key below
// invalidates a stale translation of pre-edit text. Kept bitwise-free (and thus
// within safe-integer range via the modulus) to satisfy the no-bitwise lint.
const HASH_MOD = 2147483647; // 2^31 - 1
const fingerprint = (text: string): string => {
  let h = 0;
  for (let i = 0; i < text.length; i += 1) {
    h = (h * 31 + text.charCodeAt(i)) % HASH_MOD;
  }
  return h.toString(36);
};

export const makeCacheKey = (eventId: string, targetLang: string, text = ''): string =>
  `${eventId}:${normalizeLang(targetLang)}:${fingerprint(text)}`;

/** Look up a cached translation by key (returns undefined if absent). */
export const findTranslation = (
  list: CachedTranslation[],
  key: string,
): CachedTranslation | undefined => list.find((e) => e.key === key);

/**
 * Prepend a translation, de-duping by key (moving an existing one to the front)
 * and capping the list. Pure — returns a new array. Empty key/translated is a
 * no-op.
 */
export const addTranslation = (
  prev: CachedTranslation[],
  entry: CachedTranslation,
  max = MAX_CACHED,
): CachedTranslation[] => {
  if (!entry.key || !entry.translated) return prev;
  const withoutDupe = prev.filter((e) => e.key !== entry.key);
  return [entry, ...withoutDupe].slice(0, max);
};

// Per-event "show translation" toggle, shared between the message menu action
// (which flips it) and the inline body render (which reacts to it). Keyed by
// event id so each message has its own independent state.
export const msgTranslationActiveAtomFamily = atomFamily((_eventId: string) => atom(false));

/**
 * Wipe the persisted translation cache. Called on logout — cached entries are
 * decrypted message plaintext, so they must not survive a session on a shared
 * device (mirrors the search-index wipe).
 */
export const clearTranslationCache = (): void => {
  try {
    localStorage.removeItem(STORAGE_KEY);
  } catch {
    /* localStorage unavailable — nothing to clear */
  }
};
