/**
 * Language registry queries.
 *
 * All functions accept a DrizzleClient (outer db or transaction context) as
 * their first argument — matching the project query-layer convention.
 *
 * Default-language invariant: exactly one row must have isDefault=true at all
 * times. upsertLanguage() enforces this atomically inside a transaction.
 *
 * No caching here — that is Task 12 (languages/cache.ts).
 */

import { eq } from 'drizzle-orm';
import type { DrizzleDb } from '@/server/db/client.js';
import type { DrizzleClient } from '@/server/db/client.js';
import { languages, type Language, type NewLanguage } from '@/server/db/schema.js';
import {
  upsertLanguage as persistLanguage,
  setLanguageActive as persistLanguageActive,
} from '@/server/db/queries/language-writes.js';

// ─── Read queries ─────────────────────────────────────────────────────────────

/**
 * Return ALL languages (including inactive) ordered by sortOrder.
 * Used by admin endpoints that need to see and manage the full list.
 */
export async function listLanguages(db: DrizzleClient): Promise<Language[]> {
  return db.select().from(languages).orderBy(languages.sortOrder);
}

/**
 * Return all active languages ordered by sortOrder.
 */
export async function getActiveLanguages(db: DrizzleClient): Promise<Language[]> {
  return db
    .select()
    .from(languages)
    .where(eq(languages.isActive, true))
    .orderBy(languages.sortOrder);
}

/**
 * Return the single default language.
 * Throws if the invariant is violated (no default row found).
 */
export async function getDefaultLanguage(db: DrizzleClient): Promise<Language> {
  const rows = await db.select().from(languages).where(eq(languages.isDefault, true)).limit(1);
  if (!rows[0]) {
    throw new Error('No default language configured — invariant violated');
  }
  return rows[0];
}

/**
 * Return a single language by ISO code, or null if not found.
 */
export async function getLanguage(db: DrizzleClient, code: string): Promise<Language | null> {
  const rows = await db.select().from(languages).where(eq(languages.code, code)).limit(1);
  return rows[0] ?? null;
}

// ─── Write queries ────────────────────────────────────────────────────────────

/**
 * Insert or update a language row.
 *
 * When input.isDefault is true the function:
 *   1. Demotes ALL existing default rows to isDefault=false
 *   2. Upserts the new row with isDefault=true
 *   3. Verifies the invariant — at least one default must exist after write
 *
 * All three steps run inside a single db.transaction() to close the TOCTOU
 * window. Outer db must be DrizzleDb (supports .transaction()); pass the outer
 * db, not an already-open transaction context.
 *
 * Conflict target: languages.code (primary key).
 */
export async function upsertLanguage(db: DrizzleDb, input: NewLanguage): Promise<Language> {
  return persistLanguage(db, input);
}

/**
 * Toggle isActive on a language by code.
 * Does not validate the default invariant — callers should not deactivate the
 * default language without first promoting another.
 */
export async function setActive(db: DrizzleClient, code: string, isActive: boolean): Promise<void> {
  await persistLanguageActive(db, code, isActive);
}
