import Phaser from 'phaser';
import { Card, PlayerIndex, GameState, Difficulty } from '../game/types';
import {
  createInitialState, applyMove, findCaptures, getScoreBreakdown, teamOf, calcPrimiera, getMatchOutcome,
  nextPlayer
} from '../game/engine';
import { AIMove, AIDecisionProgress } from '../game/ai';
import { AIWorkerClient, AIWorkerClientLike } from '../game/ai-worker-client';
import { CardTracker } from '../game/card-tracker';
import { CardInferenceEngine } from '../game/card-inference';
import {
  DEFAULT_AUDIO_PREFERENCES,
  GameSceneData,
  loadAudioPreferences,
  normalizeAudioPreferences,
} from '../game/preferences';

// ---------------------------------------------------------------------------
// Suit ordering for hand grouping
// ---------------------------------------------------------------------------
const SUIT_ORDER: Record<string, number> = { denara: 0, coppe: 1, bastoni: 2, spade: 3 };
function sortHand(hand: Card[]): void {
  hand.sort((a, b) => (a.value - b.value) || (SUIT_ORDER[a.suit] - SUIT_ORDER[b.suit]));
}

// ---------------------------------------------------------------------------
// Layout constants
// ---------------------------------------------------------------------------
const CARD_SCALE_HUMAN = 0.165;  // larger cards for the human player
const CARD_SCALE_AI    = 0.125;  // smaller cards for AI (fit in side slots)

const CW_H = 402 * CARD_SCALE_HUMAN;   // card width  for human  ≈ 66
const CH_H = 645 * CARD_SCALE_HUMAN;   // card height for human  ≈ 106
const CW_A = 402 * CARD_SCALE_AI;      // card width  for AI     ≈ 50
const CH_A = 645 * CARD_SCALE_AI;      // card height for AI     ≈ 81

// Scorebar height at top
const SCOREBAR_H = 54;
const AI_MIN_THINK_MS = 1000;
const MOVE_OUTCOME_STATUS_MS = 2000;
const PLAYED_CARD_TRAVEL_MS = 320;
const CAPTURE_COLLAPSE_MS = 360;
const CAPTURE_COLLAPSE_DELAY_MS = 60;
const NON_CAPTURE_TABLE_TWEEN_MS = 420;
const RELAYOUT_TWEEN_MS = 120;

// Player positions:
// 0 = South (human, bottom), 1 = West (AI, left, rotated -90°)
// 2 = North (AI partner, top), 3 = East (AI, right, rotated +90°)

/**
 * GameScene — Scopone Scientifico main game scene.
 *
 * Phaser features exercised (many discovered via trueref /local/phaser):
 *
 * [trueref] postFX.addGlow(color, outerStrength, innerStrength, knockout)
 *   → used on selected cards and settebello on the table
 * [trueref] camera.shake(duration, intensity) + camera.flash(duration, r, g, b)
 *   → triggered on scopa and settebello captures
 * [trueref] graphics.fillRoundedRect(x, y, w, h, radius)
 *   → all UI panels (score bar, HUD, think bar)
 * [trueref] ParticleEmitter (v3.60+) this.add.particles(x, y, texture, config)
 *   → capture bursts, scopa explosion, card trails, victory confetti
 * [trueref] camera.postFX.addVignette(cx, cy, radius, strength)
 *   → cinematic vignette on the main camera
 * [trueref] image.setAngle() — used to rotate AI side cards ±90°
 */
export class GameScene extends Phaser.Scene {
  private state!: GameState;
  private cardImages: Map<string, Phaser.GameObjects.Image> = new Map();

  // Difficulty & card tracker
  private difficulty: Difficulty = 'advanced';
  private tracker: CardTracker = new CardTracker();
  private inference: CardInferenceEngine = new CardInferenceEngine(this.tracker);
  private aiClient: AIWorkerClientLike | null = null;

  // Active player highlight
  private activeHighlightRect: Phaser.GameObjects.Graphics | null = null;

  // Live score bar texts
  private hudA!: { scope: Phaser.GameObjects.Text; cards: Phaser.GameObjects.Text;
                   denari: Phaser.GameObjects.Text; sette: Phaser.GameObjects.Text;
                   prim: Phaser.GameObjects.Text; total: Phaser.GameObjects.Text };
  private hudB!: { scope: Phaser.GameObjects.Text; cards: Phaser.GameObjects.Text;
                   denari: Phaser.GameObjects.Text; sette: Phaser.GameObjects.Text;
                   prim: Phaser.GameObjects.Text; total: Phaser.GameObjects.Text };
  private roundText!: Phaser.GameObjects.Text;

  // Status bar
  private statusText!: Phaser.GameObjects.Text;
  private statusTimer: Phaser.Time.TimerEvent | null = null;
  private persistentStatusText = '';

  // Think bar
  private thinkBar!: Phaser.GameObjects.Graphics;

  // Player label containers (pulsed on active turn)
  private playerLabels: Map<PlayerIndex, Phaser.GameObjects.Container> = new Map();

  // Interaction state
  private selectedCard: Card | null = null;
  private selectedCardImg: Phaser.GameObjects.Image | null = null;
  private selectedGlow: any = null;
  private selectedGlowTween: Phaser.Tweens.Tween | null = null;
  private pendingCaptures: Card[][] = [];
  private tableHighlights: Phaser.GameObjects.GameObject[] = [];
  private captureListenerImgs: Phaser.GameObjects.Image[] = [];
  private aiThinking = false;

  private tableCenter!: { x: number; y: number };

  // Web Audio
  private audioCtx: AudioContext | null = null;
  private musicGain: GainNode | null = null;
  private musicStarted = false;
  private audioPreferences = DEFAULT_AUDIO_PREFERENCES;

  constructor() {
    super({ key: 'GameScene' });
  }

  // ---------------------------------------------------------------------------
  // Create
  // ---------------------------------------------------------------------------

  create(data?: Partial<GameSceneData>): void {
    const W = this.scale.width;
    const H = this.scale.height;
    this.tableCenter = { x: W / 2, y: (H + SCOREBAR_H) / 2 + 10 };

    // Read difficulty from scene data (MenuScene passes it)
    this.difficulty = data?.difficulty ?? 'advanced';
    this.audioPreferences = data?.audioPreferences
      ? normalizeAudioPreferences(data.audioPreferences)
      : loadAudioPreferences();
    this.tracker = new CardTracker();
    this.inference = new CardInferenceEngine(this.tracker);
    this.aiClient?.dispose();
    this.aiClient = new AIWorkerClient();
    this.events.once(Phaser.Scenes.Events.SHUTDOWN, this.handleSceneShutdown, this);
    this.events.once(Phaser.Scenes.Events.DESTROY, this.handleSceneShutdown, this);

    this.generateParticleTextures();
    this.drawBackground(W, H);
    this.buildScoreBar(W);
    this.buildStatusBar(W, H);
    this.buildThinkBar(W, H);
    this.buildPlayerLabels(W, H);

    // Custom vignette drawn in scene (avoids darkening score bar corners)
    this.drawVignette(W, H);

    this.input.once('pointerdown', () => this.startMusic());

    const initialDealer = Phaser.Math.Between(0, 3) as PlayerIndex;
    this.state = createInitialState(initialDealer);
    this.dealAnimation(() => {
      this.updateScoreBar();
      this.nextTurn();
    });
  }

  // ---------------------------------------------------------------------------
  // Particle textures
  // ---------------------------------------------------------------------------

  private generateParticleTextures(): void {
    // [trueref: graphics.generateTexture] — makes particle sprites without image files

    const mkCircle = (key: string, r: number) => {
      const g = this.add.graphics();
      g.fillStyle(0xffffff, 1);
      g.fillCircle(r, r, r);
      g.generateTexture(key, r * 2, r * 2);
      g.destroy();
    };
    mkCircle('particle_glow', 8);
    mkCircle('particle_sm',   4);

    const sq = this.add.graphics();
    sq.fillStyle(0xffffff, 1);
    sq.fillRect(0, 0, 5, 5);
    sq.generateTexture('particle_sq', 5, 5);
    sq.destroy();

    // Diamond (star) for settebello
    const dia = this.add.graphics();
    dia.fillStyle(0xffffff, 1);
    dia.fillTriangle(8, 0, 16, 8, 8, 16);
    dia.fillTriangle(0, 8, 8, 16, 8, 0);
    dia.generateTexture('particle_dia', 16, 16);
    dia.destroy();
  }

  // ---------------------------------------------------------------------------
  // Background
  // ---------------------------------------------------------------------------

