// ---------------------------------------------------------------------------
// AI Strategy — table parity, spariglio, mulinello, category states, endgame
// ---------------------------------------------------------------------------

import { Card, GameState, PlayerIndex, Suit, SUITS, PRIMIERA_VALUES } from './types';
import { AIMove } from './types';
import { teamOf } from './engine';
import { CardInferenceEngine } from './card-inference';

// ---------------------------------------------------------------------------
// Exported interfaces
// ---------------------------------------------------------------------------

export interface ParityState {
  pairedRanks: number[];
  unpairedRanks: number[];
  spariglioDegree: number;
  isEvenParity: boolean;
}

export interface SpariglioPotential {
  card: Card;
  spariglioDelta: number;       // how spariglioDegree changes if this card is dumped
  isSpariglio3Card: boolean;    // true if this is a 3-card spariglio (highest priority)
}

export interface MulinelloState {
  active: boolean;
  favorableFor: 'us' | 'them' | null;
  breakingMoves: AIMove[];
}

export interface CategoryEntry {
  state: 'secured' | 'lost' | 'contested';
  closeness: number;            // 0-1, higher = closer to winning
}

export interface PrimieraCategoryEntry {
  perSuit: Record<Suit, CategoryEntry>;
  overallCloseness: number;
}

export interface CategoryStates {
  denari: CategoryEntry;
  carte: CategoryEntry;
  primiera: PrimieraCategoryEntry;
  scope: 'always_contested';
  settebello: 'always_contested';
}

export interface PrimieraRaceState {
  teamLeadsBySuit: Record<Suit, boolean | null>;  // true=we lead, false=they lead, null=tied/unknown
  contestedSuits: Suit[];
  unseenPrimieraCards: Card[];  // unseen 7s, 6s, 1s (in primiera value order)
}

// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------

function canCardCapture(card: Card, table: Card[]): boolean {
  // Direct match
  if (table.some(c => c.value === card.value)) return true;
  // Sum capture (subsets of 2+)
  if (table.length < 2) return false;
  for (let mask = 1; mask < (1 << table.length); mask++) {
    const subset = table.filter((_, i) => mask & (1 << i));
    if (subset.length >= 2 && subset.reduce((s, c) => s + c.value, 0) === card.value) return true;
  }
  return false;
}

function bestPrimieraValue(pile: Card[], suit: Suit): number {
  const cards = pile.filter(c => c.suit === suit);
  if (cards.length === 0) return 0;
  return Math.max(...cards.map(c => PRIMIERA_VALUES[c.value] ?? 10));
}

function buildPrimieraCategoryEntry(
  state: GameState,
  team: 0 | 1,
  myPile: Card[],
  oppPile: Card[],
): PrimieraCategoryEntry {
  const perSuit: Record<Suit, CategoryEntry> = {} as Record<Suit, CategoryEntry>;
  let totalCloseness = 0;

  for (const suit of SUITS) {
    const myBest = bestPrimieraValue(myPile, suit);
    const oppBest = bestPrimieraValue(oppPile, suit);

    let entry: CategoryEntry;
    if (myBest > oppBest && myBest >= PRIMIERA_VALUES[6]) {
      // We have 7 or 6 in this suit and it's the best
      entry = { state: 'secured', closeness: 1 };
    } else if (oppBest > myBest && oppBest >= PRIMIERA_VALUES[6]) {
      entry = { state: 'lost', closeness: 0 };
    } else {
      // Contested: closer to 1 if we have a better card
      const closeness = Math.max(0, Math.min(1, (myBest - oppBest + 10) / 20));
      entry = { state: 'contested', closeness };
    }

    perSuit[suit] = entry;
    totalCloseness += entry.closeness;
  }

  return {
    perSuit,
    overallCloseness: totalCloseness / 4,
  };
}

// ---------------------------------------------------------------------------
// Exported functions
// ---------------------------------------------------------------------------

