// Blob cache for decrypted attachments. Stores the Blob body in OPFS
// (Origin Private File System — always sandbox-scoped to the origin, no
// user prompt) keyed by attachment id. Avoids re-downloading + re-decrypting
// the same blob on every scroll-into-view.
//
// Tauri WebKit + WebView2 both support OPFS as of the versions targeted by
// tauri 2. On unsupported hosts the cache degrades to a no-op and callers
// fall back to the fetch path.

const DIR_NAME = 'attachments';
const DEFAULT_TTL_MS = 7 * 24 * 3600 * 1000;

interface OpfsRoot {
  getDirectoryHandle: (
    name: string,
    opts?: { create?: boolean },
  ) => Promise<OpfsDir>;
}

interface OpfsDir {
  getFileHandle: (
    name: string,
    opts?: { create?: boolean },
  ) => Promise<OpfsFile>;
  removeEntry: (name: string, opts?: { recursive?: boolean }) => Promise<void>;
  entries?: () => AsyncIterableIterator<[string, OpfsFile]>;
}

interface OpfsFile {
  getFile: () => Promise<File>;
  createWritable: () => Promise<{
    write: (data: ArrayBuffer | Blob) => Promise<void>;
    close: () => Promise<void>;
  }>;
}

let dirPromise: Promise<OpfsDir | null> | null = null;

async function getDir(): Promise<OpfsDir | null> {
  if (!dirPromise) {
    dirPromise = (async () => {
      const storage = (navigator as unknown as { storage?: { getDirectory?: () => Promise<OpfsRoot> } }).storage;
      const getDirectory = storage?.getDirectory;
      if (!storage || !getDirectory) return null;
      try {
        const root = await getDirectory.call(storage);
        return await root.getDirectoryHandle(DIR_NAME, { create: true });
      } catch (err: unknown) {
        console.warn('opfs attachment cache init failed', err);
        return null;
      }
    })();
  }
  return dirPromise;
}

function safeName(id: string): string {
  // OPFS file names can't contain slashes; attachment ids are UUIDs so this
  // is mostly defensive.
  return id.replace(/[^A-Za-z0-9_.-]/g, '_') + '.bin';
}

export async function getCachedAttachment(id: string): Promise<Blob | null> {
  const dir = await getDir();
  if (!dir) return null;
  try {
    const handle = await dir.getFileHandle(safeName(id), { create: false });
    const file = await handle.getFile();
    // Evict stale entries lazily — if older than TTL, drop and miss.
    if (Date.now() - file.lastModified > DEFAULT_TTL_MS) {
      await dir.removeEntry(safeName(id)).catch(() => undefined);
      return null;
    }
    return file;
  } catch {
    // File doesn't exist → cache miss.
    return null;
  }
}

export async function putCachedAttachment(id: string, blob: Blob): Promise<void> {
  const dir = await getDir();
  if (!dir) return;
  try {
    const handle = await dir.getFileHandle(safeName(id), { create: true });
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
  } catch (err: unknown) {
    console.warn('putCachedAttachment failed', err);
  }
}

export async function evictCachedAttachment(id: string): Promise<void> {
  const dir = await getDir();
  if (!dir) return;
  try {
    await dir.removeEntry(safeName(id));
  } catch {
    /* ignore — probably never existed */
  }
}
