"use client";

import { useCallback, useEffect, useRef } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { useSyncQueueStore } from "@/lib/stores/sync-queue.store";
import {
  enqueueOutboxOp,
  getAllQueueItems,
  getOutboxCount,
  getQueueCount,
  removeQueueItem,
} from "@/lib/offline/offline-db";
import { drainOutbox } from "@/lib/offline/outbox";

function newId(prefix: string): string {
  if (prefix === "idem" && typeof crypto !== "undefined" && "randomUUID" in crypto) {
    return crypto.randomUUID();
  }
  return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
}

/**
 * One-time migration of any items left in the legacy POS `order_queue` into the
 * generic outbox, so orders queued before this release still sync. Best-effort.
 */
async function migrateLegacyQueue(): Promise<void> {
  let legacy: Awaited<ReturnType<typeof getAllQueueItems>> = [];
  try {
    legacy = await getAllQueueItems();
  } catch {
    return;
  }
  for (const item of legacy) {
    try {
      if (item.type === "create_order") {
        const { cafeId, body } = item.payload as { cafeId: string; body: unknown };
        await enqueueOutboxOp({
          id: newId("op"),
          idempotencyKey: newId("idem"),
          method: "POST",
          url: `/api/cafes/${cafeId}/orders`,
          body,
          entityType: "order",
          idField: "id",
          createdAt: Date.parse(item.createdAt) || Date.now(),
        });
      } else if (item.type === "add_items") {
        const { cafeId, orderId, body } = item.payload as {
          cafeId: string;
          orderId: string;
          body: unknown;
        };
        await enqueueOutboxOp({
          id: newId("op"),
          idempotencyKey: newId("idem"),
          method: "POST",
          url: `/api/cafes/${cafeId}/orders/${orderId}/items`,
          body,
          entityType: "order_items",
          createdAt: Date.parse(item.createdAt) || Date.now(),
        });
      }
      await removeQueueItem(item.id);
    } catch {
      // leave the legacy item in place; we'll try again next mount
    }
  }
}

/**
 * Mount once in the app shell to:
 * - migrate any legacy queued orders into the outbox,
 * - keep the pending-count badge and online flag in sync,
 * - drain the outbox when back online or the tab regains focus,
 * - refresh server data once writes have synced.
 */
export function useOfflineSync() {
  const { setQueueCount, setSyncing, setOnline } = useSyncQueueStore();
  const queryClient = useQueryClient();
  const syncLock = useRef(false);

  const refreshCount = useCallback(async () => {
    const n = (await getOutboxCount()) + (await getQueueCount());
    setQueueCount(n);
    return n;
  }, [setQueueCount]);

  const syncQueue = useCallback(async () => {
    if (syncLock.current) return;
    if (typeof navigator !== "undefined" && !navigator.onLine) return;

    syncLock.current = true;
    setSyncing(true);
    try {
      const result = await drainOutbox();
      if (result.sent > 0) {
        // Replace optimistic local data with the authoritative server state.
        await queryClient.invalidateQueries();
      }
    } finally {
      syncLock.current = false;
      setSyncing(false);
      await refreshCount();
    }
  }, [refreshCount, setSyncing, queryClient]);

  useEffect(() => {
    // Ask the browser to keep our IndexedDB (outbox + cache) from being evicted
    // under storage pressure, so unsynced writes survive.
    if (typeof navigator !== "undefined" && navigator.storage?.persist) {
      void navigator.storage.persisted().then((granted) => {
        if (!granted) void navigator.storage.persist();
      });
    }

    void (async () => {
      await migrateLegacyQueue();
      await refreshCount();
      // Drain anything pending if we mounted already online.
      if (typeof navigator === "undefined" || navigator.onLine) void syncQueue();
    })();

    const handleOnline = () => {
      setOnline(true);
      void syncQueue();
    };
    const handleOffline = () => setOnline(false);

    setOnline(typeof navigator !== "undefined" ? navigator.onLine : true);
    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);

    const handleVisibility = () => {
      if (document.visibilityState === "visible" && navigator.onLine) {
        void syncQueue();
      }
    };
    document.addEventListener("visibilitychange", handleVisibility);

    return () => {
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
      document.removeEventListener("visibilitychange", handleVisibility);
    };
  }, [syncQueue, setOnline, refreshCount]);

  return { syncQueue, refreshCount };
}
