'use strict';

/**
 * Alerta modular de ataques recebidos.
 *
 * Responsabilidades:
 * - detetar marchas inimigas destinadas ao jogador atual;
 * - mostrar a trompa vermelha e o contador do ataque mais próximo;
 * - tocar uma trombeta apenas uma vez por nova marcha detetada;
 * - assinalar o percurso e a marcha inimiga a vermelho no mapa mundial;
 * - remover o alerta quando a marcha é cancelada ou deixa de estar a caminho.
 *
 * Este módulo lê as marchas existentes nos perfis. Num servidor multiplayer,
 * a mesma interface poderá receber a lista através da API do servidor sem
 * alterar o restante jogo.
 */
(() => {
  const VERSION = 3;
  const STATE_KEY = 'incomingAttackAlert';
  const MAX_REMEMBERED_IDS = 250;
  let audioContext = null;

  function safeNumber(value) {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : 0;
  }

  function safeCount(value) {
    return Math.max(0, Math.floor(safeNumber(value)));
  }

  function targetUserId(mission) {
    if (mission?.targetUserId) return String(mission.targetUserId);
    const id = String(mission?.targetId || '');
    return id.startsWith('player:') ? id.slice('player:'.length) : '';
  }

  function totalTroops(troops) {
    return Object.values(troops || {}).reduce((sum, count) => sum + safeCount(count), 0);
  }

  function totalTools(mission) {
    const totals = {};
    const waves = mission?.attackPlan?.waves || [];
    waves.forEach(wave => {
      Object.values(wave?.flanks || {}).forEach(flank => {
        Object.entries(flank?.tools || {}).forEach(([type, count]) => {
          totals[type] = safeCount(totals[type]) + safeCount(count);
        });
      });
    });
    return Object.values(totals).reduce((sum, count) => sum + count, 0);
  }

  function ensureState(profile) {
    if (!profile) return null;
    const existing = profile[STATE_KEY];
    if (!existing || typeof existing !== 'object') {
      profile[STATE_KEY] = {
        version: VERSION,
        notifiedMissionIds: [],
        soundEnabled: true,
        lastDetectedAt: 0
      };
    }
    const state = profile[STATE_KEY];
    state.version = VERSION;
    state.notifiedMissionIds = Array.isArray(state.notifiedMissionIds)
      ? state.notifiedMissionIds.map(String).slice(-MAX_REMEMBERED_IDS)
      : [];
    if (typeof state.soundEnabled !== 'boolean') state.soundEnabled = true;
    return state;
  }

  function coordinate(value, fallback = 0) {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : fallback;
  }

  function interpolate(from, to, progress) {
    const normalized = Math.max(0, Math.min(1, safeNumber(progress)));
    return {
      x: coordinate(from?.x) + (coordinate(to?.x) - coordinate(from?.x)) * normalized,
      y: coordinate(from?.y) + (coordinate(to?.y) - coordinate(from?.y)) * normalized
    };
  }

  function listIncoming(context) {
    const database = context.getDb?.();
    const currentUser = context.getCurrentUser?.();
    const ownProfile = context.getProfile?.();
    if (!database || !currentUser || !ownProfile) return [];

    const currentTime = context.now?.() ?? Date.now();
    const attacks = [];

    Object.entries(database.profiles || {}).forEach(([attackerUserId, attackerProfile]) => {
      if (!attackerProfile || String(attackerUserId) === String(currentUser.id)) return;
      const attackerUser = (database.users || []).find(user => String(user.id) === String(attackerUserId));

      (Array.isArray(attackerProfile.missions) ? attackerProfile.missions : []).forEach(mission => {
        if (!mission || mission.targetType !== 'player') return;
        if (targetUserId(mission) !== String(currentUser.id)) return;
        if ((mission.state || 'outgoing') !== 'outgoing') return;
        if (mission.cancelled) return;
        const endAt = safeNumber(mission.endAt);
        if (endAt <= currentTime) return;

        const startedAt = safeNumber(mission.launchStartedAt || mission.startedAt || currentTime);
        const travelMs = Math.max(1, safeNumber(mission.travelMs) || (endAt - startedAt));
        const origin = mission.originPosition || attackerProfile.worldPosition || { x: 0, y: 0 };
        const target = mission.targetPosition || ownProfile.worldPosition || { x: 0, y: 0 };
        const progress = Math.max(0, Math.min(1, (currentTime - startedAt) / travelMs));
        const position = interpolate(origin, target, progress);
        const dx = coordinate(target.x) - coordinate(origin.x);
        const dy = coordinate(target.y) - coordinate(origin.y);
        const distance = Number.isFinite(Number(mission.distanceCoordinates))
          ? Number(mission.distanceCoordinates)
          : Math.sqrt(dx * dx + dy * dy);

        attacks.push({
          id: String(mission.id),
          mission,
          attackerUserId: String(attackerUserId),
          attackerName: attackerUser?.username || 'Jogador desconhecido',
          attackerKingdom: attackerProfile.kingdomName || `Reino de ${attackerUser?.username || 'Jogador'}`,
          attackerLevel: Math.max(1, safeCount(attackerProfile.level) || 1),
          origin: { x: coordinate(origin.x), y: coordinate(origin.y) },
          target: { x: coordinate(target.x), y: coordinate(target.y) },
          position,
          startedAt,
          endAt,
          travelMs,
          distance,
          troops: totalTroops(mission.troops),
          tools: totalTools(mission),
          remainingMs: Math.max(0, endAt - currentTime)
        });
      });
    });

    return attacks.sort((first, second) => first.endAt - second.endAt);
  }

  function hornSvg() {
    return `<svg viewBox="0 0 82 70" aria-hidden="true">
      <defs>
        <linearGradient id="incomingHornGold" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0" stop-color="#fff1a8"/>
          <stop offset=".38" stop-color="#f0b42b"/>
          <stop offset="1" stop-color="#8f3e09"/>
        </linearGradient>
        <linearGradient id="incomingHornRed" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#ff5a42"/>
          <stop offset="1" stop-color="#9c0808"/>
        </linearGradient>
      </defs>
      <path d="M13 31c13-2 23-8 36-20 3-3 8-1 8 4v38c0 5-5 7-8 4-13-12-23-18-36-20z" fill="url(#incomingHornGold)" stroke="#3e1a07" stroke-width="4"/>
      <path d="M11 29h8v12h-8c-5 0-8-3-8-6s3-6 8-6z" fill="#5f2610" stroke="#2a0c04" stroke-width="3"/>
      <path d="M56 16l16-8v52l-16-8z" fill="url(#incomingHornRed)" stroke="#3a0707" stroke-width="4"/>
      <path d="M27 38c-4 13 4 21 15 22" fill="none" stroke="#4b2209" stroke-width="5" stroke-linecap="round"/>
      <path d="M64 19l7-3M64 29l8-1M64 40l8 1M64 50l7 3" stroke="#ffd15a" stroke-width="2"/>
    </svg>`;
  }

  function playTrumpet() {
    try {
      const AudioContextClass = globalThis.AudioContext || globalThis.webkitAudioContext;
      if (!AudioContextClass) return false;
      audioContext = audioContext || new AudioContextClass();
      if (audioContext.state === 'suspended') void audioContext.resume();

      const start = audioContext.currentTime + 0.03;
      const notes = [
        { frequency: 392.00, at: 0.00, duration: 0.17 },
        { frequency: 523.25, at: 0.19, duration: 0.20 },
        { frequency: 659.25, at: 0.42, duration: 0.31 },
        { frequency: 523.25, at: 0.77, duration: 0.15 },
        { frequency: 659.25, at: 0.95, duration: 0.46 }
      ];

      notes.forEach(note => {
        const gain = audioContext.createGain();
        const fundamental = audioContext.createOscillator();
        const harmonic = audioContext.createOscillator();
        fundamental.type = 'sawtooth';
        harmonic.type = 'triangle';
        fundamental.frequency.value = note.frequency;
        harmonic.frequency.value = note.frequency * 2;
        const noteStart = start + note.at;
        const noteEnd = noteStart + note.duration;
        gain.gain.setValueAtTime(0.0001, noteStart);
        gain.gain.exponentialRampToValueAtTime(0.19, noteStart + 0.025);
        gain.gain.setValueAtTime(0.16, Math.max(noteStart + 0.03, noteEnd - 0.06));
        gain.gain.exponentialRampToValueAtTime(0.0001, noteEnd);
        fundamental.connect(gain);
        harmonic.connect(gain);
        gain.connect(audioContext.destination);
        fundamental.start(noteStart);
        harmonic.start(noteStart);
        fundamental.stop(noteEnd + 0.02);
        harmonic.stop(noteEnd + 0.02);
      });
      return true;
    } catch (_) {
      return false;
    }
  }

  function announceNewAttacks(context, attacks) {
    const profile = context.getProfile?.();
    const state = ensureState(profile);
    if (!state) return false;
    const remembered = new Set(state.notifiedMissionIds);
    const newAttacks = attacks.filter(attack => !remembered.has(attack.id));
    if (!newAttacks.length) return false;

    newAttacks.forEach(attack => remembered.add(attack.id));
    state.notifiedMissionIds = [...remembered].slice(-MAX_REMEMBERED_IDS);
    state.lastDetectedAt = context.now?.() ?? Date.now();
    if (state.soundEnabled) playTrumpet();
    context.toast?.(
      newAttacks.length > 1
        ? `${newAttacks.length} ataques inimigos foram detetados!`
        : `Ataque inimigo detetado de ${newAttacks[0].attackerName}!`,
      'error'
    );
    void context.persist?.(true);
    return true;
  }

  function hornMarkup(attacks, context) {
    if (!attacks.length) return '';
    const nearest = attacks[0];
    return `<div class="incoming-attack-edge" id="incoming-attack-edge" aria-hidden="true"></div>
      <button class="incoming-attack-horn" id="incoming-attack-horn" data-action="incoming-attack-open" title="Abrir ataques a caminho">
        <span class="incoming-attack-horn-art">${hornSvg()}</span>
        <b class="incoming-attack-count">${attacks.length}</b>
        <span class="incoming-attack-caption">ATAQUE</span>
        <small data-incoming-nearest-timer>${context.formatTime(nearest.remainingMs)}</small>
      </button>`;
  }

  function positionHorn() {
    const horn = document.getElementById('incoming-attack-horn');
    if (!horn) return;

    // Usa apenas um painel do jogador realmente visível. Isto evita alinhar a
    // trombeta por cartões ocultos que possam existir durante uma mudança de ecrã.
    const playerCard = Array.from(document.querySelectorAll('.player-card')).find(card => {
      const rect = card.getBoundingClientRect?.();
      const style = window.getComputedStyle?.(card);
      return rect && rect.width > 0 && rect.height > 0
        && style?.display !== 'none' && style?.visibility !== 'hidden';
    });
    const cardRect = playerCard?.getBoundingClientRect?.();
    const hornWidth = horn.offsetWidth || 104;
    const hornHeight = horn.offsetHeight || 112;

    // A posição principal passa a ser por baixo do brasão/painel do jogador,
    // centrada na largura do painel. Assim nunca fica escondida atrás do brasão.
    let desiredLeft = cardRect && cardRect.width > 0
      ? Math.ceil(cardRect.left + ((cardRect.width - hornWidth) / 2))
      : 28;
    let desiredTop = cardRect && cardRect.height > 0
      ? Math.ceil(cardRect.bottom + 10)
      : 218;

    // Em ecrãs demasiado baixos, usa o lado direito como alternativa para que
    // o alerta continue totalmente acessível.
    const fitsBelow = desiredTop + hornHeight <= window.innerHeight - 12;
    if (!fitsBelow && cardRect && cardRect.width > 0) {
      desiredLeft = Math.ceil(cardRect.right + 12);
      desiredTop = Math.ceil(cardRect.top + 20);
    }

    const maxLeft = Math.max(8, window.innerWidth - hornWidth - 12);
    const maxTop = Math.max(48, window.innerHeight - hornHeight - 12);
    horn.style.left = `${Math.min(Math.max(8, desiredLeft), maxLeft)}px`;
    horn.style.top = `${Math.min(Math.max(48, desiredTop), maxTop)}px`;
  }

  function ensureOverlay(context, attacks) {
    const shell = document.querySelector('.game-shell');
    if (!shell) return;
    const existingHorn = document.getElementById('incoming-attack-horn');
    const existingEdge = document.getElementById('incoming-attack-edge');

    if (!attacks.length) {
      existingHorn?.remove();
      existingEdge?.remove();
      shell.classList.remove('incoming-under-attack');
      return;
    }

    shell.classList.add('incoming-under-attack');
    if (!existingHorn || !existingEdge) {
      const wrapper = document.createElement('div');
      wrapper.className = 'incoming-attack-overlay-root';
      wrapper.innerHTML = hornMarkup(attacks, context);
      // Estes elementos são fixos ao ecrã. Colocá-los no body evita que o zoom
      // do mapa/castelo desloque a trombeta para trás do painel do jogador.
      document.body.append(...wrapper.children);
    }
    updateOverlay(context, attacks);
  }

  function updateOverlay(context, attacks) {
    const horn = document.getElementById('incoming-attack-horn');
    if (!horn || !attacks.length) return;
    const count = horn.querySelector('.incoming-attack-count');
    const timer = horn.querySelector('[data-incoming-nearest-timer]');
    if (count) count.textContent = String(attacks.length);
    if (timer) timer.textContent = context.formatTime(attacks[0].remainingMs);
    horn.classList.toggle('multiple', attacks.length > 1);
    positionHorn();
  }

  function attackCard(attack, context) {
    const arrival = new Date(attack.endAt).toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
    return `<article class="incoming-attack-card" data-incoming-card-id="${context.escapeHtml(attack.id)}">
      <header>
        <span class="incoming-card-horn">${hornSvg()}</span>
        <div><h4>Ataque inimigo</h4><strong>${context.escapeHtml(attack.attackerKingdom)}</strong><small>Governante: ${context.escapeHtml(attack.attackerName)} • Nível ${context.format(attack.attackerLevel)}</small></div>
        <div class="incoming-card-countdown"><span>Chega em</span><b data-incoming-card-timer="${context.escapeHtml(attack.id)}">${context.formatTime(attack.remainingMs)}</b></div>
      </header>
      <div class="incoming-attack-route-summary">
        <div><small>Origem</small><b>X ${Math.round(attack.origin.x)} • Y ${Math.round(attack.origin.y)}</b></div>
        <span>➜</span>
        <div><small>Destino</small><b>X ${Math.round(attack.target.x)} • Y ${Math.round(attack.target.y)}</b></div>
      </div>
      <div class="incoming-attack-stats">
        <span><small>Distância</small><b>${attack.distance.toFixed(1)}</b></span>
        <span><small>Tropas detetadas</small><b>${context.format(attack.troops)}</b></span>
        <span><small>Ferramentas</small><b>${context.format(attack.tools)}</b></span>
        <span><small>Chegada prevista</small><b>${arrival}</b></span>
      </div>
      <div class="incoming-attack-actions">
        <button class="secondary-btn" data-action="incoming-focus-map" data-mission-id="${context.escapeHtml(attack.id)}">🗺️ Ver no mapa</button>
        <button class="action-btn" data-action="incoming-open-defense">🛡️ Preparar defesa</button>
      </div>
    </article>`;
  }

  function openPanel(context, requestedMissionId = '') {
    const attacks = listIncoming(context);
    if (!attacks.length) {
      context.toast?.('Já não existem ataques inimigos a caminho.', 'success');
      return;
    }
    const ordered = requestedMissionId
      ? [...attacks].sort((a, b) => (a.id === requestedMissionId ? -1 : b.id === requestedMissionId ? 1 : a.endAt - b.endAt))
      : attacks;
    context.openModal?.(
      attacks.length === 1 ? 'Ataque a caminho' : `${attacks.length} ataques a caminho`,
      `<div class="incoming-attacks-panel">
        <div class="incoming-attacks-warning"><span>${hornSvg()}</span><div><strong>O teu castelo está sob ameaça</strong><small>Acompanha o tempo de chegada e prepara a defesa antes do impacto.</small></div></div>
        ${ordered.map(attack => attackCard(attack, context)).join('')}
      </div>`,
      '<button class="secondary-btn" data-action="modal-close">Fechar</button>'
    );
    document.querySelector('.modal')?.classList.add('incoming-attacks-modal');
  }

  function renderMap(context) {
    const attacks = listIncoming(context);
    if (!attacks.length) return '';
    const lines = attacks.map(attack => {
      const x1 = context.worldPixelX(attack.origin.x);
      const y1 = context.worldPixelY(attack.origin.y);
      const x2 = context.worldPixelX(attack.target.x);
      const y2 = context.worldPixelY(attack.target.y);
      return `<line data-incoming-route-id="${context.escapeHtml(attack.id)}" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" />`;
    }).join('');
    const nodes = attacks.map(attack => {
      const left = context.worldPixelX(attack.position.x);
      const top = context.worldPixelY(attack.position.y);
      return `<div class="incoming-map-army" data-incoming-map-id="${context.escapeHtml(attack.id)}" style="left:${left}px;top:${top}px">
        <button data-action="incoming-attack-open" data-mission-id="${context.escapeHtml(attack.id)}" title="Ataque de ${context.escapeHtml(attack.attackerName)} • ${context.formatTime(attack.remainingMs)}">
          <span class="incoming-map-army-icon">⚔</span>
          <b>!</b>
        </button>
        <small><strong>${context.escapeHtml(attack.attackerName)}</strong><span data-incoming-map-timer="${context.escapeHtml(attack.id)}">${context.formatTime(attack.remainingMs)}</span></small>
      </div>`;
    }).join('');
    return `<svg class="incoming-map-routes" aria-hidden="true"><defs><marker id="incomingRouteArrow" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M0,0 L12,6 L0,12 z" /></marker></defs>${lines}</svg>${nodes}`;
  }

  function updateMap(context, attacks) {
    const byId = new Map(attacks.map(attack => [attack.id, attack]));
    if (!attacks.length) {
      document.querySelectorAll('.incoming-map-army, .incoming-map-routes').forEach(element => element.remove());
      return;
    }
    document.querySelectorAll('.incoming-map-routes line[data-incoming-route-id]').forEach(line => {
      if (!byId.has(String(line.dataset.incomingRouteId))) line.remove();
    });
    const mapNodes = document.querySelectorAll('.incoming-map-army[data-incoming-map-id]');
    mapNodes.forEach(node => {
      const attack = byId.get(String(node.dataset.incomingMapId));
      if (!attack) {
        node.remove();
        return;
      }
      node.style.left = `${context.worldPixelX(attack.position.x)}px`;
      node.style.top = `${context.worldPixelY(attack.position.y)}px`;
      const timer = node.querySelector('[data-incoming-map-timer]');
      if (timer) timer.textContent = context.formatTime(attack.remainingMs);
      const button = node.querySelector('button');
      if (button) button.title = `Ataque de ${attack.attackerName} • ${context.formatTime(attack.remainingMs)}`;
    });
  }

  function updateModalTimers(context, attacks) {
    const byId = new Map(attacks.map(attack => [attack.id, attack]));
    document.querySelectorAll('[data-incoming-card-timer]').forEach(timer => {
      const attack = byId.get(String(timer.dataset.incomingCardTimer));
      if (!attack) {
        const card = timer.closest('[data-incoming-card-id]');
        card?.classList.add('incoming-attack-ended');
        timer.textContent = 'Terminou';
        return;
      }
      timer.textContent = context.formatTime(attack.remainingMs);
    });
  }

  function refresh(context, shouldAnnounce = true) {
    const attacks = listIncoming(context);
    if (shouldAnnounce) announceNewAttacks(context, attacks);
    ensureOverlay(context, attacks);
    updateMap(context, attacks);
    updateModalTimers(context, attacks);
    return attacks;
  }

  function afterRender(context) {
    refresh(context, true);
  }

  function tick(context) {
    refresh(context, true);
  }

  async function handleAction(action, element, context) {
    if (action === 'incoming-attack-open') {
      openPanel(context, element.dataset.missionId || '');
      return true;
    }
    if (action === 'incoming-focus-map') {
      const attacks = listIncoming(context);
      const attack = attacks.find(item => item.id === String(element.dataset.missionId || '')) || attacks[0];
      if (!attack) {
        context.toast?.('Este ataque já não está a caminho.', 'success');
        context.closeModal?.();
        return true;
      }
      context.closeModal?.();
      context.openMapAt?.(attack.position.x, attack.position.y);
      return true;
    }
    if (action === 'incoming-open-defense') {
      context.closeModal?.();
      context.navigate?.('castle');
      context.toast?.('Abre os edifícios defensivos para preparar a muralha, o portão e as torres.', 'success');
      return true;
    }
    return false;
  }

  globalThis.ReinosIncomingAttackAlert = Object.freeze({
    VERSION,
    ensureState,
    listIncoming,
    renderMap,
    afterRender,
    tick,
    openPanel,
    playTrumpet,
    handleAction
  });
})();
