import type { CommandBus } from '@application/handlers/CommandBus';
import type { QueryBus } from '@application/handlers/QueryBus';
import type { Enclos } from '@domain/entities/Enclos';
import type { Dragodinde } from '@domain/entities/Dragodinde';
import type { GaugeType, StatType } from '@domain/value-objects/GaugeType';
import { GAUGE_DEFS, STAT_DEFS } from '@domain/value-objects/GaugeType';
import { tierNum, tierRate } from '@domain/value-objects/Tier';
import { computeGaugeLive, calcSerenEtaLive, calcLevelEtaLive, calcLevel200EtaLive, elapsedLive } from '@presentation/helpers/gauge-live';
import { esc, fmt, fmtClock } from '@presentation/helpers/format';
import { Toast } from './Toast';
import { ConfirmModal } from './ConfirmModal';
import { UndoManager } from '@presentation/services/UndoManager';

interface StatPillDef {
  key: StatType;
  icon: string;
  color: string;
  min: number;
  max: number;
}

const STAT_PILLS: StatPillDef[] = [
  { key: 'serenite',  icon: 'sentiment_satisfied_alt', color: '96,165,250',  min: -5000, max: 5000 },
  { key: 'endurance', icon: 'bolt',                    color: '250,204,21',  min: 0,     max: 20000 },
  { key: 'maturite',  icon: 'water_drop',              color: '34,211,238',  min: 0,     max: 20000 },
  { key: 'amour',     icon: 'favorite',                color: '248,113,113', min: 0,     max: 20000 },
  { key: 'xp',        icon: 'star',                    color: '254,240,138', min: 1,     max: 200 },
];

export class DragodindeCard {
  private el: HTMLElement | null = null;
  private enclosId = 0;
  private ddId = 0;
  private lastTick = -1;

  constructor(
    private commandBus: CommandBus,
    private queryBus: QueryBus,
    private onReorder?: () => void,
  ) {}

  render(container: HTMLElement, enclosId: number, ddId: number): void {
    this.enclosId = enclosId;
    this.ddId = ddId;

    this.el = document.createElement('div');
    this.el.className = 'dd-card enc-dd-card';
    this.el.id = `ddc-${enclosId}-${ddId}`;
    this.el.draggable = true;

    this.el.addEventListener('dragstart', (e) => {
      e.dataTransfer!.effectAllowed = 'move';
      e.dataTransfer?.setData('text/dd-id', String(ddId));
      e.dataTransfer?.setData('text/enc-id', String(enclosId));
      // Délai pour que le navigateur capture le snapshot avant d'appliquer l'opacité
      requestAnimationFrame(() => this.el!.classList.add('dragging'));
    });
    this.el.addEventListener('dragend', () => {
      this.el!.classList.remove('dragging');
      this.el!.classList.remove('drag-over');
    });
    this.el.addEventListener('dragover', (e) => {
      e.preventDefault();
      e.dataTransfer!.dropEffect = 'move';
      this.el!.classList.add('drag-over');
    });
    this.el.addEventListener('dragleave', (e) => {
      // Ignorer si on entre dans un élément enfant
      if (this.el!.contains(e.relatedTarget as Node)) return;
      this.el!.classList.remove('drag-over');
    });
    this.el.addEventListener('drop', (e) => {
      e.preventDefault();
      this.el!.classList.remove('drag-over');
      const srcDdId = e.dataTransfer?.getData('text/dd-id');
      const srcEncId = e.dataTransfer?.getData('text/enc-id');
      if (srcEncId === String(enclosId) && srcDdId && srcDdId !== String(ddId)) {
        this.commandBus.execute({
          type: 'reorder-dragodinde',
          enclosId,
          fromDdId: Number(srcDdId),
          toDdId: ddId,
        });
        this.onReorder?.();
      }
    });

    container.appendChild(this.el);
    this.renderInner();
  }

