import type { Enclos } from '@domain/entities/Enclos';
import type { Dragodinde } from '@domain/entities/Dragodinde';
import type { GaugeType, GaugeDef } from '@domain/value-objects/GaugeType';
import { GAUGE_DEFS, STAT_DEFS } from '@domain/value-objects/GaugeType';
import { tierRate } from '@domain/value-objects/Tier';
import { elapsed, timeToGain, computeGaugeState } from '@domain/services/GaugeCalculator';
import type { GaugeRecharge } from '@domain/services/GaugeCalculator';
import { xpForLevel, levelFromXp } from '@domain/value-objects/XpTable';

export interface GaugeLiveResult {
  estStat: number;
  done: boolean;
  progPct: number;
  totalSec: number;
  cntDown: number;
  startGl: number;
  curGl: number;
  liveText: string;
  deltaText: string;
}

/** Points jusqu'au cap absolu de la stat (gel de la jauge).
 *  Mangeoire : gel uniquement à niveau 200. La cible XP (< 200) ne gèle PAS la jauge. */
function ptsToAbsCap(startSt: number, def: GaugeDef): number {
  if (def.isXp) return Math.max(0, xpForLevel(200) - xpForLevel(startSt));
  const sd = STAT_DEFS[def.stat as keyof typeof STAT_DEFS];
  return def.dir > 0 ? Math.max(0, sd.max - startSt) : Math.max(0, startSt - sd.min);
}

function rechargesFor(enc: Enclos, gid: GaugeType): GaugeRecharge[] {
  return enc.timer.gaugeRecharges?.[gid] ?? [];
}

/**
 * Temps écoulé "vivant" : utilisé pour TOUTES les jauges.
 * Quand la session s'est terminée automatiquement (alerted.__done__),
 * continue en temps réel pour que toutes les jauges se vident en fond.
 * Une pause manuelle (running=false sans alerted.__done__) fige normalement.
 */
export function elapsedLive(enc: Enclos): number {
  if (!enc.timer.startTime) return 0;
  if (enc.alerted['__done__']) {
    return Math.floor((Date.now() - enc.timer.startTime - enc.timer.pausedMs) / 1000);
  }
  return elapsed(enc.timer);
}

export function computeGaugeLive(enc: Enclos, dd: Dragodinde, gid: GaugeType, el: number, started: boolean): GaugeLiveResult {
  const def = GAUGE_DEFS[gid];
  const startGl = started ? (enc.timer.snapGauges[gid] ?? enc.gaugeLevels[gid]) : enc.gaugeLevels[gid];
  const startSt = started
    ? (enc.timer.snapStats[dd.id]?.[def.stat] ?? dd.stats[def.stat as keyof typeof dd.stats])
    : dd.stats[def.stat as keyof typeof dd.stats];

  let target = dd.targets[gid];
  if (def.isXp) target = dd.levelTarget ?? 200;
  if (def.stat === 'serenite' && dd.sereniteTarget != null) target = dd.sereniteTarget;

  // Toutes les jauges utilisent le temps réel si la session s'est terminée automatiquement
  const elLive = started ? elapsedLive(enc) : 0;
  const recharges = started ? rechargesFor(enc, gid) : [];
  const pts = ptsToAbsCap(startSt as number, def);
  const { gained, curGl } = started
    ? computeGaugeState(startGl, recharges, pts, elLive)
    : { gained: 0, curGl: startGl };

  // Quand la jauge est vide, le taux est 0 (plus aucun point gagné)
  const curRate = curGl > 0 ? tierRate(curGl) : 0;

  if (def.isXp) {
    const startXP = xpForLevel(startSt as number);
    const targetXP = xpForLevel(target);
    const xpNeeded = Math.max(0, targetXP - startXP);
    const xpGained = gained;
    const xpRestante = Math.max(0, xpNeeded - xpGained);
    const estLevel = levelFromXp(startXP + xpGained);
    const done = estLevel >= target;
    const progPct = xpNeeded > 0 ? Math.min(100, (xpGained / xpNeeded) * 100) : (done ? 100 : 0);
    const totalSec = xpNeeded > 0 ? timeToGain(startGl, Math.min(xpNeeded, startGl)) : 0;
    const cntDown = done ? 0 : (curGl > 0 ? Math.max(0, timeToGain(curGl, Math.min(xpRestante, curGl)) - (elLive % 10)) : Infinity);
    const deltaText = curGl > 0 ? `+${curRate} xp` : 'Jauge vide';
    return { estStat: estLevel, done, progPct, totalSec, cntDown, startGl, curGl,
      liveText: `NIV. ${estLevel}`, deltaText };
  } else {
    const sd = STAT_DEFS[def.stat as keyof typeof STAT_DEFS];
    const rawStat = (startSt as number) + def.dir * gained;
    const estStat = Math.min(sd.max, Math.max(sd.min, rawStat));
    const ptsNeeded = def.dir > 0 ? Math.max(0, target - (startSt as number)) : Math.max(0, (startSt as number) - target);
    const totalSec = timeToGain(startGl, ptsNeeded);
    const done = def.dir > 0 ? estStat >= target : estStat <= target;
    // Countdown basé sur les points restants et le niveau de jauge actuel
    const ptsRemaining = Math.max(0, ptsNeeded - gained);
    const cntDown = done ? 0 : (curGl > 0 ? timeToGain(curGl, Math.min(ptsRemaining, curGl)) : Infinity);
    const progPct = ptsNeeded > 0 ? Math.min(100, (Math.abs(estStat - (startSt as number)) / ptsNeeded) * 100) : (done ? 100 : 0);
    const deltaText = curGl > 0 ? (def.dir > 0 ? `+${curRate}` : `-${curRate}`) : 'Jauge vide';
    return { estStat, done, progPct, totalSec, cntDown, startGl, curGl,
      liveText: Math.round(estStat).toLocaleString('fr'), deltaText };
  }
}

