/** Small serialized JSON-file cache helper for Expo FileSystem. */
import * as FileSystem from 'expo-file-system';

const CACHE_DIRECTORY_NAME = 'pixiesprout-garden';

type CacheDecoder<T> = (value: unknown) => T | null;

export function createJsonFileCache<T>(
  fileName: string,
  fallback: () => T,
  decode: CacheDecoder<T>,
): {
  load: () => Promise<T>;
  save: (value: T) => Promise<void>;
} {
  let writeChain: Promise<void> = Promise.resolve();

  const directoryPath = () => {
    const root = FileSystem.documentDirectory;
    if (!root) return null;
    const normalizedRoot = root.endsWith('/') ? root : `${root}/`;
    return `${normalizedRoot}${CACHE_DIRECTORY_NAME}/`;
  };
  const filePath = () => {
    const directory = directoryPath();
    return directory ? `${directory}${fileName}` : null;
  };

  const load = async (): Promise<T> => {
    try {
      // Preserve read-after-write ordering for callers that save and immediately
      // hydrate in the same process. Failed writes never poison the chain.
      await writeChain.catch(() => undefined);
      const path = filePath();
      if (!path) return fallback();
      const info = await FileSystem.getInfoAsync(path);
      if (!info.exists) return fallback();
      const raw = await FileSystem.readAsStringAsync(path);
      const decoded = decode(JSON.parse(raw) as unknown);
      return decoded ?? fallback();
    } catch {
      return fallback();
    }
  };

  const save = (value: T): Promise<void> => {
    // Serialize immediately so a queued write cannot observe later caller
    // mutation of the array/object that was passed to this function.
    let serialized: string;
    try {
      const encoded = JSON.stringify(value);
      if (typeof encoded !== 'string') return Promise.resolve();
      serialized = encoded;
    } catch {
      return Promise.resolve();
    }

    const run = writeChain.then(async () => {
      try {
        const directory = directoryPath();
        const path = filePath();
        if (!directory || !path) return;
        await FileSystem.makeDirectoryAsync(directory, { intermediates: true }).catch(() => undefined);
        await FileSystem.writeAsStringAsync(path, serialized);
      } catch {
        // Caches are best-effort; a failed write must never block care flows.
      }
    });
    writeChain = run.catch(() => undefined);
    return run;
  };

  return { load, save };
}