  private renderInner(): void {
    if (!this.el) return;
    const enc = this.queryBus.execute<Enclos>({ type: 'get-enclos-detail', enclosId: this.enclosId });
    const dd = enc.dragodindes.find(d => d.id === this.ddId);
    if (!dd) return;

    const eId = this.enclosId;
    const dId = this.ddId;

    /* stat pills */
    const pillsHtml = STAT_PILLS.map(sp => {
      const val = dd.stats[sp.key];
      const atMax = val >= sp.max;
      const atMin = sp.key === 'serenite' && val <= sp.min;
      const atLimit = atMax || atMin;
      return `<div class="dd-stat-pill enc-dd-stat-badge${atLimit ? ' at-max' : ''}" style="border-color:rgba(${sp.color},0.5)${atLimit ? `;box-shadow:0 0 8px rgba(${sp.color},0.5);background:rgba(${sp.color},0.18)` : ''}">
        <span class="material-symbols-outlined enc-dd-stat-icon" style="color:rgb(${sp.color})">${sp.icon}</span>
        <input type="number" class="pill-input enc-dd-stat-input"
          id="pstat-${eId}-${dId}-${sp.key}"
          data-stat="${sp.key}" data-prev="${val}"
          min="${sp.min}" max="${sp.max}"
          value="${val}">
        <span class="pill-delta" id="pill-delta-${eId}-${dId}-${sp.key}"></span>
      </div>`;
    }).join('');

    /* Mapping icônes Material Symbols pour boutons jauges */
    const GAUGE_MS_ICONS: Partial<Record<string, string>> = {
      baffeur: 'remove', caresseur: 'add', foudroyeur: 'bolt',
      abreuvoir: 'water_drop', dragofesse: 'favorite',
    };

    /* active gauge blocks */
    const gaugeBlocksHtml = enc.activeGauges.map(gid => {
      const def = GAUGE_DEFS[gid];
      if (gid === 'mangeoire') {
        return `<div class="enc-dd-gauge-block enc-dd-gauge-xp" data-gid="${gid}">
          <div class="enc-dd-xp-main">
            <div class="enc-dd-xp-left">
              <span class="enc-dd-xp-niv" id="slv-${eId}-${dId}-${gid}" style="color:var(${def.cssVar})">NIV. 1</span>
              <span class="enc-dd-xp-sub">XP <span id="eta200-pct-${eId}-${dId}">0%</span></span>
            </div>
            <span class="live-cd enc-dd-xp-cd" id="scd-${eId}-${dId}-${gid}">--:--:--</span>
          </div>
          <span id="eta200-${eId}-${dId}" class="enc-dd-xp-eta200">→ NIV. 200 : —</span>
          <div class="enc-dd-bar-bg">
            <div class="enc-dd-bar-fill" id="eta200-bar-${eId}-${dId}" style="width:0%;background:var(${def.cssVar})"></div>
          </div>
          <div class="enc-dd-bar-bg" style="margin-top:4px">
            <div class="enc-dd-bar-fill" id="spb-${eId}-${dId}-${gid}" style="width:0%;background:var(${def.cssVar})"></div>
          </div>
          <span class="live-delta" id="sdelta-${eId}-${dId}-${gid}" style="display:none"></span>
        </div>`;
      }
      const msIcon = GAUGE_MS_ICONS[gid] ?? 'circle';
      return `<div class="enc-dd-gauge-block" data-gid="${gid}">
        <div class="enc-dd-gauge-btn">
          <span class="enc-dd-gauge-btn-left">
            <span class="material-symbols-outlined" style="font-size:14px">${msIcon}</span>
            <span class="enc-dd-gauge-btn-name">${def.label.toUpperCase()}</span>
          </span>
          <span class="live-val" id="slv-${eId}-${dId}-${gid}" style="display:none"></span>
          <span class="live-delta" id="sdelta-${eId}-${dId}-${gid}" style="display:none"></span>
          <span class="live-cd enc-dd-gauge-btn-cd" id="scd-${eId}-${dId}-${gid}">--:--:--</span>
        </div>
        <div class="enc-dd-bar-bg">
          <div class="enc-dd-bar-fill" id="spb-${eId}-${dId}-${gid}" style="width:0%;background:var(${def.cssVar})"></div>
        </div>
      </div>`;
    }).join('');

    this.el.innerHTML = `
      <div class="enc-dd-done-badge" id="dd-done-${eId}-${dId}" style="display:none">✓ TERMINÉ</div>
      <div class="enc-dd-card-head">
        <span class="dd-drag-handle enc-dd-drag-handle" title="Déplacer">⠿</span>
        <input type="text" class="dd-name enc-dd-name-input" value="${esc(dd.name)}"
          data-prev="${esc(dd.name)}" id="ddname-${eId}-${dId}">
        <button class="dd-del enc-dd-del-btn" title="Supprimer">
          <span class="material-symbols-outlined" style="font-size:16px">close</span>
        </button>
      </div>
      <div class="enc-dd-body">
        <div class="enc-dd-stats-grid">${pillsHtml}</div>
        <div class="enc-dd-cibles">
          <div class="enc-dd-cible-row">
            <div class="enc-dd-cible-left">
              <span class="material-symbols-outlined" style="font-size:18px;color:rgb(96,165,250)">sentiment_satisfied_alt</span>
              <span class="enc-dd-cible-lbl">Cible</span>
            </div>
            <div class="enc-dd-cible-right">
              <input type="number" class="enc-dd-cible-inp" id="ser-tgt-${eId}-${dId}"
                min="${enc.activeGauges.includes('baffeur') ? '-5000' : '0'}"
                max="${enc.activeGauges.includes('caresseur') ? '5000' : '0'}"
                value="${dd.sereniteTarget ?? ''}" placeholder="${enc.activeGauges.includes('baffeur') ? '-5000…0' : enc.activeGauges.includes('caresseur') ? '0…5000' : '—'}">
              <button class="enc-dd-cible-clr" id="ser-clr-${eId}-${dId}" title="Réinitialiser" ${dd.sereniteTarget == null ? 'style="visibility:hidden"' : ''}>✕</button>
              <span class="enc-dd-cible-eta" id="ser-eta-${eId}-${dId}">—</span>
            </div>
          </div>
          <div class="enc-dd-cible-row">
            <div class="enc-dd-cible-left">
              <span class="material-symbols-outlined" style="font-size:18px;color:rgb(234,179,8)">stars</span>
              <span class="enc-dd-cible-lbl">Niveau</span>
            </div>
            <div class="enc-dd-cible-right">
              <input type="number" class="enc-dd-cible-inp" id="lvl-tgt-${eId}-${dId}"
                min="1" max="200"
                value="${dd.levelTarget ?? ''}" placeholder="—">
              <button class="enc-dd-cible-clr" id="lvl-clr-${eId}-${dId}" title="Réinitialiser" ${dd.levelTarget == null ? 'style="visibility:hidden"' : ''}>✕</button>
              <span class="enc-dd-cible-eta" id="lvl-eta-${eId}-${dId}">—</span>
            </div>
          </div>
        </div>
        <div class="enc-dd-gauge-blocks">${gaugeBlocksHtml}</div>
      </div>
    `;

    this.bindEvents(dd);
  }

