import { sql } from './sql.js';

import { formatEmbeddingForPg } from './card-embed.js';

export const MATCH_THRESHOLD = 0.82;
export const DISAMBIGUATION_THRESHOLD = 0.58;

function mapCardRow(card) {
  return {
    id: card.id,
    name: card.name,
    set_name: card.set_name,
    set_code: card.set_code,
    card_number: card.card_number,
    game: card.game,
    rarity: card.rarity,
    image_url: card.image_url,
    card_type: card.card_type,
    mana_cost: card.mana_cost,
    hp: card.power,
    similarity: card.sim,
  };
}

/**
 * Pure ranking logic for visual kNN candidates (unit-tested without DB).
 */
export function resolveVisualCandidates(candidates) {
  if (!candidates?.length) {
    return {
      type: 'escalate',
      reason: `No catalog match above ${DISAMBIGUATION_THRESHOLD} visual similarity`,
    };
  }

  const top = candidates[0];
  const runnerUp = candidates[1];
  const clearWinner =
    top.sim >= MATCH_THRESHOLD && (!runnerUp || top.sim - runnerUp.sim >= 0.06);

  if (clearWinner) {
    return {
      type: 'matched',
      card: top,
      similarity: top.sim,
      message: `Matched "${top.name}" via visual search (${Math.round(top.sim * 100)}% similar)`,
    };
  }

  return {
    type: 'disambiguation',
    matches: candidates.slice(0, 5).map(mapCardRow),
    similarity: top.sim,
    message: `Found ${candidates.length} visually similar printings. Select the correct one.`,
  };
}

async function querySimilarCards(vectorLiteral, game) {
  if (game) {
    return sql`
      SELECT id, name, set_name, set_code, card_number, game, rarity, image_url, card_type, mana_cost, power,
             1 - (embedding <=> ${vectorLiteral}::vector) AS sim
      FROM cards
      WHERE embedding IS NOT NULL
        AND game = ${game}
        AND 1 - (embedding <=> ${vectorLiteral}::vector) > ${DISAMBIGUATION_THRESHOLD - 0.05}
      ORDER BY embedding <=> ${vectorLiteral}::vector
      LIMIT 8
    `;
  }

  return sql`
    SELECT id, name, set_name, set_code, card_number, game, rarity, image_url, card_type, mana_cost, power,
           1 - (embedding <=> ${vectorLiteral}::vector) AS sim
    FROM cards
    WHERE embedding IS NOT NULL
      AND 1 - (embedding <=> ${vectorLiteral}::vector) > ${DISAMBIGUATION_THRESHOLD - 0.05}
    ORDER BY embedding <=> ${vectorLiteral}::vector
    LIMIT 8
  `;
}

/**
 * True when at least one catalog row has a visual embedding index entry.
 */
export async function catalogHasEmbeddings() {
  const indexed = await sql`
    SELECT EXISTS(
      SELECT 1 FROM cards WHERE embedding IS NOT NULL LIMIT 1
    ) AS has_embeddings
  `;
  return Boolean(indexed.rows[0]?.has_embeddings);
}

/**
 * kNN visual match against precomputed catalog embeddings (Layer 0).
 */
export async function matchVisualInCatalog({ embedding, game = null }) {
  if (!embedding?.length) {
    return {
      type: 'escalate',
      reason: 'Missing query embedding',
    };
  }

  if (!(await catalogHasEmbeddings())) {
    return {
      type: 'escalate',
      reason: 'Catalog visual index is empty — run npm run backfill-embeddings',
    };
  }

  const vectorLiteral = formatEmbeddingForPg(embedding);
  const result = await querySimilarCards(vectorLiteral, game || null);
  const candidates = result.rows.filter((row) => row.sim >= DISAMBIGUATION_THRESHOLD);

  return resolveVisualCandidates(candidates);
}