  private drawBackground(W: number, H: number): void {
    // Layered felt
    this.add.rectangle(0, 0, W, H, 0x193d20).setOrigin(0).setDepth(0);
    // Lighter dark overlay (was 0.5)
    this.add.rectangle(W / 2, H / 2, W, H, 0x0d2410, 0.18).setDepth(0);

    // Subtle dot grid
    const g = this.add.graphics().setDepth(0);
    g.fillStyle(0x000000, 0.07);
    for (let x = 20; x < W; x += 36) {
      for (let y = SCOREBAR_H + 10; y < H; y += 36) {
        g.fillCircle(x, y, 1.2);
      }
    }

    // Outer decorative border
    const border = this.add.graphics().setDepth(1);
    border.lineStyle(2, 0xffd700, 0.18);
    border.strokeRect(8, SCOREBAR_H + 6, W - 16, H - SCOREBAR_H - 14);

    // Table area — using [trueref: graphics.fillRoundedRect]
    const tg = this.add.graphics().setDepth(1);
    tg.fillStyle(0x0a2912, 0.55);
    tg.fillRoundedRect(W * 0.12, SCOREBAR_H + 90, W * 0.76, H - SCOREBAR_H - 185, 18);
    // Softer border (was 1.5 / 0.5)
    tg.lineStyle(1, 0x3d8b3d, 0.18);
    tg.strokeRoundedRect(W * 0.12, SCOREBAR_H + 90, W * 0.76, H - SCOREBAR_H - 185, 18);

    // Player hand slot — no border, just subtle fill
    const hg = this.add.graphics().setDepth(1);
    hg.fillStyle(0x000000, 0.22);
    hg.fillRoundedRect(W * 0.12, H - CH_H - 28, W * 0.76, CH_H + 16, 10);
  }

  /** Soft multi-step vignette that only covers below the score bar. */
  private drawVignette(W: number, H: number): void {
    const vg = this.add.graphics().setDepth(3);
    const gameH = H - SCOREBAR_H;
    const steps = 14;

    // Left gradient
    for (let i = 0; i < steps; i++) {
      const t = 1 - i / steps;
      const alpha = 0.32 * t * t;
      const bw = W * 0.14 / steps;
      vg.fillStyle(0x000000, alpha);
      vg.fillRect(i * bw, SCOREBAR_H, bw + 1, gameH);
    }
    // Right gradient
    for (let i = 0; i < steps; i++) {
      const t = 1 - i / steps;
      const alpha = 0.32 * t * t;
      const bw = W * 0.14 / steps;
      vg.fillRect(W - (i + 1) * bw, SCOREBAR_H, bw + 1, gameH);
    }
    // Bottom gradient
    for (let i = 0; i < 10; i++) {
      const t = 1 - i / 10;
      const alpha = 0.22 * t * t;
      const bh = H * 0.10 / 10;
      vg.fillStyle(0x000000, alpha);
      vg.fillRect(0, H - (i + 1) * bh, W, bh + 1);
    }
  }

  // ---------------------------------------------------------------------------
  // Score bar (top, full width)
  // ---------------------------------------------------------------------------

  private buildScoreBar(W: number): void {
    // [trueref: fillRoundedRect] Panel background
    const bg = this.add.graphics().setDepth(8);
    bg.fillStyle(0x050e07);
    bg.fillRect(0, 0, W, SCOREBAR_H);
    bg.lineStyle(1, 0xffd700, 0.7);
    bg.lineBetween(0, SCOREBAR_H, W, SCOREBAR_H);

    const mkTxt = (x: number, y: number, val: string, color = '#ffffff', size = '15px') =>
      this.add.text(x, y, val, { fontFamily: 'monospace', fontSize: size, color, resolution: 2 })
        .setOrigin(0.5).setDepth(9);

    // Left side — Team A
    this.add.text(10, SCOREBAR_H / 2, 'TEAM A  (Tu + Compagno)', {
      fontFamily: 'serif', fontSize: '13px', color: '#aaffaa', resolution: 2,
    }).setOrigin(0, 0.5).setDepth(9);

    // Right side — Team B
    this.add.text(W - 10, SCOREBAR_H / 2, 'TEAM B  (AI Ovest + AI Est)', {
      fontFamily: 'serif', fontSize: '13px', color: '#ffaaaa', resolution: 2,
    }).setOrigin(1, 0.5).setDepth(9);

    // Center — Round
    this.roundText = mkTxt(W / 2, SCOREBAR_H / 2, 'Mano 1', '#ffd700', '16px');

    // Column headers (shared, centered-ish)
    const cols = ['Scope', 'Carte', 'Denari', '7Bello', 'Primiera', 'TOTALE'];
    const xA = [230, 295, 370, 445, 520, 610];
    const xB = [W - 230, W - 295, W - 370, W - 445, W - 520, W - 610];

    cols.forEach((_, i) => {
      const label = ['Sc', 'Ca', 'De', '7B', 'Pr', 'Pt'][i];
      this.add.text(xA[i], SCOREBAR_H * 0.28, label, {
        fontFamily: 'monospace', fontSize: '10px', color: '#999999', resolution: 2,
      }).setOrigin(0.5).setDepth(9);
      this.add.text(xB[i], SCOREBAR_H * 0.28, label, {
        fontFamily: 'monospace', fontSize: '10px', color: '#999999', resolution: 2,
      }).setOrigin(0.5).setDepth(9);
    });

    // Live value texts
    const mkA = (xi: number) => mkTxt(xA[xi], SCOREBAR_H * 0.72, '0', '#aaffaa', '17px');
    const mkB = (xi: number) => mkTxt(xB[xi], SCOREBAR_H * 0.72, '0', '#ffaaaa', '17px');

    this.hudA = {
      scope:  mkA(0), cards: mkA(1), denari: mkA(2), sette: mkA(3), prim: mkA(4),
      total:  this.add.text(xA[5], SCOREBAR_H * 0.72, '0', {
        fontFamily: 'Georgia, serif', fontSize: '20px', color: '#aaffaa',
        stroke: '#000', strokeThickness: 2, resolution: 2,
      }).setOrigin(0.5).setDepth(9),
    };
    this.hudB = {
      scope:  mkB(0), cards: mkB(1), denari: mkB(2), sette: mkB(3), prim: mkB(4),
      total:  this.add.text(xB[5], SCOREBAR_H * 0.72, '0', {
        fontFamily: 'Georgia, serif', fontSize: '20px', color: '#ffaaaa',
        stroke: '#000', strokeThickness: 2, resolution: 2,
      }).setOrigin(0.5).setDepth(9),
    };
  }

  private updateScoreBar(): void {
    const s = this.state;
    const t0 = s.teamScores[0];
    const t1 = s.teamScores[1];
    const team0 = [s.players[0], s.players[2]];
    const team1 = [s.players[1], s.players[3]];

    const scope0  = team0.reduce((n, p) => n + p.scope, 0);
    const scope1  = team1.reduce((n, p) => n + p.scope, 0);
    const pile0   = team0.flatMap(p => p.pile);
    const pile1   = team1.flatMap(p => p.pile);
    const den0    = pile0.filter(c => c.suit === 'denara').length;
    const den1    = pile1.filter(c => c.suit === 'denara').length;
    const prim0   = calcPrimiera(pile0);
    const prim1   = calcPrimiera(pile1);
    const sette0  = pile0.some(c => c.suit === 'denara' && c.value === 7);
    const sette1  = pile1.some(c => c.suit === 'denara' && c.value === 7);

    const setAnim = (txt: Phaser.GameObjects.Text, val: string | number) => {
      const v = String(val);
      if (txt.text === v) return;
      txt.setText(v);
      this.tweens.add({ targets: txt, scaleX: 1.4, scaleY: 1.4, duration: 100, yoyo: true });
    };

    setAnim(this.hudA.scope,  scope0);
    setAnim(this.hudA.cards,  pile0.length);
    setAnim(this.hudA.denari, den0);
    setAnim(this.hudA.sette,  sette0 ? '✓' : '–');
    setAnim(this.hudA.prim,   prim0 > 0 ? prim0 : '-');
    setAnim(this.hudA.total,  t0.totalPoints);

    setAnim(this.hudB.scope,  scope1);
    setAnim(this.hudB.cards,  pile1.length);
    setAnim(this.hudB.denari, den1);
    setAnim(this.hudB.sette,  sette1 ? '✓' : '–');
    setAnim(this.hudB.prim,   prim1 > 0 ? prim1 : '-');
    setAnim(this.hudB.total,  t1.totalPoints);

    this.roundText.setText(`Mano ${s.roundNumber ?? 1}`);
  }

  private flashScoreScope(team: 0 | 1): void {
    const txt = team === 0 ? this.hudA.scope : this.hudB.scope;
    this.tweens.add({
      targets: txt,
      scaleX: 2.2, scaleY: 2.2,
      duration: 180, yoyo: true, repeat: 3,
      onStart:    () => txt.setColor('#ffd700'),
      onComplete: () => txt.setColor(team === 0 ? '#aaffaa' : '#ffaaaa'),
    });
  }

  // ---------------------------------------------------------------------------
  // Status bar + think bar
  // ---------------------------------------------------------------------------

