"use client";

// Thin promise wrapper around the browser's native IndexedDB — no added
// dependency. The service worker (public/sw.js) opens the same database by
// name/version/store names using the raw API directly (it can't import this
// module, since it's registered as a classic, non-module script), so any
// change here must be mirrored there.
const DB_NAME = "epicure-offline";
const DB_VERSION = 1;
export const SAVED_RECIPES_STORE = "savedRecipes";
export const PENDING_ACTIONS_STORE = "pendingActions";

export type SavedRecipe = { id: string; title: string; savedAt: string };
export type PendingAction = {
  id: string;
  method: string;
  url: string;
  body: unknown;
  description: string;
  createdAt: string;
};

function openDb(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const req = indexedDB.open(DB_NAME, DB_VERSION);
    req.onupgradeneeded = () => {
      const db = req.result;
      if (!db.objectStoreNames.contains(SAVED_RECIPES_STORE)) {
        db.createObjectStore(SAVED_RECIPES_STORE, { keyPath: "id" });
      }
      if (!db.objectStoreNames.contains(PENDING_ACTIONS_STORE)) {
        db.createObjectStore(PENDING_ACTIONS_STORE, { keyPath: "id" });
      }
    };
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}

async function withStore<T>(store: string, mode: IDBTransactionMode, fn: (s: IDBObjectStore) => T): Promise<T> {
  const db = await openDb();
  return new Promise((resolve, reject) => {
    const tx = db.transaction(store, mode);
    const result = fn(tx.objectStore(store));
    tx.oncomplete = () => resolve(result);
    tx.onerror = () => reject(tx.error);
  });
}

export async function saveRecipeOffline(id: string, title: string): Promise<void> {
  await withStore(SAVED_RECIPES_STORE, "readwrite", (s) => {
    s.put({ id, title, savedAt: new Date().toISOString() });
  });
}

export async function unsaveRecipeOffline(id: string): Promise<void> {
  await withStore(SAVED_RECIPES_STORE, "readwrite", (s) => {
    s.delete(id);
  });
}

export async function isRecipeSavedOffline(id: string): Promise<boolean> {
  const record = await withStore<SavedRecipe | undefined>(SAVED_RECIPES_STORE, "readonly", (s) => {
    let result: SavedRecipe | undefined;
    s.get(id).onsuccess = (e) => { result = (e.target as IDBRequest<SavedRecipe | undefined>).result; };
    return result;
  });
  // The transaction's oncomplete fires after the get's onsuccess callback
  // runs, so `record` is populated by the time withStore resolves.
  return record !== undefined;
}

export async function getSavedRecipesOffline(): Promise<SavedRecipe[]> {
  return withStore<SavedRecipe[]>(SAVED_RECIPES_STORE, "readonly", (s) => {
    let result: SavedRecipe[] = [];
    s.getAll().onsuccess = (e) => { result = (e.target as IDBRequest<SavedRecipe[]>).result; };
    return result;
  });
}

export async function enqueuePendingAction(action: Omit<PendingAction, "id" | "createdAt">): Promise<void> {
  await withStore(PENDING_ACTIONS_STORE, "readwrite", (s) => {
    s.put({ ...action, id: crypto.randomUUID(), createdAt: new Date().toISOString() });
  });
}

export async function getPendingActions(): Promise<PendingAction[]> {
  const actions = await withStore<PendingAction[]>(PENDING_ACTIONS_STORE, "readonly", (s) => {
    let result: PendingAction[] = [];
    s.getAll().onsuccess = (e) => { result = (e.target as IDBRequest<PendingAction[]>).result; };
    return result;
  });
  return actions.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
}

export async function deletePendingAction(id: string): Promise<void> {
  await withStore(PENDING_ACTIONS_STORE, "readwrite", (s) => {
    s.delete(id);
  });
}