export function analyzeTableParity(table: Card[]): ParityState {
  const countByValue = new Map<number, number>();
  for (const card of table) {
    countByValue.set(card.value, (countByValue.get(card.value) ?? 0) + 1);
  }
  const pairedRanks: number[] = [];
  const unpairedRanks: number[] = [];
  for (const [value, count] of countByValue) {
    if (count % 2 === 0) pairedRanks.push(value);
    else unpairedRanks.push(value);
  }
  return {
    pairedRanks,
    unpairedRanks,
    spariglioDegree: unpairedRanks.length,
    isEvenParity: unpairedRanks.length === 0,
  };
}

export function rankDumpsBySpariglio(
  hand: Card[],
  table: Card[],
  isNonDealerTeam: boolean,
): SpariglioPotential[] {
  const current = analyzeTableParity(table);

  const potentials: SpariglioPotential[] = [];
  for (const card of hand) {
    if (canCardCapture(card, table)) continue; // only dump moves

    const tableAfter = [...table, card];
    const after = analyzeTableParity(tableAfter);
    const spariglioDelta = after.spariglioDegree - current.spariglioDegree;

    potentials.push({
      card,
      spariglioDelta,
      isSpariglio3Card: spariglioDelta >= 2,
    });
  }

  // Non-dealer wants highest positive delta first; dealer wants lowest (most negative) first
  if (isNonDealerTeam) {
    potentials.sort((a, b) => b.spariglioDelta - a.spariglioDelta);
  } else {
    potentials.sort((a, b) => a.spariglioDelta - b.spariglioDelta);
  }

  return potentials;
}

export function detectMulinello(
  state: GameState,
  playerIdx: PlayerIndex,
  inference: CardInferenceEngine,
): MulinelloState {
  const table = state.table;
  if (table.length === 0 || table.length > 4) {
    return { active: false, favorableFor: null, breakingMoves: [] };
  }

  const myHand = state.players[playerIdx].hand;
  const myCaptures = myHand.filter(c => canCardCapture(c, table));

  if (myCaptures.length === 0 && table.length <= 2) {
    // We can't capture — potential mulinello against us
    return {
      active: true,
      favorableFor: 'them',
      breakingMoves: myHand.map(c => ({ card: c, capture: [] })),
    };
  }

  return { active: false, favorableFor: null, breakingMoves: [] };
}

export function getPhase(state: GameState): 'opening' | 'midgame' | 'endgame' {
  const totalCardsInHands = state.players.reduce((sum, p) => sum + p.hand.length, 0);
  const totalPlayed = 40 - totalCardsInHands - state.table.length;
  if (totalPlayed <= 12) return 'opening';
  if (totalCardsInHands <= 16) return 'endgame';
  return 'midgame';
}

export function getCategoryStates(state: GameState, team: 0 | 1): CategoryStates {
  const myPile: Card[] = [];
  const oppPile: Card[] = [];
  for (let i = 0; i < 4; i++) {
    const p = state.players[i];
    if (teamOf(i as PlayerIndex) === team) myPile.push(...p.pile);
    else oppPile.push(...p.pile);
  }

  const myCoins = myPile.filter(c => c.suit === 'denara').length;
  const oppCoins = oppPile.filter(c => c.suit === 'denara').length;
  const totalCards = myPile.length + oppPile.length + state.table.length;
  const unseenCoins = 10 - myCoins - oppCoins - state.table.filter(c => c.suit === 'denara').length;
  const unseenCards = 40 - totalCards;

  // Denari
  const denari: CategoryEntry = myCoins >= 6
    ? { state: 'secured', closeness: 1 }
    : oppCoins >= 6
      ? { state: 'lost', closeness: 0 }
      : {
          state: 'contested',
          closeness: Math.max(0, Math.min(1, (myCoins - oppCoins + 1) / Math.max(1, unseenCoins + 1))),
        };

  // Carte
  const myCards = myPile.length;
  const oppCards = oppPile.length;
  const carte: CategoryEntry = myCards >= 21
    ? { state: 'secured', closeness: 1 }
    : oppCards >= 21
      ? { state: 'lost', closeness: 0 }
      : {
          state: 'contested',
          closeness: Math.max(0, Math.min(1, (myCards - oppCards + 1) / Math.max(1, unseenCards + 1))),
        };

  // Primiera: per suit
  const primiera = buildPrimieraCategoryEntry(state, team, myPile, oppPile);

  return {
    denari,
    carte,
    primiera,
    scope: 'always_contested',
    settebello: 'always_contested',
  };
}

