import { db } from "@lila/db";
import { eq, and, isNotNull, sql, ne } from "drizzle-orm";
import { terms, translations, term_glosses } from "@lila/db/schema";
import { alias } from "drizzle-orm/pg-core";

import type {
  SupportedLanguageCode,
  SupportedPos,
  DifficultyLevel,
} from "@lila/shared";

export type TranslationPairRow = {
  termId: string;
  sourceText: string;
  targetText: string;
  sourceGloss: string | null;
};

// Note: difficulty filter is intentionally asymmetric. We filter on the target
// (answer) side only — a word can be A2 in Italian but B1 in English, and what
// matters for the learner is the difficulty of the word they're being taught.

export const getGameTerms = async (
  sourceLanguage: SupportedLanguageCode,
  targetLanguage: SupportedLanguageCode,
  pos: SupportedPos,
  difficulty: DifficultyLevel,
  rounds: number,
): Promise<TranslationPairRow[]> => {
  const sourceTranslations = alias(translations, "source_translations");
  const targetTranslations = alias(translations, "target_translations");

  const rows = await db
    .select({
      termId: terms.id,
      sourceText: sourceTranslations.text,
      targetText: targetTranslations.text,
      sourceGloss: term_glosses.text,
    })
    .from(terms)
    .innerJoin(
      sourceTranslations,
      and(
        eq(sourceTranslations.term_id, terms.id),
        eq(sourceTranslations.language_code, sourceLanguage), // Filter here!
      ),
    )
    .innerJoin(
      targetTranslations,
      and(
        eq(targetTranslations.term_id, terms.id),
        eq(targetTranslations.language_code, targetLanguage), // Filter here!
      ),
    )
    .leftJoin(
      term_glosses,
      and(
        eq(term_glosses.term_id, terms.id),
        eq(term_glosses.language_code, sourceLanguage),
      ),
    )
    .where(
      and(
        eq(terms.pos, pos),
        eq(targetTranslations.difficulty, difficulty),
        isNotNull(sourceTranslations.difficulty), // Good data quality check!
      ),
    )
    // TODO(post-mvp): ORDER BY RANDOM() sorts the entire filtered result set before
    // applying LIMIT, which is fine at current data volumes (low thousands of rows
    // after POS + difficulty filters) but degrades as the terms table grows. Once
    // the database is fully populated and tagged, replace with one of:
    //   - TABLESAMPLE BERNOULLI(n) for approximate sampling on large tables
    //   - Random offset: SELECT ... OFFSET floor(random() * (SELECT count(*) ...))
    //   - Pre-computed random column with a btree index, reshuffled periodically
    // Benchmark first — don't optimise until it actually hurts.
    .orderBy(sql`RANDOM()`)
    .limit(rounds);

  return rows;
};

export const getDistractors = async (
  excludeTermId: string,
  excludeText: string,
  targetLanguage: SupportedLanguageCode,
  pos: SupportedPos,
  difficulty: DifficultyLevel,
  count: number,
): Promise<string[]> => {
  const rows = await db
    .select({ text: translations.text })
    .from(terms)
    .innerJoin(
      translations,
      and(
        eq(translations.term_id, terms.id),
        eq(translations.language_code, targetLanguage),
      ),
    )
    .where(
      and(
        eq(terms.pos, pos),
        eq(translations.difficulty, difficulty),
        ne(terms.id, excludeTermId),
        ne(translations.text, excludeText),
      ),
    )
    // TODO(post-mvp): same ORDER BY RANDOM() concern as getGameTerms — see comment there.
    .orderBy(sql`RANDOM()`)
    .limit(count);

  return rows.map((row) => row.text);
};