  private buildStatusBar(W: number, H: number): void {
    // Background chip
    const bg = this.add.graphics().setDepth(9);
    bg.fillStyle(0x000000, 0.55);
    bg.fillRoundedRect(W / 2 - 280, H - CH_H - 50, 560, 28, 8);

    this.statusText = this.add.text(W / 2, H - CH_H - 36, '', {
      fontFamily: 'serif', fontSize: '17px', color: '#ffffff',
      stroke: '#000', strokeThickness: 2,
      resolution: 2,
    }).setOrigin(0.5).setDepth(10);
  }

  private buildThinkBar(W: number, H: number): void {
    // Thin progress bar just below the score bar
    this.thinkBar = this.add.graphics().setDepth(11).setVisible(false);
  }

  private showThinkBar(playerIdx: PlayerIndex, remainingRatio = 1): void {
    this.thinkBar.setVisible(true);
    this.drawThinkBar(playerIdx, remainingRatio);
  }

  private updateThinkBar(playerIdx: PlayerIndex, progress: AIDecisionProgress): void {
    this.drawThinkBar(playerIdx, 1 - progress.progress);
  }

  private logMasterAIDiagnostics(
    playerIdx: PlayerIndex,
    move: AIMove,
    progress: AIDecisionProgress | null,
  ): void {
    if (this.difficulty !== 'master' || !progress) {
      return;
    }

    console.info('[AI master diagnostics]', {
      playerIdx,
      roundNumber: this.state.roundNumber ?? 1,
      moveCardId: move.card.id,
      captureCount: move.capture.length,
      timing: {
        elapsedMs: progress.elapsedMs,
        budgetMs: progress.budgetMs,
        progress: progress.progress,
        timedOut: progress.timedOut ?? false,
      },
      search: {
        batchesCompleted: progress.batchesCompleted,
        cardsRemaining: progress.cardsRemaining ?? null,
        sampleCount: progress.sampleCount ?? null,
        maxDepth: progress.maxDepth ?? null,
        completedDepth: progress.completedDepth ?? null,
        rootMoveCount: progress.rootMoveCount ?? null,
        aspirationExpansions: progress.aspirationExpansions ?? 0,
      },
    });
  }

  private drawThinkBar(playerIdx: PlayerIndex, remainingRatio: number): void {
    const W = this.scale.width;
    const tg = this.thinkBar;
    const color = (playerIdx === 0 || playerIdx === 2) ? 0x44ff88 : 0xff5555;
    const clampedRatio = Phaser.Math.Clamp(remainingRatio, 0, 1);
    const width = clampedRatio * W;

    tg.clear();
    tg.fillStyle(0x000000, 0.4);
    tg.fillRect(0, SCOREBAR_H, W, 4);
    if (width <= 0) return;

    tg.fillStyle(color, 0.85);
    tg.fillRect(0, SCOREBAR_H, width, 4);
    tg.fillStyle(0xffffff, 0.6);
    tg.fillRect(Math.max(0, width - 6), SCOREBAR_H, Math.min(6, width), 4);
  }

  private hideThinkBar(): void {
    this.thinkBar.clear();
    this.thinkBar.setVisible(false);
  }

  private clearStatusTimer(): void {
    if (!this.statusTimer) {
      return;
    }

    this.statusTimer.remove(false);
    this.statusTimer.destroy();
    this.statusTimer = null;
  }

  private waitForDelay(delayMs: number): Promise<void> {
    if (delayMs <= 0 || !this.scene.isActive('GameScene')) {
      return Promise.resolve();
    }

    return new Promise((resolve) => {
      this.time.delayedCall(delayMs, () => resolve());
    });
  }

  private buildMoveOutcomeStatus(playerIdx: PlayerIndex, card: Card, capture: Card[] | null): string {
    const actor = this.getMoveActorPrefix(playerIdx);

    if (!capture || capture.length === 0) {
      return `${actor} giocato ${cardName(card)}.`;
    }

    return `${actor} preso ${capture.map(cardName).join(', ')} con ${cardName(card)}.`;
  }

  private getTurnStatus(playerIdx: PlayerIndex): string {
    switch (playerIdx) {
      case 0:
        return 'Tocca a te.';
      case 2:
        return 'Sta giocando il tuo compagno.';
      default:
        return `Sta giocando ${this.state.players[playerIdx].name}.`;
    }
  }

  private getMoveActorPrefix(playerIdx: PlayerIndex): string {
    switch (playerIdx) {
      case 0:
        return 'Hai';
      case 2:
        return 'Il tuo compagno ha';
      default:
        return `${this.state.players[playerIdx].name} ha`;
    }
  }

  private getSingleCapturePrompt(capture: Card[]): string {
    return `Puoi prendere ${capture.map(cardName).join(', ')}. Clicca di nuovo per confermare.`;
  }

  private getAiMoveErrorStatus(playerIdx: PlayerIndex): string {
    if (playerIdx === 2) {
      return 'Problema durante la mossa del tuo compagno.';
    }

    return `Problema durante la mossa di ${this.state.players[playerIdx].name}.`;
  }

  private withHiResText(
    style: Phaser.Types.GameObjects.Text.TextStyle,
  ): Phaser.Types.GameObjects.Text.TextStyle {
    return {
      ...style,
      resolution: style.resolution ?? 2,
    };
  }

  private createPlayerNameplate(
    x: number,
    y: number,
    text: string,
    color: string,
    fillColor: number,
    strokeColor: number,
  ): Phaser.GameObjects.Container {
    const label = this.add.text(0, 0, text, this.withHiResText({
      fontFamily: 'serif',
      fontSize: '11px',
      color,
      stroke: '#000',
      strokeThickness: 1,
      align: 'center',
    })).setOrigin(0.5);

    const padX = 12;
    const padY = 4;
    const width = label.width + padX * 2;
    const height = label.height + padY * 2;
    const background = this.add.graphics();
    background.fillStyle(fillColor, 0.92);
    background.fillRoundedRect(-width / 2, -height / 2, width, height, 9);
    background.lineStyle(1, strokeColor, 0.7);
    background.strokeRoundedRect(-width / 2, -height / 2, width, height, 9);

    return this.add.container(x, y, [background, label]).setDepth(18);
  }

  // ---------------------------------------------------------------------------
  // Player labels (pulse on active turn)
  // ---------------------------------------------------------------------------

  private buildPlayerLabels(W: number, H: number): void {
    const defs: Array<{ idx: PlayerIndex; x: number; y: number; color: string;
      fillColor: number; strokeColor: number; txt: string }> = [
      { idx: 0, x: W / 2, y: H - 9, color: '#dfffe5', fillColor: 0x0b2410, strokeColor: 0x4aa86a, txt: 'Tu [Team A]' },
      { idx: 1, x: CH_A + 58, y: H / 2 + SCOREBAR_H / 2, color: '#ffe0e0', fillColor: 0x260d0d, strokeColor: 0xb36a6a, txt: 'AI Ovest [B]' },
      { idx: 2, x: W / 2, y: SCOREBAR_H + 11, color: '#dfffe5', fillColor: 0x0b2410, strokeColor: 0x4aa86a, txt: 'Compagno [Team A]' },
      { idx: 3, x: W - CH_A - 58, y: H / 2 + SCOREBAR_H / 2, color: '#ffe0e0', fillColor: 0x260d0d, strokeColor: 0xb36a6a, txt: 'AI Est [B]' },
    ];
    for (const d of defs) {
      const lbl = this.createPlayerNameplate(d.x, d.y, d.txt, d.color, d.fillColor, d.strokeColor);
      this.playerLabels.set(d.idx, lbl);
    }
  }

  private pulseLabel(playerIdx: PlayerIndex): void {
    // Reset all labels
    for (const [idx, lbl] of this.playerLabels) {
      lbl.setAlpha(idx === playerIdx ? 1 : 0.4);
    }

    // Remove old highlight
    if (this.activeHighlightRect) {
      this.activeHighlightRect.destroy();
      this.activeHighlightRect = null;
    }

    // Draw turn indicator bar below/outside player's cards
    const W = this.scale.width;
    const H = this.scale.height;
    const color = teamOf(playerIdx) === 0 ? 0x00ff44 : 0xff4444;
    const gfx = this.add.graphics().setDepth(10);
    const barThick = 4;
    switch (playerIdx) {
      case 0: // South — horizontal bar below hand
        gfx.fillStyle(color, 0.9);
        gfx.fillRoundedRect(W * 0.15, H - barThick - 2, W * 0.7, barThick, 2);
        break;
      case 1: // West — vertical bar left of cards
        gfx.fillStyle(color, 0.9);
        gfx.fillRoundedRect(2, SCOREBAR_H + 60, barThick, H - SCOREBAR_H - CH_H - 80, 2);
        break;
      case 2: // North — horizontal bar above partner cards
        gfx.fillStyle(color, 0.9);
        gfx.fillRoundedRect(W * 0.18, SCOREBAR_H + 2, W * 0.64, barThick, 2);
        break;
      case 3: // East — vertical bar right of cards
        gfx.fillStyle(color, 0.9);
        gfx.fillRoundedRect(W - barThick - 2, SCOREBAR_H + 60, barThick, H - SCOREBAR_H - CH_H - 80, 2);
        break;
    }
    this.activeHighlightRect = gfx;

    // Pulse the glow
    this.tweens.add({
      targets: gfx,
      alpha: { from: 1, to: 0.4 },
      duration: 600, yoyo: true, repeat: -1, ease: 'Sine.InOut',
    });

    // Pulse the label
    const activeLbl = this.playerLabels.get(playerIdx)!;
    this.tweens.add({
      targets: activeLbl,
      scaleX: 1.08, scaleY: 1.08,
      duration: 300, yoyo: true, ease: 'Sine.InOut',
    });
  }