export function solveEndgame(
  state: GameState,
  playerIdx: PlayerIndex,
  inference: CardInferenceEngine,
  legalMoves: AIMove[],
): AIMove | null {
  // Only attempt when very few cards remain
  const totalRemaining = state.players.reduce((sum, p) => sum + p.hand.length, 0);
  if (totalRemaining > 8) return null;

  const myHand = state.players[playerIdx].hand;

  for (let i = 0; i < 4; i++) {
    if (i === playerIdx) continue;
    const p = state.players[i];
    if (p.hand.length === 0) continue;

    const constrained = inference.getConstrainedUnseen(i as PlayerIndex, myHand, state.table);
    if (constrained.length < p.hand.length) {
      // Can't fully determine this player's hand
      return null;
    }
    // If constrained pool equals exactly handSize, we know exactly what they have
    if (constrained.length > p.hand.length) return null; // still ambiguous
  }

  // We know all hands — pick the best move by greedy evaluation
  let bestMove: AIMove | null = null;
  let bestScore = -Infinity;

  for (const move of legalMoves) {
    let score = 0;
    // Scopa
    const tableAfter = state.table.filter(c => !move.capture.some(cap => cap.id === c.id));
    if (move.capture.length > 0 && tableAfter.length === 0) score += 100;
    // Settebello
    if (move.capture.some(c => c.id === 'denara_7')) score += 80;
    // 7s
    score += move.capture.filter(c => c.value === 7).length * 40;
    // Coins
    score += move.capture.filter(c => c.suit === 'denara').length * 15;
    // Cards
    score += move.capture.length * 3;

    if (score > bestScore) {
      bestScore = score;
      bestMove = move;
    }
  }

  return bestMove;
}

export function analyzePrimieraRace(state: GameState, team: 0 | 1): PrimieraRaceState {
  const myPile: Card[] = [];
  const oppPile: Card[] = [];
  for (let i = 0; i < 4; i++) {
    const p = state.players[i];
    if (teamOf(i as PlayerIndex) === team) myPile.push(...p.pile);
    else oppPile.push(...p.pile);
  }

  const allKnown = new Set([...myPile, ...oppPile, ...state.table].map(c => c.id));
  const unseenPrimieraCards: Card[] = [];
  const PRIMIERA_ORDER = [7, 6, 1, 5, 4, 3, 2, 8, 9, 10];

  for (const value of PRIMIERA_ORDER) {
    for (const suit of SUITS) {
      const id = `${suit}_${value}`;
      if (!allKnown.has(id) && (value === 7 || value === 6 || value === 1)) {
        unseenPrimieraCards.push({ suit, value, id });
      }
    }
  }

  const teamLeadsBySuit: Record<Suit, boolean | null> = {} as Record<Suit, boolean | null>;
  const contestedSuits: Suit[] = [];

  for (const suit of SUITS) {
    const myBest = bestPrimieraValue(myPile, suit);
    const oppBest = bestPrimieraValue(oppPile, suit);
    if (myBest > oppBest) teamLeadsBySuit[suit] = true;
    else if (oppBest > myBest) teamLeadsBySuit[suit] = false;
    else {
      teamLeadsBySuit[suit] = null;
      contestedSuits.push(suit);
    }
  }

  return { teamLeadsBySuit, contestedSuits, unseenPrimieraCards };
}
