// ── Generic repository factory ───────────────────────────────────────────────
// Creates a local-first CRUD interface for any offline-capable entity.
// Browser-only: all returned functions access IndexedDB via getDB().

import { createClient } from '@/lib/supabase/client'
import { getStoreByName } from './db'
import { getOfflineConfig } from './registry'
import { isCacheStale } from './local-store'
import { enqueue } from './queue'
import { startSync, pullByTableName } from './sync-engine'
import type { SyncStatus, LocalSyncStatus } from './types'

// Re-export LocalSyncStatus so module consumers don't need to import from types
export type { LocalSyncStatus }

// ── Config ────────────────────────────────────────────────────────────────────

export interface RepositoryConfig {
  /** Used for module-level grouping (e.g. 'assessment', 'planning') */
  module: string
  /** Entity type within module (e.g. 'responses', 'assignments') */
  entityType: string
  /** Supabase table name — must match an entry in the offline registry */
  tableName: string
}

// ── Repository interface ──────────────────────────────────────────────────────

export interface Repository<T extends { id: string }> {
  /** Read all records. Local-first: returns cached data if fresh or offline. */
  getAll(filter?: Partial<Record<string, unknown>>): Promise<T[]>
  /** Read a single record. Falls back to remote if not in local store. */
  getById(id: string): Promise<T | null>
  /** Write a new record locally and enqueue for Supabase sync. */
  create(data: Omit<T, 'id' | '_syncStatus'>): Promise<T>
  /** Patch a record locally and enqueue an upsert. */
  update(id: string, patch: Partial<Omit<T, 'id'>>): Promise<T>
  /** Delete locally and enqueue a delete. */
  remove(id: string): Promise<void>
  /** Force-pull the latest data from Supabase into local store. */
  refresh(): Promise<void>
  /** Get the _syncStatus of a specific record. */
  getSyncStatus(id: string): Promise<SyncStatus>
  /** Seed the local store from a pre-fetched array (e.g. from server component props). */
  seed(records: T[]): Promise<void>
}

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

export function createRepository<T extends { id: string }>(
  config: RepositoryConfig,
): Repository<T> {
  const offlineConfig = getOfflineConfig(config.tableName)

  function getStore() {
    if (!offlineConfig) throw new Error(`No offline config for table: ${config.tableName}`)
    return getStoreByName(offlineConfig.storeName)
  }

  function isOnline(): boolean {
    return typeof navigator !== 'undefined' && navigator.onLine
  }

  function triggerSync() {
    if (isOnline()) startSync().catch(() => {})
  }

  return {
    // ── READ ─────────────────────────────────────────────────────────────────

    async getAll(filter?: Partial<Record<string, unknown>>): Promise<T[]> {
      if (!offlineConfig) {
        // No local store — always fetch remote directly
        const supabase = createClient()
        const { data } = await supabase.from(config.tableName).select('*')
        return (data ?? []) as T[]
      }

      const stale  = await isCacheStale(`${config.tableName}:all`)
      const online = isOnline()

      // Pull from remote when stale and online
      if (stale && online) {
        try {
          await pullByTableName(config.tableName)
        } catch {
          // Fall through to local data
        }
      }

      let rows = (await getStore().toArray()) as T[]
      if (filter) {
        rows = rows.filter((row) =>
          Object.entries(filter).every(([k, v]) => (row as Record<string, unknown>)[k] === v),
        )
      }
      return rows
    },

    async getById(id: string): Promise<T | null> {
      if (offlineConfig) {
        const row = await getStore().get(id)
        if (row) return row as unknown as T
      }

      // Remote fallback
      const supabase = createClient()
      const selectStr = offlineConfig?.pullSelect ?? '*'
      const { data } = await supabase
        .from(config.tableName)
        .select(selectStr)
        .eq('id', id)
        .single()

      if (data && offlineConfig) {
        const row = { ...(data as unknown as Record<string, unknown>), _syncStatus: 'synced' as const }
        await getStore().put(row as unknown as Parameters<ReturnType<typeof getStoreByName>['put']>[0])
      }
      return (data as unknown as T | null)
    },

    // ── WRITE ─────────────────────────────────────────────────────────────────

    async create(data: Omit<T, 'id' | '_syncStatus'>): Promise<T> {
      const id     = crypto.randomUUID()
      const record = {
        ...data,
        id,
        updated_at:  new Date().toISOString(),
        _syncStatus: 'pending' as LocalSyncStatus,
      } as unknown as T

      if (offlineConfig) {
        await getStore().put(record as unknown as Parameters<ReturnType<typeof getStoreByName>['put']>[0])
      }

      if (offlineConfig?.queueWrites) {
        await enqueue({ table: config.tableName, operation: 'insert', payload: record as Record<string, unknown> })
        triggerSync()
      } else {
        // Not a write-queued entity — direct remote write
        const supabase = createClient()
        await supabase.from(config.tableName).insert(record)
      }

      return record
    },

    async update(id: string, patch: Partial<Omit<T, 'id'>>): Promise<T> {
      const existing = offlineConfig ? ((await getStore().get(id)) as unknown as T | undefined) : undefined

      const updated = {
        ...(existing ?? {}),
        ...patch,
        id,
        updated_at:  new Date().toISOString(),
        _syncStatus: 'pending' as LocalSyncStatus,
      } as unknown as T

      if (offlineConfig) {
        await getStore().put(updated as unknown as Parameters<ReturnType<typeof getStoreByName>['put']>[0])
      }

      if (offlineConfig?.queueWrites) {
        await enqueue({
          table:          config.tableName,
          operation:      'upsert',
          payload:        updated as Record<string, unknown>,
          recordId:       id,
          conflictTarget: offlineConfig.primaryKey,
        })
        triggerSync()
      } else {
        const supabase = createClient()
        await supabase.from(config.tableName).update(patch as Record<string, unknown>).eq('id', id)
      }

      return updated
    },

    async remove(id: string): Promise<void> {
      if (offlineConfig) {
        await getStore().delete(id)
      }

      if (offlineConfig?.queueWrites) {
        await enqueue({ table: config.tableName, operation: 'delete', recordId: id })
        triggerSync()
      } else {
        const supabase = createClient()
        await supabase.from(config.tableName).delete().eq('id', id)
      }
    },

    // ── SYNC ──────────────────────────────────────────────────────────────────

    async refresh(): Promise<void> {
      await pullByTableName(config.tableName)
    },

    // ── STATUS ────────────────────────────────────────────────────────────────

    async getSyncStatus(id: string): Promise<SyncStatus> {
      if (!offlineConfig) return 'synced'
      const row = await getStore().get(id) as Record<string, unknown> | undefined
      return (row?._syncStatus as SyncStatus) ?? 'synced'
    },

    // ── SEED ──────────────────────────────────────────────────────────────────

    async seed(records: T[]): Promise<void> {
      if (!offlineConfig) return
      const store = getStore()
      const rows = records.map((r) => ({
        ...r,
        _syncStatus: (r as Record<string, unknown>)._syncStatus ?? 'synced',
      }))
      await store.bulkPut(rows as unknown as Parameters<typeof store.bulkPut>[0])
    },
  }
}
