// ── Central write queue ──────────────────────────────────────────────────────
// All offline write operations flow through this API.
// The sync engine drains this queue when the app comes online.
// Browser-only: every function calls getDB() which throws on the server.

import { getDB } from './db'
import { getOfflineConfig } from './registry'
import type { SyncQueueEntry, OperationType } from './types'

export const MAX_RETRY_COUNT = 5

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

/** A QueueEntry is a SyncQueueEntry with the auto-id assigned by Dexie. */
export type QueueEntry = Required<Pick<SyncQueueEntry, 'id'>> & SyncQueueEntry

/** Input required to enqueue a new operation (everything else is set automatically). */
export type EnqueueInput = {
  table:           string
  operation:       OperationType
  payload?:        Record<string, unknown>
  recordId?:       string
  conflictTarget?: string
}

// ── Core API ──────────────────────────────────────────────────────────────────

/**
 * Add a write operation to the queue.
 * Silently skips if the table is not registered with queueWrites: true.
 */
export async function enqueue(input: EnqueueInput): Promise<void> {
  const config = getOfflineConfig(input.table)
  if (!config?.queueWrites) return

  const entry: Omit<SyncQueueEntry, 'id'> = {
    table:             input.table,
    operation:         input.operation,
    payload:           input.payload,
    recordId:          input.recordId,
    conflictTarget:    input.conflictTarget,
    createdAt:         new Date().toISOString(),
    attempts:          0,
    retryCount:        0,
    permanentlyFailed: false,
    status:            'pending',
  }
  await getDB().syncQueue.add(entry as SyncQueueEntry)
}

/**
 * Get the next pending entry (FIFO by createdAt).
 * Returns null if the queue is empty.
 */
export async function dequeue(): Promise<QueueEntry | null> {
  const entries = await getDB().syncQueue
    .where('status').equals('pending')
    .sortBy('createdAt')
  return (entries[0] as QueueEntry | undefined) ?? null
}

/**
 * Mark an entry as completed and remove it from the queue.
 */
export async function markCompleted(autoId: number): Promise<void> {
  await getDB().syncQueue.delete(autoId)
}

/**
 * Mark an entry as failed.
 * Increments retryCount. When retryCount >= MAX_RETRY_COUNT, marks permanentlyFailed.
 */
export async function markFailed(autoId: number, error: string): Promise<void> {
  const entry = await getDB().syncQueue.get(autoId)
  if (!entry) return

  const retryCount        = (entry.retryCount ?? 0) + 1
  const permanentlyFailed = retryCount >= MAX_RETRY_COUNT

  await getDB().syncQueue.update(autoId, {
    status:            'error',
    lastError:         error,
    lastAttemptAt:     new Date().toISOString(),
    attempts:          (entry.attempts ?? 0) + 1,
    retryCount,
    permanentlyFailed,
  })
}

/**
 * Count entries currently in 'pending' status.
 */
export async function getPendingCount(): Promise<number> {
  return getDB().syncQueue.where('status').equals('pending').count()
}

/**
 * Get all pending entries in creation order.
 */
export async function getAllPending(): Promise<QueueEntry[]> {
  const entries = await getDB().syncQueue
    .where('status').equals('pending')
    .sortBy('createdAt')
  return entries as QueueEntry[]
}

/**
 * Delete all completed (synced) entries from the queue.
 * Error and pending entries are untouched.
 */
export async function clearCompleted(): Promise<void> {
  await getDB().syncQueue.where('status').equals('synced').delete()
}

/**
 * Reset all non-permanently-failed error entries back to 'pending'
 * so the sync engine will attempt them again.
 */
export async function retryFailed(): Promise<number> {
  const failed = await getDB().syncQueue
    .where('status').equals('error')
    .toArray()

  const retryable = failed.filter(
    (e) => !(e as SyncQueueEntry & { permanentlyFailed?: boolean }).permanentlyFailed
  )

  for (const entry of retryable) {
    await getDB().syncQueue.update(entry.id!, { status: 'pending' })
  }

  return retryable.length
}

/**
 * Get all permanently failed entries (exceeded MAX_RETRY_COUNT).
 * These require manual intervention.
 */
export async function getPermanentlyFailed(): Promise<QueueEntry[]> {
  const all = await getDB().syncQueue.where('status').equals('error').toArray()
  return all.filter(
    (e) => (e as SyncQueueEntry & { permanentlyFailed?: boolean }).permanentlyFailed
  ) as QueueEntry[]
}

/**
 * Count error entries (failed, non-permanently) for badge display.
 */
export async function getFailedCount(): Promise<number> {
  return getDB().syncQueue.where('status').equals('error').count()
}