  // ---------------------------------------------------------------------------
  // Deal animation
  // ---------------------------------------------------------------------------

  private dealAnimation(onComplete: () => void): void {
    const W = this.scale.width;
    const H = this.scale.height;
    let delay = 0, done = 0;

    const allCards: Array<{ card: Card; p: number; destX: number; destY: number; face: boolean }> = [];

    for (let p = 0; p < 4; p++) {
      sortHand(this.state.players[p as PlayerIndex].hand);
      const positions = this.getHandPositions(p as PlayerIndex,
        this.state.players[p as PlayerIndex].hand.length);
      this.state.players[p as PlayerIndex].hand.forEach((card, i) =>
        allCards.push({ card, p, destX: positions[i].x, destY: positions[i].y, face: p === 0 })
      );
    }

    const total = allCards.length;
    for (const { card, p, destX, destY, face } of allCards) {
      const scale = p === 0 ? CARD_SCALE_HUMAN : CARD_SCALE_AI;
      const img = this.add.image(W / 2, SCOREBAR_H + 40,
        face ? 'cards' : 'retro', face ? card.id : undefined)
        .setScale(scale).setDepth(5).setAlpha(0);

      // Rotate side cards [trueref: image.setAngle() — rotation in degrees]
      if (p === 1) img.setAngle(-90);
      if (p === 3) img.setAngle(90);

      // Subtle drop shadow on all cards [trueref: postFX.addShadow]
      if (this.renderer.type === Phaser.WEBGL) {
        img.postFX.addShadow(0, 4, 0.006, 1.2, 0x000000, 6, 0.4);
      }

      this.cardImages.set(card.id, img);

      this.tweens.add({
        targets: img,
        x: destX, y: destY, alpha: 1,
        duration: 260, delay,
        ease: 'Power2',
        onComplete: () => {
          done++;
          if (done === total) onComplete();
        },
      });
      delay += 25;
    }
  }

  // ---------------------------------------------------------------------------
  // Hand / table positions
  // ---------------------------------------------------------------------------

  private getHandPositions(playerIdx: PlayerIndex, count: number): Array<{ x: number; y: number }> {
    const W = this.scale.width;
    const H = this.scale.height;

    switch (playerIdx) {
      case 0: { // South (human) — horizontal, bottom
        const sp = Math.min(CW_H + 5, (W * 0.72) / Math.max(count - 1, 1));
        const sx = W / 2 - ((count - 1) * sp) / 2;
        return Array.from({ length: count }, (_, i) => ({ x: sx + i * sp, y: H - CH_H / 2 - 18 }));
      }
      case 2: { // North (partner) — horizontal, top
        const sp = Math.min(CW_A + 4, (W * 0.65) / Math.max(count - 1, 1));
        const sx = W / 2 - ((count - 1) * sp) / 2;
        return Array.from({ length: count }, (_, i) => ({ x: sx + i * sp, y: SCOREBAR_H + CH_A / 2 + 22 }));
      }
      case 1: { // West — vertical, rotated -90°
        // When rotated -90°, visual footprint: width=CH_A, height=CW_A
        const available = H - SCOREBAR_H - CH_H - 60;
        const sp = Math.min(CW_A + 4, available / Math.max(count - 1, 1));
        const sy = (H + SCOREBAR_H) / 2 - ((count - 1) * sp) / 2;
        return Array.from({ length: count }, (_, i) => ({ x: CH_A / 2 + 10, y: sy + i * sp }));
      }
      case 3: { // East — vertical, rotated +90°
        const available = H - SCOREBAR_H - CH_H - 60;
        const sp = Math.min(CW_A + 4, available / Math.max(count - 1, 1));
        const sy = (H + SCOREBAR_H) / 2 - ((count - 1) * sp) / 2;
        return Array.from({ length: count }, (_, i) => ({ x: W - CH_A / 2 - 10, y: sy + i * sp }));
      }
    }
  }

  private getTablePositions(count: number): Array<{ x: number; y: number }> {
    const W = this.scale.width;
    const H = this.scale.height;
    const cols = Math.min(count, 7);
    const rows = Math.ceil(count / cols);
    const spX = CW_H + 10;
    const spY = CH_H + 8;
    const results: Array<{ x: number; y: number }> = [];
    for (let i = 0; i < count; i++) {
      results.push({
        x: W / 2 + (i % cols - (cols - 1) / 2) * spX,
        y: this.tableCenter.y + (Math.floor(i / cols) - (rows - 1) / 2) * spY,
      });
    }
    return results;
  }

  // ---------------------------------------------------------------------------
  // Turn management
  // ---------------------------------------------------------------------------

  private nextTurn(): void {
    if (this.state.roundOver) { this.showRoundEnd(); return; }
    const cur = this.state.currentPlayer;
    const player = this.state.players[cur];
    this.setStatus(this.getTurnStatus(cur), { persist: true });
    this.pulseLabel(cur);

    if (player.isHuman) {
      this.hideThinkBar();
      this.enableHumanInteraction();
    } else {
      this.aiThinking = true;
      this.showThinkBar(cur, 1);
      void this.doAIMove(cur);
    }
  }

  private handleSceneShutdown(): void {
    this.clearStatusTimer();
    this.aiClient?.dispose();
    this.aiClient = null;
    this.aiThinking = false;
    if (this.thinkBar) {
      this.hideThinkBar();
    }
  }

  private async doAIMove(playerIdx: PlayerIndex): Promise<void> {
    const turnState = this.state;
    const aiClient = this.aiClient;
    let finalProgress: AIDecisionProgress | null = null;
    const thinkStartedAt = Date.now();

    if (!aiClient) {
      return;
    }

    try {
      const move = await aiClient.chooseMove(
        this.state,
        playerIdx,
        this.difficulty,
        this.tracker,
        (progress) => {
          if (this.aiClient !== aiClient || !this.scene.isActive('GameScene') || this.state !== turnState) return;
          this.updateThinkBar(playerIdx, progress);
          if (progress.difficulty !== 'master') return;
          finalProgress = progress;
        },
        { inference: this.inference },
      );

      const remainingThinkMs = AI_MIN_THINK_MS - (Date.now() - thinkStartedAt);
      if (remainingThinkMs > 0) {
        await this.waitForDelay(remainingThinkMs);
      }

      if (this.aiClient !== aiClient) return;
      if (!this.scene.isActive('GameScene')) return;
      if (this.state !== turnState || this.state.currentPlayer !== playerIdx || this.state.roundOver) return;

      this.logMasterAIDiagnostics(playerIdx, move, finalProgress);
      this.hideThinkBar();
      this.aiThinking = false;
      this.executeMove(playerIdx, move.card, move.capture);
    } catch (error) {
      console.error('AI move failed', error);
      if (this.aiClient === aiClient && this.scene.isActive('GameScene') && this.state === turnState) {
        this.setStatus(this.getAiMoveErrorStatus(playerIdx));
      }
    } finally {
      if (this.aiClient === aiClient && this.scene.isActive('GameScene') && this.state === turnState) {
        this.hideThinkBar();
        this.aiThinking = false;
      }
    }
  }

  // ---------------------------------------------------------------------------
  // Human interaction
  // ---------------------------------------------------------------------------

  private enableHumanInteraction(): void {
    for (const card of this.state.players[0].hand) {
      const img = this.cardImages.get(card.id);
      if (!img) continue;
      img.setInteractive({ useHandCursor: true });
      img.on('pointerover', () => {
        if (this.selectedCard?.id !== card.id) img.setY(img.y - 10);
      });
      img.on('pointerout', () => {
        if (this.selectedCard?.id !== card.id) img.setY(img.y + 10);
      });
      img.on('pointerdown', () => this.onCardClick(card, img));
    }
  }

  private disableHumanInteraction(): void {
    for (const card of this.state.players[0].hand) {
      const img = this.cardImages.get(card.id);
      if (img) { img.removeAllListeners(); img.disableInteractive(); }
    }
    this.clearSelectedGlow();
    this.clearHighlights();
    this.selectedCard = null;
    this.selectedCardImg = null;
  }