  private bindEvents(dd: Dragodinde): void {
    if (!this.el) return;
    const eId = this.enclosId;
    const dId = this.ddId;

    /* Delete button */
    const delBtn = this.el.querySelector('.dd-del');
    delBtn?.addEventListener('click', async () => {
      const ok = await ConfirmModal.show('Retirer la dragodinde', 'Retirer cette dragodinde de l\'enclos ?');
      if (!ok) return;
      const hasSnap = await UndoManager.snapshotCurrent('Dragodinde retirée');
      this.commandBus.execute({ type: 'remove-dragodinde', enclosId: eId, ddId: dId });
      Toast.show('success', 'Dragodinde retirée.', hasSnap ? { label: 'Annuler', callback: () => UndoManager.undo() } : undefined);
    });

    /* Name input */
    const nameInput = this.el.querySelector<HTMLInputElement>('.dd-name');
    if (nameInput) {
      nameInput.addEventListener('focus', () => {
        nameInput.dataset.prev = nameInput.value;
        nameInput.value = '';
      });
      nameInput.addEventListener('blur', () => {
        const v = nameInput.value.trim();
        if (!v) nameInput.value = nameInput.dataset.prev || dd.name;
        else this.commandBus.execute({ type: 'rename-dragodinde', enclosId: eId, ddId: dId, name: v });
      });
      nameInput.addEventListener('keydown', (e) => {
        if (e.key === 'Escape') { nameInput.value = nameInput.dataset.prev || dd.name; nameInput.blur(); }
        else if (e.key === 'Enter') nameInput.blur();
      });
    }

    /* Stat pill inputs */
    this.el.querySelectorAll<HTMLInputElement>('.pill-input').forEach(inp => {
      const stat = inp.dataset.stat as StatType;
      inp.addEventListener('focus', () => {
        inp.dataset.prev = inp.value;
        inp.value = '';
      });
      inp.addEventListener('input', () => {
        if (!inp.value) return;
        const v = Number(inp.value);
        if (!isNaN(v)) this.commandBus.execute({ type: 'update-dd-stat', enclosId: eId, ddId: dId, stat, value: v });
      });
      inp.addEventListener('blur', () => {
        if (inp.value === '') { inp.value = inp.dataset.prev || '0'; return; }
        const v = Number(inp.value);
        if (!isNaN(v)) this.commandBus.execute({ type: 'update-dd-stat', enclosId: eId, ddId: dId, stat, value: v });
      });
      inp.addEventListener('keydown', (e) => {
        if (e.key === 'Escape') { inp.value = inp.dataset.prev || '0'; inp.blur(); }
        else if (e.key === 'Enter') inp.blur();
      });
    });

    /* Clear buttons */
    this.el.querySelector(`#ser-clr-${eId}-${dId}`)?.addEventListener('click', () => {
      this.commandBus.execute({ type: 'update-dd-seren-target', enclosId: eId, ddId: dId, target: null });
      const inp = this.el?.querySelector<HTMLInputElement>(`#ser-tgt-${eId}-${dId}`);
      if (inp) inp.value = '';
      const btn = this.el?.querySelector<HTMLElement>(`#ser-clr-${eId}-${dId}`);
      if (btn) btn.style.visibility = 'hidden';
    });
    this.el.querySelector(`#lvl-clr-${eId}-${dId}`)?.addEventListener('click', () => {
      this.commandBus.execute({ type: 'update-dd-level-target', enclosId: eId, ddId: dId, target: null });
      const inp = this.el?.querySelector<HTMLInputElement>(`#lvl-tgt-${eId}-${dId}`);
      if (inp) inp.value = '';
      const btn = this.el?.querySelector<HTMLElement>(`#lvl-clr-${eId}-${dId}`);
      if (btn) btn.style.visibility = 'hidden';
    });

    /* Serenite target — clamp selon la jauge active (baffeur → négatif, caresseur → positif) */
    const serTgt = this.el.querySelector<HTMLInputElement>(`#ser-tgt-${eId}-${dId}`);
    if (serTgt) {
      const clampSeren = (raw: number): number => {
        const enc = this.queryBus.execute<Enclos>({ type: 'get-enclos-detail', enclosId: eId });
        if (enc.activeGauges.includes('baffeur')) return Math.min(0, Math.max(-5000, raw));
        if (enc.activeGauges.includes('caresseur')) return Math.max(0, Math.min(5000, raw));
        return Math.min(5000, Math.max(-5000, raw));
      };
      serTgt.addEventListener('focus', () => { serTgt.dataset.prev = serTgt.value; serTgt.value = ''; });
      serTgt.addEventListener('input', () => {
        if (!serTgt.value) return;
        const v = Number(serTgt.value);
        if (isNaN(v)) return;
        const clamped = clampSeren(v);
        this.commandBus.execute({ type: 'update-dd-seren-target', enclosId: eId, ddId: dId, target: clamped });
      });
      serTgt.addEventListener('blur', () => {
        if (serTgt.value === '') { serTgt.value = serTgt.dataset.prev || ''; return; }
        const v = Number(serTgt.value);
        if (isNaN(v)) { serTgt.value = serTgt.dataset.prev || ''; return; }
        const clamped = clampSeren(v);
        this.commandBus.execute({ type: 'update-dd-seren-target', enclosId: eId, ddId: dId, target: clamped });
        serTgt.value = String(clamped);
      });
      serTgt.addEventListener('keydown', (e) => {
        if (e.key === 'Escape') { serTgt.value = serTgt.dataset.prev || ''; serTgt.blur(); }
        else if (e.key === 'Enter') serTgt.blur();
      });
    }

    /* Level target */
    const lvlTgt = this.el.querySelector<HTMLInputElement>(`#lvl-tgt-${eId}-${dId}`);
    if (lvlTgt) {
      lvlTgt.addEventListener('focus', () => { lvlTgt.dataset.prev = lvlTgt.value; lvlTgt.value = ''; });
      lvlTgt.addEventListener('input', () => {
        if (!lvlTgt.value) return;
        const v = Number(lvlTgt.value);
        this.commandBus.execute({ type: 'update-dd-level-target', enclosId: eId, ddId: dId, target: isNaN(v) ? null : v });
      });
      lvlTgt.addEventListener('blur', () => {
        if (lvlTgt.value === '') { lvlTgt.value = lvlTgt.dataset.prev || ''; return; }
        const v = Number(lvlTgt.value);
        this.commandBus.execute({ type: 'update-dd-level-target', enclosId: eId, ddId: dId, target: isNaN(v) ? null : v });
      });
      lvlTgt.addEventListener('keydown', (e) => {
        if (e.key === 'Escape') { lvlTgt.value = lvlTgt.dataset.prev || ''; lvlTgt.blur(); }
        else if (e.key === 'Enter') lvlTgt.blur();
      });
    }
  }

