/**
 * Persistent offline retry queue for browser and React Native.
 *
 * When an API call fails (offline, timeout, etc.), the operation is
 * queued in configurable storage and retried on the next flush.
 *
 * No Node.js, React, or React Native dependencies.
 *
 * @example
 * ```ts
 * import { createOfflineQueue } from '@bytelyst/offline-queue';
 *
 * const queue = createOfflineQueue({
 *   storageKey: 'nomgap-offline-queue',
 *   storage: mmkvStorage, // or localStorage
 * });
 *
 * // On API failure:
 * queue.enqueue({ id: 'sess-1', action: 'create', path: '/sessions', payload: { ... } });
 *
 * // On app foreground / auth success:
 * const result = await queue.flush(async (action, path, payload) => {
 *   await apiClient.request(action === 'create' ? 'POST' : 'PUT', path, payload);
 * });
 * ```
 */

// ── Types ────────────────────────────────────────────────────

export interface QueueStorage {
  getItem(key: string): string | null;
  setItem(key: string, value: string): void;
}

export interface OfflineQueueConfig {
  /** Storage key for persisting the queue. */
  storageKey: string;

  /** Storage adapter (localStorage, MMKV, AsyncStorage wrapper, etc.). */
  storage: QueueStorage;

  /** Maximum retry attempts per item. Default: 5. */
  maxRetries?: number;

  /** Maximum queue size. Oldest items are dropped when exceeded. Default: 50. */
  maxQueueSize?: number;
}

export interface QueueItem {
  id: string;
  action: string;
  path: string;
  payload: Record<string, unknown>;
  enqueuedAt: number;
  retryCount: number;
}

export interface FlushResult {
  flushed: number;
  failed: number;
}

export interface OfflineQueue {
  /** Enqueue a failed operation for later retry. Replaces existing entry with same id + action. */
  enqueue(item: {
    id: string;
    action: string;
    path: string;
    payload: Record<string, unknown>;
  }): void;

  /** Flush the queue — retry all pending items via the provided executor. */
  flush(
    executor: (action: string, path: string, payload: Record<string, unknown>) => Promise<void>
  ): Promise<FlushResult>;

  /** Get current queue length. */
  length(): number;

  /** Clear the entire queue. */
  clear(): void;
}

// ── Factory ──────────────────────────────────────────────────

export function createOfflineQueue(config: OfflineQueueConfig): OfflineQueue {
  const { storageKey, storage, maxRetries = 5, maxQueueSize = 50 } = config;

  function loadQueue(): QueueItem[] {
    try {
      const raw = storage.getItem(storageKey);
      if (!raw) return [];
      return JSON.parse(raw) as QueueItem[];
    } catch {
      return [];
    }
  }

  function saveQueue(queue: QueueItem[]): void {
    try {
      storage.setItem(storageKey, JSON.stringify(queue));
    } catch {
      // Storage unavailable
    }
  }

  function enqueue(item: {
    id: string;
    action: string;
    path: string;
    payload: Record<string, unknown>;
  }): void {
    const queue = loadQueue();

    // Replace existing entry for same entity + action
    const filtered = queue.filter(q => !(q.id === item.id && q.action === item.action));

    // Cap queue size
    if (filtered.length >= maxQueueSize) {
      filtered.shift();
    }

    filtered.push({
      ...item,
      enqueuedAt: Date.now(),
      retryCount: 0,
    });

    saveQueue(filtered);
  }

  async function flush(
    executor: (action: string, path: string, payload: Record<string, unknown>) => Promise<void>
  ): Promise<FlushResult> {
    const queue = loadQueue();
    if (queue.length === 0) return { flushed: 0, failed: 0 };

    let flushed = 0;
    const remaining: QueueItem[] = [];

    for (const item of queue) {
      try {
        await executor(item.action, item.path, item.payload);
        flushed++;
      } catch {
        if (item.retryCount + 1 < maxRetries) {
          remaining.push({ ...item, retryCount: item.retryCount + 1 });
        }
        // else: silently drop — too many retries
      }
    }

    saveQueue(remaining);
    return { flushed, failed: remaining.length };
  }

  function length(): number {
    return loadQueue().length;
  }

  function clear(): void {
    saveQueue([]);
  }

  return { enqueue, flush, length, clear };
}