  private onCardClick(card: Card, img: Phaser.GameObjects.Image): void {
    if (this.aiThinking) return;
    const captures = findCaptures(card, this.state.table);

    if (this.selectedCard?.id === card.id) {
      // Second click: confirm if possible
      if (captures.length === 1) { this.confirmMove(card, captures[0]); return; }
      if (captures.length === 0) { this.confirmMove(card, []); return; }
      this.deselectCard();
      return;
    }

    this.deselectCard();
    this.selectedCard = card;
    this.selectedCardImg = img;

    // [trueref: postFX.addGlow] — pulsing selection glow
    if (this.renderer.type === Phaser.WEBGL) {
      img.postFX.clear();
      this.selectedGlow = img.postFX.addGlow(0xffff88, 4, 0);
      this.selectedGlowTween = this.tweens.add({
        targets: this.selectedGlow,
        outerStrength: 12,
        duration: 500, yoyo: true, repeat: -1, ease: 'Sine.InOut',
      });
    } else {
      img.setTint(0xffff88);
    }

    if (captures.length === 0) {
      this.setStatus('Non puoi prendere nulla: clicca di nuovo per lasciare la carta sul tavolo.');
      this.highlightTableForDump(card);
    } else if (captures.length === 1) {
      this.setStatus(this.getSingleCapturePrompt(captures[0]));
      this.pendingCaptures = captures;
      this.highlightCapture(captures[0]);
    } else {
      this.setStatus('Scegli quale presa vuoi fare.');
      this.pendingCaptures = captures;
      this.highlightMultipleCaptures(captures);
    }
  }

  private deselectCard(): void {
    this.clearSelectedGlow();
    if (this.selectedCardImg && this.renderer.type !== Phaser.WEBGL) {
      this.selectedCardImg.clearTint();
    }
    this.selectedCard = null;
    this.selectedCardImg = null;
    this.pendingCaptures = [];
    this.clearHighlights();
  }

  private clearSelectedGlow(): void {
    this.selectedGlowTween?.stop();
    this.selectedGlowTween = null;
    this.selectedGlow = null;
    if (this.selectedCardImg) {
      this.selectedCardImg.postFX?.clear();
      if (this.renderer.type === Phaser.WEBGL) {
        // Re-add shadow after clearing
        this.selectedCardImg.postFX.addShadow(0, 4, 0.006, 1.2, 0x000000, 6, 0.4);
      }
    }
  }

  private confirmMove(card: Card, capture: Card[]): void {
    this.disableHumanInteraction();
    this.executeMove(0, card, capture);
  }

  // ---------------------------------------------------------------------------
  // Highlights
  // ---------------------------------------------------------------------------

  private highlightCapture(capture: Card[]): void {
    this.clearHighlights();
    for (const card of capture) {
      const img = this.cardImages.get(card.id);
      if (!img) continue;
      const hl = this.add.rectangle(img.x, img.y, CW_H + 8, CH_H + 8, 0x00ff88, 0.2)
        .setStrokeStyle(2, 0x00ff88, 0.9).setDepth(4);
      this.tableHighlights.push(hl);
      img.setInteractive({ useHandCursor: true });
      img.once('pointerdown', () => this.confirmMove(this.selectedCard!, capture));
      this.captureListenerImgs.push(img);
    }
  }

  private highlightMultipleCaptures(captures: Card[][]): void {
    this.clearHighlights();
    const W = this.scale.width;

    // Distinct color palette for each capture option
    const palette = [
      { fill: 0x00cc66, stroke: 0x00ff88, text: '#00ffaa', bg: 0x001a0a },
      { fill: 0x3399ff, stroke: 0x66bbff, text: '#88ccff', bg: 0x001020 },
      { fill: 0xff8833, stroke: 0xffaa55, text: '#ffcc88', bg: 0x1a0d00 },
      { fill: 0xcc44cc, stroke: 0xff66ff, text: '#ff88ff', bg: 0x1a001a },
      { fill: 0x00cccc, stroke: 0x44ffff, text: '#88ffff', bg: 0x001a1a },
    ];

    captures.forEach((cap, i) => {
      const color = palette[i % palette.length];
      const label = cap.map(cardName).join(' + ');
      const y = SCOREBAR_H + 70 + i * 36;
      const bg = this.add.graphics().setDepth(20);
      bg.fillStyle(color.bg, 0.9);
      bg.fillRoundedRect(W / 2 - 180, y - 14, 360, 28, 7);
      bg.lineStyle(2, color.stroke, 0.8);
      bg.strokeRoundedRect(W / 2 - 180, y - 14, 360, 28, 7);
      const btn = this.add.zone(W / 2, y, 360, 28).setInteractive({ useHandCursor: true }).setDepth(21);
      const txt = this.add.text(W / 2, y, `Prendi: ${label}`, {
        fontFamily: 'serif', fontSize: '14px', color: color.text,
        resolution: 2,
      }).setOrigin(0.5).setDepth(21);
      btn.on('pointerdown', () => this.confirmMove(this.selectedCard!, cap));
      (bg as any)._captureBtn = true;
      this.tableHighlights.push(bg, btn, txt);

      // Highlight table cards belonging to this option with the matching color
      for (const c of cap) {
        const img = this.cardImages.get(c.id);
        if (img) {
          const hl = this.add.rectangle(img.x, img.y, CW_H + 8, CH_H + 8, color.fill, 0.2)
            .setStrokeStyle(2, color.stroke, 0.9).setDepth(4);
          this.tableHighlights.push(hl);
          img.setInteractive({ useHandCursor: true });
          img.once('pointerdown', () => this.confirmMove(this.selectedCard!, cap));
          this.captureListenerImgs.push(img);
        }
      }
    });
  }

  private highlightTableForDump(card: Card): void {
    const { x, y } = this.tableCenter;
    const W = this.scale.width;
    const H = this.scale.height;
    const tw = W * 0.76 - 4;
    const th = H - SCOREBAR_H - 175;
    const hl = this.add.graphics().setDepth(4).setInteractive(
      new Phaser.Geom.Rectangle(x - tw / 2, y - th / 2, tw, th),
      Phaser.Geom.Rectangle.Contains
    );
    hl.fillStyle(0xffff00, 0.05);
    hl.fillRoundedRect(x - tw / 2, y - th / 2, tw, th, 18);
    hl.lineStyle(2, 0xffff00, 0.4);
    hl.strokeRoundedRect(x - tw / 2, y - th / 2, tw, th, 18);
    hl.on('pointerdown', () => this.confirmMove(card, []));
    this.tableHighlights.push(hl);
  }

  private clearHighlights(): void {
    for (const img of this.captureListenerImgs) img.off('pointerdown');
    this.captureListenerImgs = [];
    for (const h of this.tableHighlights) h.destroy();
    this.tableHighlights = [];
  }

  // ---------------------------------------------------------------------------
  // Execute move + animate
  // ---------------------------------------------------------------------------

  private executeMove(playerIdx: PlayerIndex, card: Card, capture: Card[]): void {
    const tableBeforeMove = [...this.state.table];
    const { nextState, capture: captureResult, isScopa } = applyMove(
      this.state, playerIdx, card, capture.length > 0 ? capture : undefined
    );
    const oldState = this.state;
    this.state = nextState;

    // Update card tracker
    this.tracker.trackPlay(card);
    if (captureResult) {
      this.tracker.trackCapture(captureResult.captured);
    }

    // Update inference engine
    this.inference.onMove(
      playerIdx,
      { card, capture: captureResult?.captured ?? [] },
      tableBeforeMove,
    );

    const cardImg = this.cardImages.get(card.id)!;
    cardImg.setDepth(15);

    const isSettebello = captureResult !== null &&
      [card, ...captureResult.captured].some(c => c.suit === 'denara' && c.value === 7);
    const isDenariCapture = captureResult !== null && !isSettebello &&
      [card, ...captureResult.captured].some(c => c.suit === 'denara');
    const isPrimieraPick = captureResult !== null &&
      [card, ...captureResult.captured].some(c => [7, 6, 1, 5].includes(c.value));

    // Flip to face-up
    cardImg.setTexture('cards', card.id);
    if (this.renderer.type === Phaser.WEBGL) {
      cardImg.postFX.clear();
      cardImg.postFX.addShadow(0, 4, 0.006, 1.2, 0x000000, 6, 0.4);
    }

    this.playSfx(isSettebello ? 'settebello' : captureResult ? 'capture' : 'card_play');
    this.spawnCardTrail(cardImg);

    if (captureResult) {
      this.tweens.add({
        targets: cardImg,
        x: this.tableCenter.x, y: this.tableCenter.y,
        duration: PLAYED_CARD_TRAVEL_MS, ease: 'Power2',
        onComplete: () => {
          this.spawnCaptureEffect(this.tableCenter.x, this.tableCenter.y, isSettebello);

          if (isSettebello) {
            // [trueref: camera.shake + camera.flash] — dramatic impact for settebello
            this.cameras.main.shake(450, 0.012);
            this.cameras.main.flash(400, 255, 215, 0);
            this.spawnSettebelloFlash();
          }
          if (isDenariCapture) {
            this.spawnDenariEffect(this.tableCenter.x, this.tableCenter.y);
          }
          if (isPrimieraPick && !isSettebello) {
            this.spawnPrimieraEffect(this.tableCenter.x, this.tableCenter.y);
          }

          const pilePos = this.getPilePos(playerIdx);
          const toClear = [card, ...captureResult.captured];
          let done = 0;
          for (const c of toClear) {
            const img = this.cardImages.get(c.id)!;
            this.tweens.add({
              targets: img,
              x: pilePos.x, y: pilePos.y, alpha: 0,
              duration: CAPTURE_COLLAPSE_MS, delay: CAPTURE_COLLAPSE_DELAY_MS,
              onComplete: () => {
                img.setVisible(false);
                done++;
                if (done === toClear.length) {
                  if (isScopa) {
                    this.playSfx('scopa');
                    this.doScopaEffect(playerIdx, () =>
                      this.afterMove(playerIdx, card, captureResult?.captured ?? null, nextState, oldState)
                    );
                  } else {
                    this.afterMove(playerIdx, card, captureResult?.captured ?? null, nextState, oldState);
                  }
                }
              },
            });
          }
        },
      });
    } else {
      const tablePos = this.nextTablePos(nextState.table);
      // Drop shadow on newly placed table card [trueref: postFX.addShadow]
      if (this.renderer.type === Phaser.WEBGL) {
        cardImg.postFX.clear();
        cardImg.postFX.addShadow(2, 6, 0.008, 1.2, 0x000000, 8, 0.5);
      }
      // Random "fallen card" angle — readable but organic [trueref: image.setAngle]
      const randomAngle = Phaser.Math.Between(-9, 9);
      this.tweens.add({
        targets: cardImg,
        x: tablePos.x, y: tablePos.y, angle: randomAngle,
        duration: NON_CAPTURE_TABLE_TWEEN_MS, ease: 'Back.Out',
        onComplete: () => this.afterMove(playerIdx, card, null, nextState, oldState),
      });
    }
  }