/**
 * Niveau actuel de la jauge pour l'affichage global de l'enclos.
 * Utilise le temps réel si la session s'est terminée automatiquement.
 */
export function enclosGaugeCurGl(enc: Enclos, gid: GaugeType): number {
  const startGl = enc.timer.snapGauges[gid] ?? enc.gaugeLevels[gid];
  if (!enc.timer.startTime) return enc.gaugeLevels[gid];

  const def = GAUGE_DEFS[gid];
  const recharges = rechargesFor(enc, gid);
  const elLive = elapsedLive(enc);

  if (!enc.dragodindes.length) return computeGaugeState(startGl, recharges, Infinity, elLive).curGl;

  let maxEffectiveEl = -1;
  let resultCurGl = startGl;

  for (const dd of enc.dragodindes) {
    const startSt = (enc.timer.snapStats[dd.id]?.[def.stat] ?? dd.stats[def.stat as keyof typeof dd.stats]) as number;
    const pts = ptsToAbsCap(startSt, def);
    if (!isFinite(pts)) return computeGaugeState(startGl, recharges, Infinity, elLive).curGl;
    const { curGl, effectiveEl } = computeGaugeState(startGl, recharges, pts, elLive);
    if (effectiveEl > maxEffectiveEl) { maxEffectiveEl = effectiveEl; resultCurGl = curGl; }
  }

  return resultCurGl;
}

export interface EnclosGlobalStateResult {
  globalMax: number;
  allDone: boolean;
  started: boolean;
  el: number;
  ddDone: number;
}

export function enclosGlobalState(enc: Enclos): EnclosGlobalStateResult {
  const el = elapsed(enc.timer);
  const started = !!enc.timer.startTime;
  if (!enc.activeGauges.length || !enc.dragodindes.length)
    return { globalMax: 0, allDone: false, started, el, ddDone: 0 };

  let globalMax = 0, allEnclosDone = true, ddDone = 0;
  enc.dragodindes.forEach(dd => {
    let ddAllDone = true;
    enc.activeGauges.forEach(gid => {
      const r = computeGaugeLive(enc, dd, gid, el, started);
      if (!r.done) {
        ddAllDone = false;
        allEnclosDone = false;
      }
      if (r.cntDown > globalMax) globalMax = r.cntDown;
    });
    if (ddAllDone) ddDone++;
  });

  return { globalMax, allDone: allEnclosDone, started, el, ddDone };
}

export function calcSerenEta(enc: Enclos, dd: Dragodinde): string {
  const target = dd.sereniteTarget;
  if (target === null || target === undefined) return '—';
  const cur = dd.stats.serenite;
  const diff = target - cur;
  if (diff === 0) return '✅';
  const needUp = diff > 0;
  const gid: GaugeType = needUp ? 'caresseur' : 'baffeur';
  if (!enc.activeGauges.includes(gid))
    return needUp ? '<span class="eta-need" title="Activer le Caresseur">➕</span>' : '<span class="eta-need" title="Activer le Baffeur">➖</span>';
  const gl = enc.gaugeLevels[gid] || 0;
  const sec = timeToGain(gl, Math.abs(diff));
  return sec === Infinity ? '∞' : '~' + fmtShort(sec);
}

export function calcSerenEtaLive(enc: Enclos, dd: Dragodinde, el: number, started: boolean): string {
  const target = dd.sereniteTarget;
  if (target === null || target === undefined) return '—';
  const cur = dd.stats.serenite;
  const diff = target - cur;
  if (diff === 0) return '✅';
  const needUp = diff > 0;
  const gid: GaugeType = needUp ? 'caresseur' : 'baffeur';
  if (!enc.activeGauges.includes(gid))
    return needUp ? '<span class="eta-need" title="Activer le Caresseur">➕</span>' : '<span class="eta-need" title="Activer le Baffeur">➖</span>';
  const startSt = started ? (enc.timer.snapStats[dd.id]?.['serenite'] ?? dd.stats.serenite) : dd.stats.serenite;
  const startGl = started ? (enc.timer.snapGauges[gid] ?? enc.gaugeLevels[gid]) : enc.gaugeLevels[gid];
  const def = GAUGE_DEFS[gid];
  const recharges = started ? rechargesFor(enc, gid) : [];
  const pts = ptsToAbsCap(startSt, def);
  const elLive = started ? elapsedLive(enc) : 0;
  const { gained, curGl } = started ? computeGaugeState(startGl, recharges, pts, elLive) : { gained: 0, curGl: startGl };
  const curStat = Math.min(5000, Math.max(-5000, startSt + def.dir * gained));
  if ((needUp && curStat >= target) || (!needUp && curStat <= target)) return '✅';
  const remaining = Math.abs(target - curStat);
  const sec = timeToGain(curGl, remaining);
  return sec === Infinity ? '∞' : '~' + fmtShort(sec);
}

