/**
 * Translation Memory (TM) query module.
 *
 * All functions accept `db: DrizzleClient` as first argument so they can be
 * called from both outer DB contexts and inside `db.transaction()` callbacks.
 *
 * Functions:
 *   tmLookupBatch  — batch-load TM rows by composite PK; touches hits in-place.
 *   tmInsertBatch  — bulk upsert with ON CONFLICT DO UPDATE to bump usageCount.
 *   tmTouch        — increment usageCount + refresh lastUsedAt for a single row.
 *   tmEvict        — DELETE stale/low-use rows; returns deleted count.
 */

import { and, eq, inArray } from 'drizzle-orm';
import type { DrizzleClient } from '@/server/db/client';
import { translationMemory } from '@/server/db/schema';
import { hashUnit } from './normalize';
import {
  touchTranslationMemoryBatch,
  upsertTranslationMemory,
  touchTranslationMemory,
  evictTranslationMemory,
} from '@/server/db/queries/translation/memory.js';

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

export interface TmHit {
  sourceHash: string;
  sourceText: string;
  translatedText: string;
  modelId: string;
  qualityScore: number | null;
}

export interface TmInsertRow {
  sourceHash: string;
  sourceText: string;
  translatedText: string;
  modelId: string;
  sourceLocale: string;
  targetLocale: string;
}

export interface TmEvictOpts {
  /** Delete rows not used for this many days. */
  olderThanDays?: number;
  /** Delete rows with usageCount <= this value (combined with olderThanDays via AND). */
  minUsageCount?: number;
}

// ---------------------------------------------------------------------------
// tmLookupBatch
// ---------------------------------------------------------------------------

/**
 * Batch-look up TM rows for an array of {text, srcLocale, tgtLocale} items.
 *
 * Returns a Map keyed by the input array *index* (0-based) for hits only.
 * Rows with qualityScore < 0 (user-reported bad) are excluded.
 * All hits are touched (lastUsedAt + usageCount) in the same call.
 */
export async function tmLookupBatch(
  db: DrizzleClient,
  items: Array<{ text: string; srcLocale: string; tgtLocale: string }>,
): Promise<Map<number, TmHit>> {
  if (items.length === 0) return new Map();

  // Group items by (srcLocale, tgtLocale) pair to issue minimal queries.
  // Build hash → original index map for result assembly.
  type PairKey = string;
  const pairGroups = new Map<
    PairKey,
    { srcLocale: string; tgtLocale: string; hashes: string[]; indices: number[] }
  >();

  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    if (!item) continue;
    const { text, srcLocale, tgtLocale } = item;
    const hash = await hashUnit(text, srcLocale);
    const key: PairKey = `${srcLocale}\0${tgtLocale}`;
    if (!pairGroups.has(key)) {
      pairGroups.set(key, { srcLocale, tgtLocale, hashes: [], indices: [] });
    }
    const g = pairGroups.get(key)!;
    g.hashes.push(hash);
    g.indices.push(i);
  }

  const result = new Map<number, TmHit>();
  const touchKeys: Array<{ sourceHash: string; srcLocale: string; tgtLocale: string }> = [];

  for (const { srcLocale, tgtLocale, hashes, indices } of pairGroups.values()) {
    const rows = await db
      .select()
      .from(translationMemory)
      .where(
        and(
          inArray(translationMemory.sourceHash, hashes),
          eq(translationMemory.sourceLocale, srcLocale),
          eq(translationMemory.targetLocale, tgtLocale),
        ),
      );

    // Build hash → row map; skip rows marked bad (qualityScore < 0).
    const hashToRow = new Map<string, (typeof rows)[number]>();
    for (const row of rows) {
      if (row.qualityScore != null && row.qualityScore < 0) continue;
      hashToRow.set(row.sourceHash, row);
    }

    for (let j = 0; j < hashes.length; j++) {
      const hash = hashes[j];
      const idx = indices[j];
      if (hash == null || idx == null) continue;
      const row = hashToRow.get(hash);
      if (!row) continue;
      result.set(idx, {
        sourceHash: row.sourceHash,
        sourceText: row.sourceText,
        translatedText: row.translatedText,
        modelId: row.modelId,
        qualityScore: row.qualityScore,
      });
      touchKeys.push({ sourceHash: row.sourceHash, srcLocale, tgtLocale });
    }
  }

  // Touch all hits in-place (single UPDATE per locale-pair group).
  if (touchKeys.length > 0) {
    // Group touch keys by pair so we can issue one UPDATE per pair.
    const touchGroups = new Map<
      PairKey,
      { srcLocale: string; tgtLocale: string; hashes: string[] }
    >();
    for (const { sourceHash, srcLocale, tgtLocale } of touchKeys) {
      const key: PairKey = `${srcLocale}\0${tgtLocale}`;
      if (!touchGroups.has(key)) {
        touchGroups.set(key, { srcLocale, tgtLocale, hashes: [] });
      }
      touchGroups.get(key)!.hashes.push(sourceHash);
    }
    for (const { srcLocale, tgtLocale, hashes } of touchGroups.values()) {
      await touchTranslationMemoryBatch(db, { sourceHash: hashes, srcLocale, tgtLocale });
    }
  }

  return result;
}

// ---------------------------------------------------------------------------
// tmInsertBatch
// ---------------------------------------------------------------------------

/**
 * Bulk-insert TM rows.  On conflict (composite PK) bumps usageCount and
 * refreshes lastUsedAt — preserving translatedText and modelId of the
 * existing winner.
 */
export async function tmInsertBatch(db: DrizzleClient, rows: TmInsertRow[]): Promise<void> {
  if (rows.length === 0) return;
  await upsertTranslationMemory(db, rows);
}

// ---------------------------------------------------------------------------
// tmTouch
// ---------------------------------------------------------------------------

/**
 * Increment usageCount and refresh lastUsedAt for a single TM row identified
 * by its composite PK.  No-ops silently when the row does not exist.
 */
export async function tmTouch(
  db: DrizzleClient,
  sourceHash: string,
  srcLocale: string,
  tgtLocale: string,
): Promise<void> {
  await touchTranslationMemory(db, { sourceHash, srcLocale, tgtLocale });
}

// ---------------------------------------------------------------------------
// tmEvict
// ---------------------------------------------------------------------------

/**
 * Delete stale TM rows matching ALL supplied predicates (AND logic).
 *
 * @param opts.olderThanDays  — row's lastUsedAt older than N days
 * @param opts.minUsageCount  — row's usageCount <= N
 *
 * At least one predicate must be provided; returns { deleted: number }.
 */
export async function tmEvict(db: DrizzleClient, opts: TmEvictOpts): Promise<{ deleted: number }> {
  return evictTranslationMemory(db, opts);
}