  private afterMove(
    playerIdx: PlayerIndex,
    card: Card,
    capture: Card[] | null,
    nextState: GameState,
    _old: GameState,
  ): void {
    this.updateScoreBar();
    this.relayoutTable();
    if (!nextState.roundOver) {
      this.relayoutHand(0);
    }

    this.setStatus(this.buildMoveOutcomeStatus(playerIdx, card, capture), {
      durationMs: MOVE_OUTCOME_STATUS_MS,
      onExpire: () => {
        if (!this.scene.isActive('GameScene')) {
          return;
        }

        if (nextState.roundOver) {
          this.showRoundEnd();
          return;
        }

        this.nextTurn();
      },
    });
  }

  private relayoutHand(playerIdx: PlayerIndex): void {
    const hand = this.state.players[playerIdx].hand;
    sortHand(hand);
    const positions = this.getHandPositions(playerIdx, hand.length);
    hand.forEach((card, i) => {
      const img = this.cardImages.get(card.id);
      if (img) {
        this.tweens.add({
          targets: img,
          x: positions[i].x,
          y: positions[i].y,
          duration: RELAYOUT_TWEEN_MS,
          ease: 'Cubic.Out',
        });
      }
    });
  }

  private relayoutTable(): void {
    const table = this.state.table;
    if (!table.length) return;
    const positions = this.getTablePositions(table.length);
    table.forEach((card, i) => {
      const img = this.cardImages.get(card.id);
      if (img?.visible) {
        this.tweens.add({
          targets: img,
          x: positions[i].x,
          y: positions[i].y,
          duration: RELAYOUT_TWEEN_MS,
          ease: 'Cubic.Out',
        });
      }
    });
  }

  // ---------------------------------------------------------------------------
  // Particle effects
  // ---------------------------------------------------------------------------

  private spawnCaptureEffect(x: number, y: number, settebello = false): void {
    const color = settebello ? 0xffd700 : 0x66ffcc;
    const count = settebello ? 55 : 28;

    // [trueref: ParticleEmitter v3.60+] this.add.particles returns emitter directly
    const e1 = this.add.particles(x, y, 'particle_glow', {
      lifespan: { min: 350, max: 700 },
      speed: { min: 80, max: 280 },
      scale: { start: 0.9, end: 0 },
      alpha: { start: 1, end: 0 },
      tint: color, gravityY: 100, emitting: false,
    }).setDepth(25);
    e1.explode(count);
    this.time.delayedCall(750, () => e1.destroy());

    if (settebello) {
      const e2 = this.add.particles(x, y, 'particle_dia', {
        lifespan: 1000, speed: { min: 60, max: 200 },
        scale: { start: 1.3, end: 0 }, alpha: { start: 1, end: 0 },
        tint: 0xffd700, gravityY: -30, emitting: false,
      }).setDepth(26);
      e2.explode(30);
      this.time.delayedCall(1100, () => e2.destroy());
    }
  }

  private doScopaEffect(playerIdx: PlayerIndex, onDone: () => void): void {
    const W = this.scale.width;
    const H = this.scale.height;
    const isTeamA = (playerIdx === 0 || playerIdx === 2);

    // [trueref: camera.shake + camera.flash]
    this.cameras.main.shake(600, 0.018);
    this.cameras.main.flash(500, 255, 215, 0);

    // Gold explosion
    const e1 = this.add.particles(W / 2, H / 2, 'particle_glow', {
      lifespan: { min: 700, max: 1400 },
      speed: { min: 200, max: 500 },
      scale: { start: 1.3, end: 0 },
      alpha: { start: 1, end: 0 },
      tint: [0xffd700, 0xffcc00, 0xffffff, 0xff8800],
      gravityY: 50, emitting: false,
    }).setDepth(28);
    e1.explode(100);
    this.time.delayedCall(1500, () => e1.destroy());

    // Shockwave ring
    const ring = this.add.particles(W / 2, H / 2, 'particle_sq', {
      lifespan: 450, speed: { min: 350, max: 650 },
      scale: { start: 0.8, end: 0 }, alpha: { start: 0.7, end: 0 },
      tint: 0xffcc00, gravityY: 0, emitting: false,
    }).setDepth(27);
    ring.explode(60);
    this.time.delayedCall(500, () => ring.destroy());

    // Update scope counter live [already done in updateScoreBar]
    this.updateScoreBar();
    this.flashScoreScope(isTeamA ? 0 : 1);

    // SCOPA! text with bounce-in [trueref: Back.Out ease]
    const player = this.state.players[playerIdx];
    const txt = this.add.text(W / 2, H / 2, 'SCOPA!', {
      fontFamily: 'Georgia, serif', fontSize: '108px', color: '#ffd700',
      stroke: '#000000', strokeThickness: 10,
      resolution: 2,
    }).setOrigin(0.5).setDepth(50).setAlpha(0).setScale(0.2);

    const sub = this.add.text(W / 2, H / 2 + 110, player.name, {
      fontFamily: 'serif', fontSize: '32px',
      color: isTeamA ? '#aaffaa' : '#ffaaaa',
      stroke: '#000', strokeThickness: 3,
      resolution: 2,
    }).setOrigin(0.5).setDepth(50).setAlpha(0);

    this.tweens.add({
      targets: txt,
      alpha: 1, scale: 1,
      duration: 280, ease: 'Back.Out',
    });
    this.tweens.add({ targets: sub, alpha: 1, duration: 300, delay: 120 });

    this.time.delayedCall(1600, () => {
      this.tweens.add({
        targets: [txt, sub], alpha: 0, y: '-=50',
        duration: 280,
        onComplete: () => { txt.destroy(); sub.destroy(); onDone(); },
      });
    });
  }

  private spawnCardTrail(img: Phaser.GameObjects.Image): void {
    const e = this.add.particles(img.x, img.y, 'particle_sm', {
      lifespan: 220, speed: 15,
      scale: { start: 0.6, end: 0 }, alpha: { start: 0.5, end: 0 },
      tint: 0xffffff, follow: img, frequency: 50, quantity: 1,
    }).setDepth(img.depth - 1);
    this.time.delayedCall(420, () => {
      e.stop();
      this.time.delayedCall(250, () => e.destroy());
    });
  }

  /** Denari capture: warm orange shimmer — smaller than scopa, bigger than plain capture. */
  private spawnDenariEffect(x: number, y: number): void {
    const e = this.add.particles(x, y, 'particle_sm', {
      lifespan: { min: 280, max: 550 },
      speed: { min: 60, max: 160 },
      scale: { start: 1.1, end: 0 },
      alpha: { start: 0.9, end: 0 },
      tint: [0xff9900, 0xffcc44, 0xffdd00],
      gravityY: 70, emitting: false,
    }).setDepth(24);
    e.explode(18);
    this.time.delayedCall(650, () => e.destroy());
    // Flash the denari counter in the score bar
    const isTeamA = this.state.currentPlayer === 0 || this.state.currentPlayer === 2;
    const denTxt = isTeamA ? this.hudA.denari : this.hudB.denari;
    this.tweens.add({ targets: denTxt, scaleX: 2.0, scaleY: 2.0, duration: 130, yoyo: true });
  }