export function calcLevelEta(enc: Enclos, dd: Dragodinde): string {
  const target = dd.levelTarget ?? 200;
  if (dd.stats.xp >= target) return '✅';
  if (!enc.activeGauges.includes('mangeoire')) return '<span class="eta-need" title="Activer la Mangeoire">🍖</span>';
  const gl = enc.gaugeLevels.mangeoire || 0;
  const xpNeeded = Math.max(0, xpForLevel(target) - xpForLevel(dd.stats.xp));
  const sec = timeToGain(gl, xpNeeded);
  return sec === Infinity ? '∞' : '~' + fmtShort(sec);
}

export function calcLevelEtaLive(enc: Enclos, dd: Dragodinde, el: number, started: boolean): string {
  const target = dd.levelTarget ?? 200;
  if (!enc.activeGauges.includes('mangeoire')) return '<span class="eta-need" title="Activer la Mangeoire">🍖</span>';
  const startXp = started ? (enc.timer.snapStats[dd.id]?.['xp'] ?? dd.stats.xp) : dd.stats.xp;
  const startGl = started ? (enc.timer.snapGauges['mangeoire'] ?? enc.gaugeLevels.mangeoire) : enc.gaugeLevels.mangeoire;
  const def = GAUGE_DEFS['mangeoire'];
  const recharges = started ? rechargesFor(enc, 'mangeoire') : [];
  const pts = ptsToAbsCap(startXp, def);
  const elLive = started ? elapsedLive(enc) : 0;
  const { gained, curGl } = started ? computeGaugeState(startGl, recharges, pts, elLive) : { gained: 0, curGl: startGl };
  const estLevel = levelFromXp(xpForLevel(startXp) + gained);
  if (estLevel >= target) return '✅';
  const xpRemaining = Math.max(0, xpForLevel(target) - xpForLevel(startXp) - gained);
  const sec = timeToGain(curGl, Math.min(xpRemaining, curGl));
  return sec === Infinity ? '∞' : '~' + fmtShort(sec);
}

/**
 * Estimation du temps pour passer du niveau actuel au niveau 200,
 * en supposant que la jauge reste au tier courant en continu.
 * Retourne une chaîne formatée (~3j 8h, ~12h 30m, ~45m, ✅, ∞).
 */
export function calcLevel200EtaLive(enc: Enclos, dd: Dragodinde, el: number, started: boolean): string {
  if (!enc.activeGauges.includes('mangeoire')) return '';

  const startXp = started ? (enc.timer.snapStats[dd.id]?.['xp'] ?? dd.stats.xp) : dd.stats.xp;
  const startGl  = started ? (enc.timer.snapGauges['mangeoire'] ?? enc.gaugeLevels.mangeoire) : enc.gaugeLevels.mangeoire;
  const def      = GAUGE_DEFS['mangeoire'];
  const recharges = started ? rechargesFor(enc, 'mangeoire') : [];
  const pts      = ptsToAbsCap(startXp, def);
  const elLive   = started ? elapsedLive(enc) : 0;
  const { gained, curGl } = started
    ? computeGaugeState(startGl, recharges, pts, elLive)
    : { gained: 0, curGl: startGl };

  const currentLevel = levelFromXp(xpForLevel(startXp) + gained);
  if (currentLevel >= 200) return '✅';

  const xpNeeded = Math.max(0, xpForLevel(200) - (xpForLevel(startXp) + gained));
  const rate = tierRate(curGl); // pts par tick (10s), basé sur le tier courant
  if (rate <= 0 || curGl <= 0) return '∞';

  const seconds = Math.ceil(xpNeeded / rate) * 10;
  return fmtEta200(seconds);
}

function fmtEta200(s: number): string {
  if (!isFinite(s) || s <= 0) return '∞';
  const d = Math.floor(s / 86400);
  const h = Math.floor((s % 86400) / 3600);
  const m = Math.floor((s % 3600) / 60);
  if (d > 0) return `~${d}j ${h}h`;
  if (h > 0) return `~${h}h ${String(m).padStart(2, '0')}m`;
  return `~${m}m`;
}

function fmtShort(s: number): string {
  if (!isFinite(s) || s < 0) return '—';
  const h = Math.floor(s / 3600);
  const m = Math.floor((s % 3600) / 60);
  const ss = Math.floor(s % 60);
  return h > 0 ? `${h}h ${String(m).padStart(2, '0')}m ${String(ss).padStart(2, '0')}s`
    : m > 0 ? `${m}m ${String(ss).padStart(2, '0')}s` : `${ss}s`;
}