  update(enc: Enclos, dd: Dragodinde, el: number, started: boolean): void {
    if (!this.el) return;
    const eId = this.enclosId;
    const dId = this.ddId;

    /* Détection de tick (toutes les 10 sec).
     * Après complétion automatique, le temps réel continue pour que les animations
     * continuent sur toutes les jauges qui se vident en fond. */
    const elForTick = started ? elapsedLive(enc) : 0;
    const tick = started ? Math.floor(elForTick / 10) : -1;
    if (!started) {
      this.lastTick = -1;
    } else if (this.lastTick === -1) {
      this.lastTick = tick; // initialise sans déclencher au démarrage
    }
    const newTick = started && tick !== this.lastTick;
    if (newTick) this.lastTick = tick;

    let allDone = enc.activeGauges.length > 0;

    /* Update active gauge blocks */
    enc.activeGauges.forEach(gid => {
      const r = computeGaugeLive(enc, dd, gid, el, started);
      const def = GAUGE_DEFS[gid];

      // Toutes les jauges comptent pour le badge "✓ TERMINÉ"
      if (!r.done) allDone = false;

      const lvEl = this.el!.querySelector(`#slv-${eId}-${dId}-${gid}`);
      if (lvEl) lvEl.textContent = r.liveText;

      /* Delta live-delta : pop à chaque tick.
       * Continue jusqu'au cap absolu de la stat (pas juste la cible). */
      const sd = def.isXp ? null : STAT_DEFS[def.stat as keyof typeof STAT_DEFS];
      const atAbsCap = def.isXp
        ? (r.estStat as number) >= 200
        : (def.dir > 0 ? (r.estStat as number) >= sd!.max : (r.estStat as number) <= sd!.min);
      const deltaActive = started && !atAbsCap;
      const deltaEl = this.el!.querySelector<HTMLElement>(`#sdelta-${eId}-${dId}-${gid}`);
      if (deltaEl) {
        deltaEl.textContent = r.deltaText;
        if (newTick && deltaActive) {
          deltaEl.classList.remove('show');
          void deltaEl.offsetWidth; // force reflow pour relancer l'animation
          deltaEl.classList.add('show');
        } else if (!deltaActive) {
          deltaEl.classList.remove('show');
        }
      }

      const cdEl = this.el!.querySelector(`#scd-${eId}-${dId}-${gid}`);
      if (cdEl) cdEl.textContent = r.done ? '✅' : (!isFinite(r.cntDown) ? '∞' : fmtClock(r.cntDown));

      /* ETA + barre de progression niveau 200 (mangeoire uniquement) */
      if (gid === 'mangeoire') {
        const eta200El = this.el!.querySelector(`#eta200-${eId}-${dId}`);
        if (eta200El) {
          const eta = calcLevel200EtaLive(enc, dd, el, started);
          eta200El.textContent = `→ NIV. 200 : ${eta || '—'}`;
        }
        const pct200 = Math.min(100, Math.max(0, ((r.estStat as number) - 1) / 199 * 100));
        const barFillEl = this.el!.querySelector<HTMLElement>(`#eta200-bar-${eId}-${dId}`);
        if (barFillEl) barFillEl.style.width = `${pct200.toFixed(1)}%`;
        const pctEl = this.el!.querySelector<HTMLElement>(`#eta200-pct-${eId}-${dId}`);
        if (pctEl) pctEl.textContent = `${Math.round(pct200)}%`;
      }

      const pbEl = this.el!.querySelector<HTMLElement>(`#spb-${eId}-${dId}-${gid}`);
      if (pbEl) pbEl.style.width = `${r.progPct.toFixed(1)}%`;

      /* Mise à jour live du badge de stat correspondant.
       * Désactivée après complétion (__done__) : dd.stats est déjà à jour
       * et l'utilisateur doit pouvoir corriger les valeurs à la main. */
      if (started && !enc.alerted['__done__']) {
        const pillInput = this.el!.querySelector<HTMLInputElement>(`#pstat-${eId}-${dId}-${def.stat}`);
        if (pillInput && document.activeElement !== pillInput) {
          pillInput.value = String(Math.round(r.estStat as number));
        }
        const sp = STAT_PILLS.find(p => p.key === def.stat);
        if (sp) {
          const val = Math.round(r.estStat as number);
          const atLimit = val >= sp.max || (sp.key === 'serenite' && val <= sp.min);
          const pill = pillInput?.closest<HTMLElement>('.dd-stat-pill');
          if (pill) {
            pill.classList.toggle('at-max', atLimit);
            pill.style.background = atLimit ? `rgba(${sp.color},0.18)` : '';
            pill.style.boxShadow = atLimit ? `0 0 8px rgba(${sp.color},0.5)` : '';
          }
        }
      }

      /* Pill delta : pop à chaque tick — uniquement endurance, maturite, amour */
      const pillDeltaStats = ['endurance', 'maturite', 'amour'];
      if (pillDeltaStats.includes(def.stat)) {
        const pillDelta = this.el!.querySelector<HTMLElement>(`#pill-delta-${eId}-${dId}-${def.stat}`);
        if (pillDelta) {
          pillDelta.textContent = r.deltaText;
          if (newTick && started && !r.done) {
            pillDelta.classList.remove('show');
            void pillDelta.offsetWidth;
            pillDelta.classList.add('show');
          } else if (!started || r.done) {
            pillDelta.classList.remove('show');
          }
        }
      }
    });

    /* Done badge */
    const doneBadge = this.el.querySelector<HTMLElement>(`#dd-done-${eId}-${dId}`);
    if (doneBadge) {
      doneBadge.style.display = (allDone && started && enc.activeGauges.length > 0) ? '' : 'none';
    }

    /* Serenity ETA */
    const serEta = this.el.querySelector(`#ser-eta-${eId}-${dId}`);
    if (serEta) serEta.innerHTML = calcSerenEtaLive(enc, dd, el, started);
    const serClr = this.el.querySelector<HTMLElement>(`#ser-clr-${eId}-${dId}`);
    if (serClr) serClr.style.visibility = dd.sereniteTarget == null ? 'hidden' : 'visible';

    /* Level ETA */
    const lvlEta = this.el.querySelector(`#lvl-eta-${eId}-${dId}`);
    if (lvlEta) lvlEta.innerHTML = calcLevelEtaLive(enc, dd, el, started);
    const lvlClr = this.el.querySelector<HTMLElement>(`#lvl-clr-${eId}-${dId}`);
    if (lvlClr) lvlClr.style.visibility = dd.levelTarget == null ? 'hidden' : 'visible';
  }

  destroy(): void {
    this.el?.remove();
    this.el = null;
  }
}