  /** Settebello capture: brief "7♦" pop and gold flash — between capture and scopa in weight. */
  private spawnSettebelloFlash(): void {
    const W = this.scale.width;
    const H = this.scale.height;
    const txt = this.add.text(W / 2, H / 2 - 60, '7♦', {
      fontFamily: 'Georgia, serif', fontSize: '64px', color: '#ffd700',
      stroke: '#000', strokeThickness: 7,
      resolution: 2,
    }).setOrigin(0.5).setDepth(50).setAlpha(0).setScale(0.4);

    this.tweens.add({
      targets: txt, alpha: 1, scale: 1,
      duration: 220, ease: 'Back.Out',
    });
    this.time.delayedCall(900, () => {
      this.tweens.add({
        targets: txt, alpha: 0, y: txt.y - 40,
        duration: 200, onComplete: () => txt.destroy(),
      });
    });

    // Diamond burst in gold
    const e = this.add.particles(W / 2, H / 2, 'particle_dia', {
      lifespan: { min: 500, max: 900 },
      speed: { min: 100, max: 300 },
      scale: { start: 1.2, end: 0 }, alpha: { start: 1, end: 0 },
      tint: [0xffd700, 0xffcc00, 0xffffff],
      gravityY: -20, emitting: false,
    }).setDepth(27);
    e.explode(22);
    this.time.delayedCall(1000, () => e.destroy());
  }

  /** Primiera-valuable pick (7,6,1,5): subtle blue-white shimmer at capture point. */
  private spawnPrimieraEffect(x: number, y: number): void {
    const e = this.add.particles(x, y, 'particle_glow', {
      lifespan: { min: 250, max: 480 },
      speed: { min: 25, max: 80 },
      scale: { start: 0.6, end: 0 },
      alpha: { start: 0.8, end: 0 },
      tint: [0xaaddff, 0xffffff, 0x88ccff],
      gravityY: -30, emitting: false,
    }).setDepth(23);
    e.explode(10);
    this.time.delayedCall(530, () => e.destroy());
    // Briefly flash primiera score
    const isTeamA = this.state.currentPlayer === 0 || this.state.currentPlayer === 2;
    const primTxt = isTeamA ? this.hudA.prim : this.hudB.prim;
    this.tweens.add({ targets: primTxt, scaleX: 1.7, scaleY: 1.7, duration: 120, yoyo: true,
      onStart: () => primTxt.setColor('#aaddff'),
      onComplete: () => primTxt.setColor(isTeamA ? '#aaffaa' : '#ffaaaa'),
    });
  }

  // ---------------------------------------------------------------------------
  // Settebello glow on table
  // ---------------------------------------------------------------------------

  private maybeGlowSettebello(): void {
    if (this.renderer.type !== Phaser.WEBGL) return;
    for (const card of this.state.table) {
      const img = this.cardImages.get(card.id);
      if (!img) continue;
      // Clear old FX first
      img.postFX.clear();
      img.postFX.addShadow(2, 6, 0.008, 1.2, 0x000000, 8, 0.5);
      if (card.suit === 'denara' && card.value === 7) {
        // [trueref: postFX.addGlow] pulsing golden halo on settebello
        const glow = img.postFX.addGlow(0xffd700, 0, 0);
        this.tweens.add({
          targets: glow, outerStrength: 14,
          duration: 700, yoyo: true, repeat: -1, ease: 'Sine.InOut',
        });
      }
    }
  }

  // ---------------------------------------------------------------------------
  // Positions
  // ---------------------------------------------------------------------------

  private nextTablePos(table: Card[]): { x: number; y: number } {
    const count = table.length;
    const positions = this.getTablePositions(count);
    return positions[count - 1] ?? this.tableCenter;
  }

  private getPilePos(playerIdx: PlayerIndex): { x: number; y: number } {
    const W = this.scale.width;
    const H = this.scale.height;
    return teamOf(playerIdx) === 0
      ? { x: 50, y: H - 40 }
      : { x: W - 50, y: H - 40 };
  }

  // ---------------------------------------------------------------------------
  // Sound FX (Web Audio)
  // ---------------------------------------------------------------------------

  private startMusic(): void {
    if (!this.audioPreferences.musicEnabled) return;
    if (this.musicStarted) return;
    this.musicStarted = true;
    try {
      this.audioCtx = (this.sound as Phaser.Sound.WebAudioSoundManager).context as AudioContext;
    } catch { return; }
    if (!this.audioCtx) return;
    const ctx = this.audioCtx;

    this.musicGain = ctx.createGain();
    this.musicGain.gain.setValueAtTime(0, ctx.currentTime);
    this.musicGain.gain.linearRampToValueAtTime(0.16, ctx.currentTime + 4);
    this.musicGain.connect(ctx.destination);

    const reverb = ctx.createDelay(0.4);
    reverb.delayTime.value = 0.14;
    const fbk = ctx.createGain();
    fbk.gain.value = 0.38;
    reverb.connect(fbk);
    fbk.connect(reverb);
    fbk.connect(this.musicGain);

    // Bass drone
    [110, 165, 220].forEach((f, i) => {
      const o = ctx.createOscillator();
      const g = ctx.createGain();
      o.type = 'sine';
      o.frequency.value = f;
      g.gain.value = [0.07, 0.045, 0.02][i];
      o.connect(g); g.connect(reverb); o.start();
    });

    // Melodic loop
    const notes = [440,523,587,659,587,523,440,392,440,523,659,784,659,587,523,440];
    const durs  = [0.3,0.2,0.3,0.5,0.2,0.3,0.6,0.3,0.2,0.4,0.3,0.2,0.3,0.4,0.2,0.8];
    const totalDur = durs.reduce((s, d) => s + d + 0.05, 0);
    const scheduleLoop = (t0: number) => {
      if (!this.scene.isActive('GameScene')) return;
      let t = t0;
      notes.forEach((freq, i) => {
        const o = ctx.createOscillator();
        const env = ctx.createGain();
        o.type = 'triangle';
        o.frequency.value = freq;
        env.gain.setValueAtTime(0, t);
        env.gain.linearRampToValueAtTime(0.06, t + 0.02);
        env.gain.exponentialRampToValueAtTime(0.001, t + durs[i]);
        o.connect(env); env.connect(reverb);
        o.start(t); o.stop(t + durs[i] + 0.05);
        t += durs[i] + 0.05;
      });
      this.time.delayedCall((totalDur + 0.8) * 1000, () => {
        if (this.scene.isActive('GameScene')) scheduleLoop(ctx.currentTime);
      });
    };
    scheduleLoop(ctx.currentTime + 1.8);

    // Chord stabs
    const scheduleChords = () => {
      if (!this.scene.isActive('GameScene')) return;
      const now = ctx.currentTime + 0.05;
      [220, 261, 329].forEach(f => {
        const o = ctx.createOscillator(); const g = ctx.createGain();
        o.type = 'sine'; o.frequency.value = f;
        g.gain.setValueAtTime(0, now);
        g.gain.linearRampToValueAtTime(0.038, now + 0.06);
        g.gain.exponentialRampToValueAtTime(0.001, now + 1.4);
        o.connect(g); g.connect(reverb); o.start(now); o.stop(now + 1.5);
      });
      this.time.delayedCall(4200, scheduleChords);
    };
    this.time.delayedCall(2400, scheduleChords);
  }

  private playSfx(type: 'card_play' | 'capture' | 'scopa' | 'settebello'): void {
    if (!this.audioPreferences.effectsEnabled) return;
    if (!this.audioCtx) return;
    const ctx = this.audioCtx;
    const now = ctx.currentTime;
    const note = (freq: number, t: number, dur: number, vol: number, type: OscillatorType = 'triangle') => {
      const o = ctx.createOscillator();
      const g = ctx.createGain();
      o.type = type;
      o.frequency.value = freq;
      g.gain.setValueAtTime(vol, t);
      g.gain.exponentialRampToValueAtTime(0.001, t + dur);
      o.connect(g); g.connect(ctx.destination);
      o.start(t); o.stop(t + dur + 0.05);
    };
    switch (type) {
      case 'card_play':
        note(280, now, 0.12, 0.12, 'sine');
        note(200, now + 0.04, 0.1, 0.06, 'sine');
        break;
      case 'capture':
        note(520, now, 0.08, 0.1);
        note(720, now + 0.07, 0.1, 0.1);
        break;
      case 'scopa':
        [440,554,659,880].forEach((f, i) => note(f, now + i * 0.07, 0.3, 0.14));
        break;
      case 'settebello':
        [440,523,659,784,1047].forEach((f, i) => note(f, now + i * 0.06, 0.5, 0.12, 'sine'));
        break;
    }
  }

  private stopMusic(): void {
    if (!this.audioPreferences.musicEnabled) return;
    if (this.musicGain && this.audioCtx) {
      this.musicGain.gain.linearRampToValueAtTime(0, this.audioCtx.currentTime + 1.5);
    }
  }

  // ---------------------------------------------------------------------------
  // Round end / game over
  // ---------------------------------------------------------------------------

  private showRoundEnd(): void {
    const W = this.scale.width;
    const H = this.scale.height;
    const bd = getScoreBreakdown(this.state);
    const t0 = this.state.teamScores[0];
    const t1 = this.state.teamScores[1];

    const overlay = this.add.rectangle(0, 0, W, H, 0x000000, 0.82).setOrigin(0).setDepth(30);

    // Panel with rounded rect [trueref: fillRoundedRect]
    const panel = this.add.graphics().setDepth(31);
    panel.fillStyle(0x07200c, 1);
    panel.fillRoundedRect(W / 2 - 280, H / 2 - 210, 560, 420, 16);
    panel.lineStyle(2, 0xffd700, 0.7);
    panel.strokeRoundedRect(W / 2 - 280, H / 2 - 210, 560, 420, 16);

    const lines: Array<[string, string]> = [
      [`Fine della mano ${this.state.roundNumber ?? 1}`, '#ffd700'],
      ['', ''],
      [`Squadra tua   +${t0.roundPoints} pt   →   ${t0.totalPoints} totali`, '#aaffaa'],
      [`Avversari     +${t1.roundPoints} pt   →   ${t1.totalPoints} totali`, '#ffaaaa'],
      ['', ''],
      [`Carte     A=${t0.cards}  B=${t1.cards}   ${pointStr(bd.cartePoint)}`,     '#ffffff'],
      [`Denari    A=${t0.denari}  B=${t1.denari}   ${pointStr(bd.denariPoint)}`,   '#ffdd88'],
      [`Settebello  →  ${bd.settebelloPoint === 0 ? 'squadra tua' : 'avversari'}`, '#ffd700'],
      [`Primiera  A=${t0.primiera}  B=${t1.primiera}   ${pointStr(bd.primieraPoint)}`, '#aaddff'],
      [`Scope     A=${bd.scopeTeam0}  B=${bd.scopeTeam1}`,                         '#ccffcc'],
    ];

    lines.forEach(([line, color], i) => {
      this.add.text(W / 2, H / 2 - 190 + i * 34, line, this.withHiResText({
        fontFamily: i === 0 ? 'Georgia, serif' : 'monospace',
        fontSize: i === 0 ? '28px' : '16px',
        color,
      })).setOrigin(0.5).setDepth(32);
    });

    const outcome = getMatchOutcome(this.state.teamScores);
    const gameOver = !outcome.continueMatch;
    const btnLabel = gameOver ? 'Fine Partita' : 'Prossima Mano';

    const btnG = this.add.graphics().setDepth(32);
    btnG.fillStyle(0xffd700, 1);
    btnG.fillRoundedRect(W / 2 - 110, H / 2 + 185, 220, 44, 10);
    const btnZone = this.add.zone(W / 2, H / 2 + 207, 220, 44)
      .setInteractive({ useHandCursor: true }).setDepth(33);
    this.add.text(W / 2, H / 2 + 207, btnLabel, {
      fontFamily: 'Georgia, serif', fontSize: '20px', color: '#0a2e10',
      resolution: 2,
    }).setOrigin(0.5).setDepth(34);

    btnG.on('pointerover', () => { btnG.clear(); btnG.fillStyle(0xffec6e, 1); btnG.fillRoundedRect(W / 2 - 110, H / 2 + 185, 220, 44, 10); });
    btnG.on('pointerout',  () => { btnG.clear(); btnG.fillStyle(0xffd700, 1); btnG.fillRoundedRect(W / 2 - 110, H / 2 + 185, 220, 44, 10); });
    btnZone.on('pointerover', () => btnG.emit('pointerover'));
    btnZone.on('pointerout',  () => btnG.emit('pointerout'));
    btnZone.on('pointerdown', () => {
      [overlay, panel, btnG, btnZone].forEach(o => o.destroy());
      this.children.list.filter((c: any) => c.depth >= 31).forEach(c => c.destroy());
      gameOver ? this.showGameOver() : this.startNewRound();
    });
  }

  private showGameOver(): void {
    const W = this.scale.width;
    const H = this.scale.height;
    const t0 = this.state.teamScores[0];
    const t1 = this.state.teamScores[1];
    const outcome = getMatchOutcome(this.state.teamScores);
    const win = outcome.winner === 0;
    this.stopMusic();

    // Victory confetti
    const confetti = this.add.particles(W / 2, H / 2, 'particle_glow', {
      lifespan: 2500, speed: { min: 80, max: 450 },
      scale: { start: 1.1, end: 0 }, tint: [0xffd700, 0xffffff, 0x00ff88, 0xff8800],
      gravityY: 90, frequency: 30,
    }).setDepth(39);
    this.time.delayedCall(3500, () => confetti.destroy());

    this.add.rectangle(0, 0, W, H, 0x000000, 0.88).setOrigin(0).setDepth(40);

    // Rounded panel
    const pg = this.add.graphics().setDepth(41);
    pg.fillStyle(0x04150a, 1);
    pg.fillRoundedRect(W / 2 - 220, H / 2 - 150, 440, 310, 20);
    pg.lineStyle(3, 0xffd700, 0.8);
    pg.strokeRoundedRect(W / 2 - 220, H / 2 - 150, 440, 310, 20);

    this.add.text(W / 2, H / 2 - 110, 'PARTITA CONCLUSA', {
      fontFamily: 'Georgia, serif', fontSize: '44px', color: '#ffd700',
      stroke: '#000', strokeThickness: 6,
      resolution: 2,
    }).setOrigin(0.5).setDepth(42);
    this.add.text(W / 2, H / 2 - 30, win ? 'Vince la tua squadra' : 'Vincono gli avversari', {
      fontFamily: 'serif', fontSize: '26px',
      color: win ? '#aaffaa' : '#ffaaaa',
      resolution: 2,
    }).setOrigin(0.5).setDepth(42);
    this.add.text(W / 2, H / 2 + 35, `${t0.totalPoints}  —  ${t1.totalPoints}`, {
      fontFamily: 'Georgia, serif', fontSize: '50px', color: '#ffd700',
      resolution: 2,
    }).setOrigin(0.5).setDepth(42);

    const bz = this.add.zone(W / 2, H / 2 + 115, 230, 48)
      .setInteractive({ useHandCursor: true }).setDepth(44);
    const bg2 = this.add.graphics().setDepth(43);
    const drawBtn = (c: number) => { bg2.clear(); bg2.fillStyle(c, 1); bg2.fillRoundedRect(W / 2 - 115, H / 2 + 91, 230, 48, 12); };
    drawBtn(0xffd700);
    this.add.text(W / 2, H / 2 + 115, 'NUOVA PARTITA', {
      fontFamily: 'Georgia, serif', fontSize: '21px', color: '#0a2e10',
      resolution: 2,
    }).setOrigin(0.5).setDepth(44);
    bz.on('pointerover', () => drawBtn(0xffec6e));
    bz.on('pointerout',  () => drawBtn(0xffd700));
    bz.on('pointerdown', () => this.scene.restart());
  }

  private startNewRound(): void {
    const totals = this.state.teamScores.map(t => t.totalPoints);
    const nextRound = (this.state.roundNumber ?? 1) + 1;
    const matchStartingPlayer = this.state.matchStartingPlayer;
    const nextDealer = nextPlayer(this.state.dealer);
    for (const img of this.cardImages.values()) img.destroy();
    this.cardImages.clear();
    this.tracker.reset();
    this.inference.reset();
    this.state = createInitialState(nextDealer);
    this.state.matchStartingPlayer = matchStartingPlayer;
    this.state.teamScores[0].totalPoints = totals[0];
    this.state.teamScores[1].totalPoints = totals[1];
    this.state.roundNumber = nextRound;
    this.dealAnimation(() => {
      this.updateScoreBar();
      this.nextTurn();
    });
  }

  private setStatus(
    msg: string,
    options: {
      persist?: boolean;
      durationMs?: number;
      onExpire?: () => void;
    } = {},
  ): void {
    const { persist = false, durationMs, onExpire } = options;

    this.clearStatusTimer();
    this.statusText.setText(msg);

    if (persist) {
      this.persistentStatusText = msg;
    }

    if (durationMs === undefined) {
      return;
    }

    this.statusTimer = this.time.delayedCall(durationMs, () => {
      this.statusTimer = null;
      onExpire?.();
    });
  }
}

// ---------------------------------------------------------------------------
// Utils
// ---------------------------------------------------------------------------

function cardName(card: Card): string {
  const s: Record<string, string> = { bastoni: 'Bastoni', coppe: 'Coppe', denara: 'Denari', spade: 'Spade' };
  const v: Record<number, string> = { 1: 'Asso', 8: 'Fante', 9: 'Cavallo', 10: 'Re' };
  return `${v[card.value] ?? card.value} di ${s[card.suit]}`;
}

function pointStr(p: 0 | 1 | null): string {
  return p === null ? '(pari)' : p === 0 ? '→ squadra tua' : '→ avversari';
}
