'use strict';

const APP_API = globalThis.realmAPI || (() => {
  const DEFAULT_CONFIG = {
    serverUrl: 'http://127.0.0.1:31337',
    accessKey: 'MUDA_ESTA_CHAVE_ANTES_DE_ABRIR_AOS_AMIGOS'
  };
  const emptyData = () => ({ version: 1, users: [], profiles: {}, sessionUserId: null });
  let cachedConfig = null;

  async function loadServerConfig() {
    if (cachedConfig) return cachedConfig;

    const embedded = globalThis.REINOS_CLIENT_CONFIG || null;
    if (embedded && (embedded.serverUrl || embedded.accessKey)) {
      const serverUrl = String(embedded.serverUrl || '').replace(/\/+$/, '');
      const accessKey = String(embedded.accessKey || '');
      if (!serverUrl) throw new Error('serverUrl vazio no client-config.js.');
      if (!accessKey) throw new Error('accessKey vazia no client-config.js.');
      cachedConfig = { serverUrl, accessKey };
      return cachedConfig;
    }

    let response;
    try {
      response = await fetch('../config_server.json', { cache: 'no-store' });
    } catch (_) {
      throw new Error('config_server.json não encontrado. Este cliente precisa de estar configurado para o servidor.');
    }
    if (!response.ok) throw new Error('config_server.json não encontrado. Este cliente precisa de estar configurado para o servidor.');
    const raw = await response.json();
    const serverUrl = String(raw?.client?.serverUrl || '').replace(/\/+$/, '');
    const accessKey = String(raw?.client?.accessKey || raw?.security?.accessKey || '');
    if (!serverUrl) throw new Error('serverUrl vazio no config_server.json.');
    if (!accessKey) throw new Error('accessKey vazia no config_server.json.');
    cachedConfig = { serverUrl, accessKey };
    return cachedConfig;
  }

  function getSessionToken() {
    return localStorage.getItem('reinos-session-token') || '';
  }

  function setSessionToken(token) {
    if (token) localStorage.setItem('reinos-session-token', token);
  }

  function clearSessionToken() {
    try { localStorage.removeItem('reinos-session-token'); } catch (_) {}
    try { localStorage.removeItem('reinos-de-ferro-last-server-data'); } catch (_) {}
  }

  function isInvalidSessionMessage(message) {
    return /sess[ãa]o inv[áa]lida|faz login novamente|faz login novamente/i.test(String(message || ''));
  }

  async function backendRequest(route, options = {}) {
    const cfg = await loadServerConfig();
    const sessionToken = getSessionToken();
    const response = await fetch(`${cfg.serverUrl}${route}`, {
      cache: 'no-store',
      ...options,
      headers: {
        'Content-Type': 'application/json; charset=utf-8',
        ...(cfg.accessKey ? { 'X-Reinos-Token': cfg.accessKey } : {}),
        ...(sessionToken ? { 'X-Reinos-Session': sessionToken } : {}),
        ...(options.headers || {})
      }
    });
    let parsed = {};
    try { parsed = await response.json(); } catch (_) { parsed = {}; }
    if (!response.ok || parsed.ok === false) {
      const errorMessage = parsed.error || 'Backend indisponível.';
      if (isInvalidSessionMessage(errorMessage)) clearSessionToken();
      throw new Error(errorMessage);
    }
    return parsed;
  }

  return {
    loadData: async () => {
      const result = await backendRequest('/data');
      return result.data || emptyData();
    },
    saveData: async (data) => {
      const result = await backendRequest('/data', { method: 'POST', body: JSON.stringify(data) });
      if (result.data) localStorage.setItem('reinos-de-ferro-last-server-data', JSON.stringify(result.data));
      return result.data || true;
    },
    login: async (username, password) => {
      const result = await backendRequest('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }) });
      setSessionToken(result.sessionToken);
      if (result.data) localStorage.setItem('reinos-de-ferro-last-server-data', JSON.stringify(result.data));
      return result;
    },
    register: async (username, password, userId = '', profile = null) => {
      const result = await backendRequest('/auth/register', { method: 'POST', body: JSON.stringify({ username, password, userId, profile }) });
      setSessionToken(result.sessionToken);
      if (result.data) localStorage.setItem('reinos-de-ferro-last-server-data', JSON.stringify(result.data));
      return result;
    },
    changePassword: async (currentPassword, newPassword) => {
      const result = await backendRequest('/auth/change-password', { method: 'POST', body: JSON.stringify({ currentPassword, newPassword }) });
      if (result.data) localStorage.setItem('reinos-de-ferro-last-server-data', JSON.stringify(result.data));
      return result;
    },
    resetProfile: async (profile) => {
      const result = await backendRequest('/profile/reset', { method: 'POST', body: JSON.stringify({ profile }) });
      if (result.data) localStorage.setItem('reinos-de-ferro-last-server-data', JSON.stringify(result.data));
      return result;
    },
    adminGrantResources: async (targetUserId, resources) => {
      const result = await backendRequest('/admin/resources', { method: 'POST', body: JSON.stringify({ targetUserId, resources }) });
      if (result.data) localStorage.setItem('reinos-de-ferro-last-server-data', JSON.stringify(result.data));
      return result;
    },
    adminGrantInventory: async (targetUserId, inventory = {}) => {
      const result = await backendRequest('/admin/inventory', {
        method: 'POST',
        body: JSON.stringify({
          targetUserId,
          resources: inventory.resources || {},
          troops: inventory.troops || {},
          siegeTools: inventory.siegeTools || {}
        })
      });
      if (result.data) localStorage.setItem('reinos-de-ferro-last-server-data', JSON.stringify(result.data));
      return result;
    },
    adminSetAccess: async (targetUserId, enabled) => {
      const result = await backendRequest('/admin/access', { method: 'POST', body: JSON.stringify({ targetUserId, enabled }) });
      if (result.data) localStorage.setItem('reinos-de-ferro-last-server-data', JSON.stringify(result.data));
      return result;
    },
    marketSendResources: async (targetUserId, resources) => {
      const result = await backendRequest('/market/send', { method: 'POST', body: JSON.stringify({ targetUserId, resources }) });
      if (result.data) localStorage.setItem('reinos-de-ferro-last-server-data', JSON.stringify(result.data));
      return result;
    },
    getRanking: async () => backendRequest('/ranking'),
    getVersion: async () => '0.1.150-admin-por-janelas',
    getDataPath: async () => {
      const cfg = await loadServerConfig();
      return `Backend configurado em ${cfg.serverUrl}`;
    },
    clearSession: async () => { clearSessionToken(); return true; },
    getBackendStatus: async () => {
      try { return await backendRequest('/health'); }
      catch (_) { return { online: false, fallback: false, required: true }; }
    }
  };
})();


const RESOURCE_ICONS = globalThis.ReinosResourceIcons;
function resourceIcon(type, fallback = '') {
  return RESOURCE_ICONS?.icon?.(type) || fallback;
}
function resourceLine(resources, types = ['wood', 'stone', 'food', 'coins']) {
  if (RESOURCE_ICONS?.list) return RESOURCE_ICONS.list(resources, types, fmt);
  return types.map(type => `${RESOURCE_META[type]?.name || type} ${fmt(resources?.[type] || 0)}`).join(' · ');
}

const BUILDINGS = {
  keep: { name: 'Fortaleza', icon: '🏰', description: 'Centro do reino. Desbloqueia níveis superiores.', baseCost: { wood: 900, stone: 900, coins: 350 }, baseTime: 30, max: 1, maxLevel: 80 },
  woodcutter: { name: 'Madeireira', icon: resourceIcon('wood', 'Madeira'), description: 'Produz madeira continuamente.', baseCost: { wood: 260, stone: 140, coins: 60 }, baseTime: 9, production: { wood: 220 } },
  quarry: { name: 'Pedreira', icon: resourceIcon('stone', 'Pedra'), description: 'Produz pedra continuamente.', baseCost: { wood: 180, stone: 260, coins: 60 }, baseTime: 10, production: { stone: 190 } },
  farm: { name: 'Quinta', icon: resourceIcon('food', 'Comida'), description: 'Produz comida para tropas e população.', baseCost: { wood: 230, stone: 160, coins: 55 }, baseTime: 8, production: { food: 250 } },
  house: { name: 'Casa', icon: '🏠', description: 'Aumenta a população e gera impostos.', baseCost: { wood: 320, stone: 220, coins: 80 }, baseTime: 12, production: { coins: 75 } },
  barracks: { name: 'Quartel', icon: '⚔️', description: 'Permite recrutar unidades militares.', baseCost: { wood: 650, stone: 540, coins: 250 }, baseTime: 18, max: 1, maxLevel: 20 },
  warehouse: { name: 'Armazém', icon: '📦', description: 'Aumenta a capacidade máxima de recursos.', baseCost: { wood: 520, stone: 600, coins: 180 }, baseTime: 16, max: 2 },
  marketplace: { name: 'Mercado', icon: '⚖️', description: 'Permite trocar e enviar recursos.', baseCost: { wood: 700, stone: 620, coins: 400 }, baseTime: 22, max: 1, keepRequired: 2 },
  workshop: { name: 'Oficina', icon: '🔨', description: 'Produz ferramentas de cerco e defesa.', baseCost: { wood: 850, stone: 750, coins: 520 }, baseTime: 26, max: 1, keepRequired: 2 },
  stables: { name: 'Estábulos', icon: '🐎', description: 'Aumenta a velocidade das marchas, desbloqueia montadas e permite recrutar cavalaria.', baseCost: { wood: 1100, stone: 850, coins: 850 }, baseTime: 35, max: 1, keepRequired: 3 },
  hospital: { name: 'Hospital', icon: '🏥', description: 'Recupera parte das tropas perdidas.', baseCost: { wood: 950, stone: 1200, coins: 700 }, baseTime: 34, max: 1, keepRequired: 3 },
  tavern: { name: 'Taverna', icon: '🍺', description: 'Melhora espionagem e missões especiais.', baseCost: { wood: 520, stone: 420, coins: 450 }, baseTime: 20, max: 1, keepRequired: 2 },
  watchtower: { name: 'Torre de Vigia', icon: '🗼', description: 'Aumenta a defesa do castelo.', baseCost: { wood: 740, stone: 1050, coins: 390 }, baseTime: 24, defense: 180 },
  decoration: { name: 'Monumento', icon: '🗿', description: 'Aumenta a ordem pública e a produtividade.', baseCost: { wood: 420, stone: 520, coins: 260 }, baseTime: 14, productivity: 0.04 }
};

const TROOPS = {
  spearman: { name: 'Lanceiro', icon: '🔱', attack: 12, defense: 25, carry: 18, cost: { food: 45, coins: 18 }, seconds: 2 },
  swordsman: { name: 'Espadachim', icon: '🗡️', attack: 27, defense: 18, carry: 25, cost: { food: 70, coins: 32 }, seconds: 3 },
  archer: { name: 'Arqueiro', icon: '🏹', attack: 34, defense: 12, carry: 22, cost: { food: 80, coins: 38 }, seconds: 3 },
  knight: { name: 'Cavaleiro', icon: '🐴', attack: 68, defense: 42, carry: 55, cost: { food: 165, coins: 110 }, seconds: 5, buildingRequired: 'stables' }
};

const RESOURCE_META = {
  wood: { name: 'Madeira', icon: resourceIcon('wood', 'Madeira') },
  stone: { name: 'Pedra', icon: resourceIcon('stone', 'Pedra') },
  food: { name: 'Comida', icon: resourceIcon('food', 'Comida') },
  coins: { name: 'Moedas', icon: resourceIcon('coins', 'Moedas') },
  gems: { name: 'Coroas', icon: resourceIcon('gems', 'Coroas') }
};

const NAV_ITEMS = [
  ['castle', '🏰', 'Castelo'],
  ['map', '🗺️', 'Mapa Mundial'],
  ['commanders', '🗡️', 'Comandantes'],
  ['governors', '🛡️', 'Governadores'],
  ['blacksmith', '⚒️', 'Ferreiro'],
  ['army', '⚔️', 'Exército'],
  ['alliance', '🤝', 'Aliança'],
  ['reports', '📜', 'Relatórios'],
  ['quests', '⭐', 'Missões'],
  ['ranking', '🏆', 'Classificação']
];

const FALKEN_NAME = 'Barão Falken';
const FALKEN_PROGRESSION = globalThis.ReinosFalkenProgression;
const FALKEN_PROGRESSION_VERSION = FALKEN_PROGRESSION?.VERSION ?? 3;
const FALKEN_POPULATION = globalThis.ReinosFalkenPopulation;
const FALKEN_POPULATION_VERSION = FALKEN_POPULATION?.VERSION ?? 1;
const WORLD_COORDINATES = globalThis.ReinosWorldCoordinates;
const WORLD_COORDINATE_VERSION = WORLD_COORDINATES?.VERSION ?? 3;
const WORLD_WIDTH = WORLD_COORDINATES?.WORLD_WIDTH ?? 12000;
const WORLD_HEIGHT = WORLD_COORDINATES?.WORLD_HEIGHT ?? 8000;
const WORLD_COORD_MIN = WORLD_COORDINATES?.COORD_MIN ?? -1000;
const WORLD_COORD_MAX = WORLD_COORDINATES?.COORD_MAX ?? 1000;
const WORLD_COORD_CENTER = WORLD_COORDINATES?.COORD_CENTER ?? 0;
const FALKEN_POSITIONS = FALKEN_POPULATION?.POSITIONS ?? WORLD_COORDINATES?.FALKEN_POSITIONS ?? [
  { id: 'npc_1', x: -640, y: -499 }, { id: 'npc_2', x: -299, y: 241 },
  { id: 'npc_3', x: 161, y: -600 }, { id: 'npc_4', x: 542, y: -39 },
  { id: 'npc_5', x: -499, y: 642 }, { id: 'npc_6', x: -39, y: 502 },
  { id: 'npc_7', x: 441, y: 682 }, { id: 'npc_8', x: 762, y: -660 }
];

let db = null;
let currentUser = null;
let profile = null;
let activeView = 'castle';
let authMode = 'login';
let saveCounter = 0;
let serverRefreshInFlight = false;
let lastServerRefreshAt = 0;
let appVersion = '0.1.111';
let worldMapCleanup = null;
let rankingCache = null;
let pendingMapFocus = null;

const $ = (selector, root = document) => root.querySelector(selector);
const $$ = (selector, root = document) => [...root.querySelectorAll(selector)];

function uid(prefix = 'id') {
  return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
}

async function sha256(text) {
  if (globalThis.crypto && globalThis.crypto.subtle) {
    const bytes = new TextEncoder().encode(text);
    const hash = await globalThis.crypto.subtle.digest('SHA-256', bytes);
    return [...new Uint8Array(hash)].map(b => b.toString(16).padStart(2, '0')).join('');
  }
  // Compatibilidade para execução local em navegadores antigos.
  let hash = 2166136261;
  for (let i = 0; i < text.length; i++) {
    hash ^= text.charCodeAt(i);
    hash = Math.imul(hash, 16777619);
  }
  return `local-${(hash >>> 0).toString(16)}`;
}

function now() { return Date.now(); }
function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); }
function fmt(value) { return Math.floor(Number(value) || 0).toLocaleString('pt-PT'); }
function fmtTime(ms) {
  const seconds = Math.max(0, Math.ceil(ms / 1000));
  if (seconds < 60) return `${seconds}s`;
  const minutes = Math.floor(seconds / 60);
  const rest = seconds % 60;
  if (minutes < 60) return `${minutes}m ${rest}s`;
  const hours = Math.floor(minutes / 60);
  return `${hours}h ${minutes % 60}m`;
}

function escapeHtml(text) {
  return String(text ?? '').replace(/[&<>'"]/g, char => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#039;', '"': '&quot;' }[char]));
}

function stringHash(text) {
  let hash = 2166136261;
  for (const char of String(text || 'reino')) {
    hash ^= char.charCodeAt(0);
    hash = Math.imul(hash, 16777619);
  }
  return hash >>> 0;
}

function worldPositionForSeed(seed) {
  if (WORLD_COORDINATES?.positionForSeed) return WORLD_COORDINATES.positionForSeed(seed);
  const first = stringHash(seed);
  const second = stringHash(`${seed}:y:${first}`);
  return { x: WORLD_COORD_MIN + 120 + (first % 1761), y: WORLD_COORD_MIN + 120 + (second % 1761) };
}

function normalizeWorldCoordinate(value, fallback = WORLD_COORD_CENTER) {
  if (WORLD_COORDINATES?.normalizeCoordinate) return WORLD_COORDINATES.normalizeCoordinate(value, fallback);
  const parsed = Number(value);
  return clamp(Math.round(Number.isFinite(parsed) ? parsed : fallback), WORLD_COORD_MIN, WORLD_COORD_MAX);
}

function worldPixelX(coordinate) {
  if (WORLD_COORDINATES?.coordinateToPixelX) return WORLD_COORDINATES.coordinateToPixelX(coordinate);
  return Math.round((normalizeWorldCoordinate(coordinate) - WORLD_COORD_MIN) / (WORLD_COORD_MAX - WORLD_COORD_MIN) * WORLD_WIDTH);
}

function worldPixelY(coordinate) {
  if (WORLD_COORDINATES?.coordinateToPixelY) return WORLD_COORDINATES.coordinateToPixelY(coordinate);
  return Math.round((normalizeWorldCoordinate(coordinate) - WORLD_COORD_MIN) / (WORLD_COORD_MAX - WORLD_COORD_MIN) * WORLD_HEIGHT);
}

function pixelToWorldX(pixel) {
  if (WORLD_COORDINATES?.pixelToCoordinateX) return WORLD_COORDINATES.pixelToCoordinateX(pixel);
  return normalizeWorldCoordinate(WORLD_COORD_MIN + pixel / WORLD_WIDTH * (WORLD_COORD_MAX - WORLD_COORD_MIN));
}

function pixelToWorldY(pixel) {
  if (WORLD_COORDINATES?.pixelToCoordinateY) return WORLD_COORDINATES.pixelToCoordinateY(pixel);
  return normalizeWorldCoordinate(WORLD_COORD_MIN + pixel / WORLD_HEIGHT * (WORLD_COORD_MAX - WORLD_COORD_MIN));
}

function falkenStrength(level) {
  const safeLevel = Math.max(1, Math.floor(Number(level) || 1));
  return Math.round(130 * Math.pow(1.34, safeLevel - 1));
}

function falkenLoot(level) {
  const safeLevel = Math.max(1, Math.floor(Number(level) || 1));
  const multiplier = Math.pow(1.38, safeLevel - 1);
  return {
    wood: Math.round(280 * multiplier),
    stone: Math.round(240 * multiplier),
    food: Math.round(320 * multiplier),
    coins: Math.round(105 * multiplier)
  };
}

function knownCastlePositions(extraProfile = null) {
  const positions = [];
  const knownIds = new Set();

  for (const [userId, savedProfile] of Object.entries(db?.profiles || {})) {
    const source = savedProfile?.worldPosition;
    const fallback = worldPositionForSeed(userId);
    positions.push({
      id: userId,
      x: normalizeWorldCoordinate(source?.x, fallback.x),
      y: normalizeWorldCoordinate(source?.y, fallback.y)
    });
    knownIds.add(userId);
  }

  const extraId = String(extraProfile?.userId || '');
  if (extraProfile && extraId && !knownIds.has(extraId)) {
    const source = extraProfile.worldPosition;
    const fallback = worldPositionForSeed(extraId);
    positions.push({
      id: extraId,
      x: normalizeWorldCoordinate(source?.x, fallback.x),
      y: normalizeWorldCoordinate(source?.y, fallback.y)
    });
  }

  return positions;
}

function falkenPositionsForProfile(targetProfile = null) {
  const castles = knownCastlePositions(targetProfile);
  return FALKEN_POPULATION?.getPositionsForCastles?.(castles) || FALKEN_POSITIONS;
}

function defaultMapTargets(targetProfile = null) {
  return falkenPositionsForProfile(targetProfile).map((position, index) => ({
    id: position.id || `npc_${index + 1}`,
    name: FALKEN_NAME,
    level: 1,
    x: position.x,
    y: position.y,
    strength: falkenStrength(1),
    loot: falkenLoot(1),
    cooldownUntil: 0,
    victories: 0,
    totalVictories: 0,
    levelVictories: 0
  }));
}

function migrateFalkenTargets(targetProfile) {
  if (!targetProfile) return false;
  let changed = false;
  const previousVersion = Math.max(0, Math.floor(Number(targetProfile.falkenProgressionVersion) || 0));
  const previousPopulationVersion = Math.max(0, Math.floor(Number(targetProfile.falkenPopulationVersion) || 0));
  const previousCoordinateVersion = Math.max(0, Math.floor(Number(targetProfile.worldCoordinateVersion) || 0));
  const migrateLegacyCoordinates = previousCoordinateVersion < WORLD_COORDINATE_VERSION;
  const previousTargets = Array.isArray(targetProfile.targets) ? targetProfile.targets : [];
  const populationPositions = falkenPositionsForProfile(targetProfile);
  const migrateCoordinate = (value, fallback) => migrateLegacyCoordinates && WORLD_COORDINATES?.migrateLegacyCoordinate
    ? WORLD_COORDINATES.migrateLegacyCoordinate(value, fallback)
    : normalizeWorldCoordinate(value, fallback);

  targetProfile.targets = populationPositions.map((position, index) => {
    const targetId = position.id || `npc_${index + 1}`;
    const previous = previousTargets.find(target => target?.id === targetId) || previousTargets[index] || null;
    const level = FALKEN_PROGRESSION?.normalizeLevel(previous?.level) ?? Math.max(1, Math.min(80, Math.floor(Number(previous?.level) || 1)));
    const x = previous && Number.isFinite(Number(previous.x)) ? migrateCoordinate(previous.x, position.x) : position.x;
    const y = previous && Number.isFinite(Number(previous.y)) ? migrateCoordinate(previous.y, position.y) : position.y;
    const migrated = {
      id: targetId, name: FALKEN_NAME, level,
      x: normalizeWorldCoordinate(x, position.x), y: normalizeWorldCoordinate(y, position.y),
      strength: falkenStrength(level), loot: falkenLoot(level),
      cooldownUntil: Math.max(0, Number(previous?.cooldownUntil) || 0),
      victories: Math.max(0, Math.floor(Number(previous?.victories) || (level - 1))),
      totalVictories: Math.max(0, Math.floor(Number(previous?.totalVictories) || Number(previous?.victories) || (level - 1))),
      levelVictories: Math.max(0, Math.floor(Number(previous?.levelVictories) || 0))
    };
    FALKEN_PROGRESSION?.normalizeTarget(migrated, { strengthFn: falkenStrength, lootFn: falkenLoot });
    return migrated;
  });

  if (!targetProfile.worldPosition || !Number.isFinite(Number(targetProfile.worldPosition.x)) || !Number.isFinite(Number(targetProfile.worldPosition.y))) {
    targetProfile.worldPosition = worldPositionForSeed(targetProfile.userId || targetProfile.kingdomName);
    changed = true;
  } else {
    const original = targetProfile.worldPosition;
    targetProfile.worldPosition = { x: migrateCoordinate(original.x, WORLD_COORD_CENTER), y: migrateCoordinate(original.y, WORLD_COORD_CENTER) };
    if (targetProfile.worldPosition.x !== Number(original.x) || targetProfile.worldPosition.y !== Number(original.y)) changed = true;
  }

  if (!targetProfile.worldView || !Number.isFinite(Number(targetProfile.worldView.x)) || !Number.isFinite(Number(targetProfile.worldView.y))) {
    targetProfile.worldView = { ...targetProfile.worldPosition };
    changed = true;
  } else {
    const original = targetProfile.worldView;
    targetProfile.worldView = { x: migrateCoordinate(original.x, targetProfile.worldPosition.x), y: migrateCoordinate(original.y, targetProfile.worldPosition.y) };
    if (targetProfile.worldView.x !== Number(original.x) || targetProfile.worldView.y !== Number(original.y)) changed = true;
  }

  if (migrateLegacyCoordinates) {
    for (const mission of targetProfile.missions || []) {
      for (const key of ['originPosition', 'targetPosition']) {
        const position = mission?.[key];
        if (!position || !Number.isFinite(Number(position.x)) || !Number.isFinite(Number(position.y))) continue;
        mission[key] = { x: migrateCoordinate(position.x, WORLD_COORD_CENTER), y: migrateCoordinate(position.y, WORLD_COORD_CENTER) };
        changed = true;
      }
    }
    for (const report of targetProfile.reports || []) {
      const coordinates = report?.spyData?.coordinates;
      if (!coordinates || !Number.isFinite(Number(coordinates.x)) || !Number.isFinite(Number(coordinates.y))) continue;
      report.spyData.coordinates = { x: migrateCoordinate(coordinates.x, WORLD_COORD_CENTER), y: migrateCoordinate(coordinates.y, WORLD_COORD_CENTER) };
      changed = true;
    }
  }

  const normalizedWorldZoom = globalThis.RealmWorldMapZoom?.normalizeZoom(targetProfile.worldZoom) ?? 1;
  if (Number(targetProfile.worldZoom) !== normalizedWorldZoom) changed = true;
  targetProfile.worldZoom = normalizedWorldZoom;
  if (previousVersion !== FALKEN_PROGRESSION_VERSION || previousPopulationVersion !== FALKEN_POPULATION_VERSION || previousCoordinateVersion !== WORLD_COORDINATE_VERSION || previousTargets.length !== populationPositions.length) changed = true;
  targetProfile.falkenProgressionVersion = FALKEN_PROGRESSION_VERSION;
  targetProfile.falkenPopulationVersion = FALKEN_POPULATION_VERSION;
  targetProfile.worldCoordinateVersion = WORLD_COORDINATE_VERSION;
  return changed;
}

function initialProfile(userId, username) {
  const created = now();
  const worldPosition = worldPositionForSeed(userId);
  const newProfile = {
    userId,
    kingdomName: `Reino de ${username}`,
    worldPosition: { ...worldPosition },
    worldView: { ...worldPosition },
    worldZoom: 1,
    worldCoordinateVersion: WORLD_COORDINATE_VERSION,
    activeLocationId: 'main',
    level: 1,
    xp: 0,
    xpToNext: 300,
    honor: 0,
    power: 120,
    resources: { wood: 4800, stone: 4800, food: 5200, coins: 2500, gems: 200 },
    buildings: [
      { id: uid('b'), type: 'keep', level: 1, slot: 0, status: 'ready' },
      { id: uid('b'), type: 'woodcutter', level: 1, slot: 1, status: 'ready' },
      { id: uid('b'), type: 'quarry', level: 1, slot: 2, status: 'ready' },
      { id: uid('b'), type: 'farm', level: 1, slot: 3, status: 'ready' },
      { id: uid('b'), type: 'barracks', level: 1, slot: 6, status: 'ready' },
      { id: uid('b'), type: 'warehouse', level: 1, slot: 7, status: 'ready' },
      { id: uid('b'), type: 'house', level: 1, slot: 8, status: 'ready' }
    ],
    constructionQueue: [],
    constructionQueueExtras: [
      { slot: 2, expiresAt: 0 },
      { slot: 3, expiresAt: 0 }
    ],
    recruitQueue: [],
    troops: { spearman: 35, swordsman: 18, archer: 12, knight: 0 },
    missions: [],
    reports: [{ id: uid('r'), at: created, title: 'Bem-vindo ao reino', result: 'info', text: 'A tua fortaleza está pronta. Produz recursos, recruta tropas e expande o território.' }],
    targets: [],
    falkenProgressionVersion: FALKEN_PROGRESSION_VERSION,
    falkenPopulationVersion: FALKEN_POPULATION_VERSION,
    alliance: null,
    questsClaimed: [],
    dailyClaimAt: 0,
    chat: [],
    lastTick: created,
    createdAt: created,
    totalBattles: 0,
    totalVictories: 0,
    foodDesertion: { version: 1, totalDeserted: 0, events: 0, lastEventAt: 0 }
  };
  newProfile.targets = defaultMapTargets(newProfile);
  return newProfile;
}

function getKeepLevel() {
  return profile?.buildings.find(b => b.type === 'keep')?.level || 1;
}

function buildingCount(type) {
  return profile.buildings.filter(b => b.type === type).length;
}

function hasBuilding(type) {
  return profile.buildings.some(b => b.type === type && b.level > 0);
}

function warehouseCapacity() {
  const levels = profile.buildings.filter(b => b.type === 'warehouse').reduce((sum, b) => sum + b.level, 0);
  return 9000 + levels * 6000 + getKeepLevel() * 1200;
}

function productivityMultiplier() {
  if (globalThis.ReinosCastleSystem?.productivityMultiplier) {
    return globalThis.ReinosCastleSystem.productivityMultiplier(profile);
  }
  const decor = profile.buildings.filter(b => b.type === 'decoration').reduce((sum, b) => sum + b.level, 0);
  return 1 + decor * 0.04;
}

function resourceRates() {
  const rates = { wood: 0, stone: 0, food: 0, coins: 0 };
  for (const building of profile.buildings) {
    if (building.level < 1 || building.status === 'constructing') continue;
    const def = BUILDINGS[building.type];
    if (!def?.production) continue;
    for (const [resource, base] of Object.entries(def.production)) {
      rates[resource] += base * Math.pow(1.45, building.level - 1);
    }
  }
  const multiplier = productivityMultiplier();
  Object.keys(rates).forEach(k => rates[k] *= multiplier);
  globalThis.ReinosOutposts?.applyProduction?.(rates, outpostSystemContext());
  globalThis.ReinosTroopUpkeep?.applyToRates?.(rates, profile);
  return rates;
}

function buildingCost(type, targetLevel = 1) {
  const def = BUILDINGS[type];
  const factor = Math.pow(1.62, targetLevel - 1);
  return Object.fromEntries(Object.entries(def.baseCost).map(([key, value]) => [key, Math.round(value * factor)]));
}

function buildingTime(type, targetLevel = 1) {
  return Math.round(BUILDINGS[type].baseTime * Math.pow(1.38, targetLevel - 1) * 1000);
}

function canAfford(cost) {
  if (globalThis.ReinosResourceShortageFeedback?.canAfford) {
    return globalThis.ReinosResourceShortageFeedback.canAfford(profile?.resources, cost);
  }
  return Object.entries(cost).every(([key, value]) => (profile.resources[key] || 0) >= value);
}

function spend(cost) {
  for (const [key, value] of Object.entries(cost)) profile.resources[key] -= value;
}

function addResources(resources) {
  const cap = warehouseCapacity();
  for (const [key, value] of Object.entries(resources)) {
    if (key === 'gems') profile.resources[key] = Math.max(0, (profile.resources[key] || 0) + value);
    else profile.resources[key] = clamp((profile.resources[key] || 0) + value, 0, cap);
  }
}

function gainXp(amount) {
  profile.xp += amount;
  while (profile.xp >= profile.xpToNext) {
    profile.xp -= profile.xpToNext;
    profile.level += 1;
    profile.xpToNext = Math.round(profile.xpToNext * 1.45);
    const reward = { coins: 500 * profile.level, gems: 10 };
    addResources(reward);
    toast(`Subiste para o nível ${profile.level}! Recebeste moedas e coroas.`, 'success');
  }
}

function applyServerDataSnapshot(serverData) {
  if (!serverData || !Array.isArray(serverData.users) || !serverData.profiles || !currentUser) return false;
  const previousUserId = currentUser.id;
  db = serverData;
  const updatedUser = db.users.find(user => user.id === previousUserId);
  if (updatedUser && db.profiles[updatedUser.id]) {
    currentUser = updatedUser;
    profile = db.profiles[updatedUser.id];
    db.sessionUserId = updatedUser.id;
  }
  return true;
}

async function persist(force = false) {
  if (!db || !currentUser || !profile) return;
  db.profiles[currentUser.id] = profile;
  db.sessionUserId = currentUser.id;
  saveCounter += 1;
  if (force || saveCounter >= 4) {
    saveCounter = 0;
    const savedData = await APP_API.saveData(db);
    if (savedData && savedData !== true) applyServerDataSnapshot(savedData);
  }
}

async function adminGrantResources(targetUserId, resources) {
  const result = await APP_API.adminGrantResources?.(targetUserId, resources);
  if (result?.data) applyServerDataSnapshot(result.data);
  return result;
}

async function adminGrantInventory(targetUserId, inventory = {}) {
  if (APP_API.adminGrantInventory) {
    const result = await APP_API.adminGrantInventory(targetUserId, inventory);
    if (result?.data) applyServerDataSnapshot(result.data);
    return result;
  }
  if (inventory.resources && Object.keys(inventory.resources).length) {
    return adminGrantResources(targetUserId, inventory.resources);
  }
  throw new Error('O servidor desta versão não suporta oferta direta de tropas/ferramentas. Atualiza o backend.');
}

async function adminSetAccess(targetUserId, enabled) {
  const result = await APP_API.adminSetAccess?.(targetUserId, enabled);
  if (result?.data) applyServerDataSnapshot(result.data);
  return result;
}

async function resetCurrentProfileToNewAccount() {
  if (!currentUser) throw new Error('Sessão inválida. Faz login novamente.');
  const freshProfile = initialProfile(currentUser.id, currentUser.username);
  if (APP_API.resetProfile) {
    const result = await APP_API.resetProfile(freshProfile);
    if (result?.data) applyServerDataSnapshot(result.data);
    else {
      profile = freshProfile;
      db.profiles[currentUser.id] = profile;
      db.sessionUserId = currentUser.id;
    }
    return result;
  }
  profile = freshProfile;
  db.profiles[currentUser.id] = profile;
  db.sessionUserId = currentUser.id;
  await persist(true);
  return { ok: true, data: db };
}

function marketSystemContext() {
  return {
    getDb: () => db,
    getCurrentUser: () => currentUser,
    getProfile: () => profile,
    buildings: BUILDINGS,
    resources: RESOURCE_META,
    format: fmt,
    formatTime: fmtTime,
    escapeHtml,
    now,
    toast,
    openModal,
    closeModal,
    renderGame,
    refreshDatabaseFromServer,
    hasBuilding,
    applyServerDataSnapshot,
    sendResources: async (targetUserId, resources) => {
      if (!APP_API.marketSendResources) throw new Error('Envio de recursos indisponível neste cliente.');
      const result = await APP_API.marketSendResources(targetUserId, resources);
      if (result?.data) applyServerDataSnapshot(result.data);
      return result;
    }
  };
}

async function refreshDatabaseFromServer() {
  if (!currentUser || !profile) return false;
  try {
    await persist(true);
    const serverData = await APP_API.loadData();
    if (!serverData || !Array.isArray(serverData.users) || !serverData.profiles) return false;
    applyServerDataSnapshot(serverData);
    return true;
  } catch (_) {
    return false;
  }
}

async function backgroundRefreshFromServer() {
  if (!currentUser || !profile || serverRefreshInFlight) return false;
  const time = now();
  if (time - lastServerRefreshAt < 1500) return false;
  lastServerRefreshAt = time;
  serverRefreshInFlight = true;
  try {
    const beforeInventory = JSON.stringify({
      resources: profile.resources || {},
      troops: profile.troops || {},
      siegeTools: profile.siegeTools || {}
    });
    const beforeReports = Array.isArray(profile.reports) ? profile.reports.length : 0;
    const serverData = await APP_API.loadData();
    if (!serverData || !Array.isArray(serverData.users) || !serverData.profiles) return false;
    const applied = applyServerDataSnapshot(serverData);
    if (applied) {
      updateHud();
      updateDailyRewardIndicators();
      renderRightbar();
      const afterInventory = JSON.stringify({
        resources: profile.resources || {},
        troops: profile.troops || {},
        siegeTools: profile.siegeTools || {}
      });
      const afterReports = Array.isArray(profile.reports) ? profile.reports.length : 0;
      if (afterInventory !== beforeInventory || afterReports !== beforeReports) {
        renderRightbar();
        globalThis.ReinosAdmin?.afterRender?.(adminSystemContext());
      }
    }
    return applied;
  } catch (_) {
    return false;
  } finally {
    serverRefreshInFlight = false;
  }
}

async function refreshRankingFromServer() {
  rankingCache = null;
  try {
    await refreshDatabaseFromServer();
    const result = await APP_API.getRanking?.();
    if (result?.ok && Array.isArray(result.entries)) {
      rankingCache = result.entries.map(entry => ({
        ...entry,
        you: entry.id === currentUser?.id
      }));
      return true;
    }
  } catch (_) {}
  return false;
}

function toast(message, type = '') {
  const detailed = globalThis.ReinosResourceShortageFeedback?.consumeToast?.(message, type);
  const root = $('#toast-root');
  const element = document.createElement('div');
  element.className = `toast ${type}${detailed?.className ? ` ${detailed.className}` : ''}`;
  element.textContent = detailed?.message || message;
  root.appendChild(element);
  setTimeout(() => element.remove(), detailed?.duration || 3600);
}

function openModal(title, body, actions = '') {
  $('#modal-root').innerHTML = `
    <div class="modal-backdrop" data-action="modal-close-backdrop">
      <div class="modal" role="dialog" aria-modal="true">
        <div class="modal-head"><h3>${title}</h3><button class="modal-close" data-action="modal-close">×</button></div>
        <div class="modal-body">${body}</div>
        ${actions ? `<div class="modal-actions">${actions}</div>` : ''}
      </div>
    </div>`;
  document.body.classList.add('modal-open-ui');
}

function closeModal() {
  $('#modal-root').innerHTML = '';
  document.body.classList.remove('modal-open-ui');
}

function falkenEspionageContext() {
  return {
    getProfile: () => profile,
    getCurrentUser: () => currentUser,
    uid,
    now,
    format: fmt,
    formatTime: fmtTime,
    escapeHtml,
    openModal,
    closeModal,
    toast,
    persist
  };
}


function currentLocationLabelForGge() {
  if (!profile) return 'Castelo';
  if (activeView === 'map') return 'Mapa Mundial';
  const outpost = globalThis.ReinosOutposts?.activeOutpost?.(outpostSystemContext());
  if (outpost?.name) return outpost.name;
  return profile.kingdomName || currentUser?.username || 'Castelo Principal';
}

globalThis.ReinosGetCurrentLocationLabel = currentLocationLabelForGge;

function attackPlannerContext() {
  return {
    getDb: () => db,
    getProfile: () => profile,
    getCurrentUser: () => currentUser,
    getTroopsMeta: () => TROOPS,
    uid,
    now,
    clamp,
    format: fmt,
    formatTime: fmtTime,
    escapeHtml,
    openModal,
    closeModal,
    toast,
    persist,
    adminGrantResources,
    renderGame,
    hasBuilding,
    openMarketSend: targetUserId => globalThis.ReinosMarketSystem?.openSendModal?.(marketSystemContext(), targetUserId),
    addResources,
    gainXp,
    falkenStrength,
    falkenLoot,
    getAttackTarget: (targetId, targetType = null) => {
      if (targetType === 'player' || String(targetId || '').startsWith('player:')) {
        return globalThis.ReinosPlayerInteractions?.makeTarget?.(attackPlannerContext(), targetId) || null;
      }
      if (targetType === 'outpost' || String(targetId || '').startsWith('outpost:')) {
        return globalThis.ReinosOutposts?.makeAttackTarget?.(profile, targetId, outpostSystemContext()) || null;
      }
      return profile?.targets?.find(target => target.id === targetId) || null;
    },
    getAttackDefense: target => target?.targetType === 'player'
      ? globalThis.ReinosPlayerInteractions?.generateDefense?.(target, attackPlannerContext())
      : target?.targetType === 'outpost'
        ? globalThis.ReinosOutposts?.generateDefense?.(target, outpostSystemContext())
        : globalThis.RealmFalkenEspionage?.generateDefense?.(target),
    resolvePlayerBattle: mission => globalThis.ReinosPlayerInteractions?.resolveBattle?.(mission, attackPlannerContext()) || false,
    worldPixelX,
    worldPixelY,
    navigate: view => {
      activeView = view;
      renderGame();
    },
    openMapAt: (x, y) => {
      pendingMapFocus = {
        x: normalizeWorldCoordinate(x, profile?.worldPosition?.x ?? WORLD_COORD_CENTER),
        y: normalizeWorldCoordinate(y, profile?.worldPosition?.y ?? WORLD_COORD_CENTER)
      };
      activeView = 'map';
      renderGame();
    },
    finishBattleMission: (mission, survivors, loot, win) => globalThis.ReinosWorldMarches?.finishBattleMission?.(attackPlannerContext(), mission, survivors, loot, win),
    createMarchMission: (target, troops, extra = {}) => globalThis.ReinosWorldMarches?.createMission?.(attackPlannerContext(), target, troops, extra)
  };
}

function adminSystemContext() {
  return {
    getDb: () => db,
    getCurrentUser: () => currentUser,
    getProfile: () => profile,
    getTroopsMeta: () => TROOPS,
    getResourceMeta: () => RESOURCE_META,
    format: fmt,
    escapeHtml,
    persist,
    adminGrantResources,
    adminGrantInventory,
    adminSetAccess,
    renderGame,
    toast
  };
}

const CONSTRUCTION_QUEUE_MAX_EXTRA = 2;
const CONSTRUCTION_QUEUE_PURCHASES = [
  { id: '1h', label: '1 hora', ms: 60 * 60 * 1000, cost: 15 },
  { id: '24h', label: '24 horas', ms: 24 * 60 * 60 * 1000, cost: 95 },
  { id: '7d', label: '7 dias', ms: 7 * 24 * 60 * 60 * 1000, cost: 450 }
];

function ensureConstructionQueueExtras(targetProfile = profile) {
  if (!targetProfile) return [];
  const previous = Array.isArray(targetProfile.constructionQueueExtras) ? targetProfile.constructionQueueExtras : [];
  const normalized = [];
  for (let index = 0; index < CONSTRUCTION_QUEUE_MAX_EXTRA; index += 1) {
    const slot = index + 2;
    const found = previous.find(item => Number(item?.slot) === slot) || previous[index] || {};
    normalized.push({
      slot,
      expiresAt: Math.max(0, Number(found.expiresAt) || 0)
    });
  }
  targetProfile.constructionQueueExtras = normalized;
  return normalized;
}

function activeConstructionExtraCount(targetProfile = profile, time = now()) {
  return ensureConstructionQueueExtras(targetProfile).filter(item => Number(item.expiresAt) > time).length;
}

function constructionQueueLimit(targetProfile = profile) {
  return 1 + activeConstructionExtraCount(targetProfile);
}

function constructionQueueSlot(slotNumber, targetProfile = profile) {
  const slot = Number(slotNumber);
  return ensureConstructionQueueExtras(targetProfile).find(item => Number(item.slot) === slot) || null;
}

function constructionQueuePackage(packageId) {
  return CONSTRUCTION_QUEUE_PURCHASES.find(item => item.id === packageId) || null;
}

function nextLockedConstructionSlot(targetProfile = profile) {
  const extras = ensureConstructionQueueExtras(targetProfile);
  const time = now();
  return extras.find(item => Number(item.expiresAt) <= time) || null;
}

function openConstructionQueueModal(slotNumber = null) {
  ensureConstructionQueueExtras(profile);
  let slot = Number(slotNumber) || Number(nextLockedConstructionSlot(profile)?.slot || 2);
  slot = clamp(slot, 2, 3);
  const previousSlot = slot > 2 ? constructionQueueSlot(slot - 1, profile) : null;
  if (previousSlot && Number(previousSlot.expiresAt) <= now()) {
    slot = slot - 1;
  }
  const entry = constructionQueueSlot(slot, profile);
  const active = entry && Number(entry.expiresAt) > now();
  const status = active
    ? `<div class="stats-line"><span>Estado</span><strong>Ativa por <span data-countdown="${entry.expiresAt}">${fmtTime(entry.expiresAt - now())}</span></strong></div>`
    : '<div class="stats-line"><span>Estado</span><strong>Bloqueada</strong></div>';
  const packages = CONSTRUCTION_QUEUE_PURCHASES.map(pack => `
    <div class="construction-extra-option">
      <div><strong>${escapeHtml(pack.label)}</strong><small>Desbloqueia a Fila ${slot} durante ${escapeHtml(pack.label)}.</small></div>
      <button class="action-btn" data-action="construction-queue-buy" data-slot="${slot}" data-package="${escapeHtml(pack.id)}">${RESOURCE_META.gems.icon} ${fmt(pack.cost)}</button>
    </div>`).join('');
  openModal(`Comprar fila extra ${slot - 1}`, `
    <p>Compra uma fila temporária de construção. Enquanto estiver ativa, podes ter mais uma obra em simultâneo.</p>
    ${status}
    <div class="construction-extra-note">Se o tempo da fila acabar com uma construção em curso, essa construção termina normalmente. Depois a fila bloqueia e não inicia novas obras.</div>
    <div class="construction-extra-options">${packages}</div>
  `, '<button class="secondary-btn" data-action="modal-close">Fechar</button>');
  document.querySelector('.modal')?.classList.add('construction-extra-modal');
}

async function buyConstructionQueue(slotNumber, packageId) {
  ensureConstructionQueueExtras(profile);
  const slot = clamp(Number(slotNumber) || 2, 2, 3);
  const previousSlot = slot > 2 ? constructionQueueSlot(slot - 1, profile) : null;
  if (previousSlot && Number(previousSlot.expiresAt) <= now()) return toast('Compra primeiro a primeira fila extra.', 'error');
  const pack = constructionQueuePackage(packageId);
  const entry = constructionQueueSlot(slot, profile);
  if (!pack || !entry) return toast('Opção de fila inválida.', 'error');
  if ((profile.resources.gems || 0) < pack.cost) return toast('Não tens coroas suficientes.', 'error');
  profile.resources.gems -= pack.cost;
  entry.expiresAt = Math.max(now(), Number(entry.expiresAt) || 0) + pack.ms;
  profile.reports.push({ id: uid('r'), at: now(), title: `Fila extra de construção comprada`, result: 'info', reportType: 'kingdom', text: `A Fila ${slot} de construção ficou ativa durante ${pack.label}.` });
  closeModal();
  renderGame();
  await persist(true);
  toast(`Fila extra ${slot - 1} ativa por ${pack.label}.`, 'success');
}

function workshopSystemContext() {
  return {
    getProfile: () => profile,
    getBuildingsMeta: () => BUILDINGS,
    getResourceMeta: () => RESOURCE_META,
    uid,
    now,
    format: fmt,
    formatTime: fmtTime,
    escapeHtml,
    canAfford,
    spend,
    addResources,
    costsHtml,
    buildingCost,
    buildingTime,
    openModal,
    closeModal,
    toast,
    persist,
    renderGame,
    renderActiveView
  };
}

function barracksSystemContext() {
  return {
    getProfile: () => profile,
    getTroopsMeta: () => TROOPS,
    getBuildingsMeta: () => BUILDINGS,
    getResourceMeta: () => RESOURCE_META,
    uid,
    now,
    format: fmt,
    formatTime: fmtTime,
    escapeHtml,
    hasBuilding,
    canAfford,
    spend,
    costsHtml,
    buildingCost,
    buildingTime,
    openModal,
    closeModal,
    toast,
    persist,
    renderGame,
    renderActiveView,
    renderRightbar,
    updateHud,
    queueRecruitment: (type, count, options = {}) => recruitTroops(type, count, options)
  };
}


function equipmentSystemContext() {
  return {
    getProfile: () => profile,
    getCurrentUser: () => currentUser,
    getContent: () => $('#content'),
    getActiveView: () => activeView,
    uid,
    now,
    format: fmt,
    formatTime: fmtTime,
    escapeHtml,
    viewHeader,
    openModal,
    closeModal,
    toast,
    persist,
    renderGame,
    renderActiveView,
    updateHud
  };
}

function castleSystemContext() {
  return {
    getProfile: () => profile,
    getBuildingsMeta: () => BUILDINGS,
    getResourceMeta: () => RESOURCE_META,
    getContent: () => $('#content'),
    getKeepLevel,
    buildingCount,
    buildingCost,
    buildingTime,
    constructionQueueLimit,
    canAfford,
    spend,
    costsHtml,
    format: fmt,
    formatTime: fmtTime,
    escapeHtml,
    now,
    viewHeader,
    openModal,
    closeModal,
    toast,
    persist,
    renderGame,
    renderActiveView,
    startBuilding,
    demolishBuilding
  };
}

function outpostSystemContext() {
  return {
    getProfile: () => profile,
    getCurrentUser: () => currentUser,
    getResourceMeta: () => RESOURCE_META,
    getTroopsMeta: () => TROOPS,
    getContent: () => $('#content'),
    getActiveView: () => activeView,
    resourceMeta: RESOURCE_META,
    uid,
    now,
    format: fmt,
    formatTime: fmtTime,
    escapeHtml,
    viewHeader,
    openModal,
    closeModal,
    toast,
    persist,
    renderGame,
    renderActiveView,
    navigate: view => { activeView = view; renderGame(); },
    openAttackPlanner: targetId => showAttackModal(targetId),
    costsHtml,
    worldPixelX,
    worldPixelY,
    normalizeWorldCoordinate,
    getBuildingsMeta: () => BUILDINGS,
    buildingCost,
    buildingTime,
    constructionQueueLimit,
    canAfford,
    spend,
    renderRightbar,
    updateHud
  };
}

function costsHtml(cost) {
  return Object.entries(cost).map(([key, value]) => `<span class="cost">${RESOURCE_META[key]?.icon || ''} ${fmt(value)}</span>`).join('');
}

function renderAuth(message = '') {
  cleanupWorldMap();
  currentUser = null;
  profile = null;
  const isLogin = authMode === 'login';
  $('#app').innerHTML = `
    <section class="auth-shell">
      <div class="auth-card">
        <div class="brand">
          <div class="brand-emblem">♛</div>
          <h1>Reinos de Ferro</h1>
          <p>Constrói. Conquista. Governa.</p>
        </div>
        <div class="auth-tabs">
          <button class="${isLogin ? 'active' : ''}" data-action="auth-switch" data-mode="login">Iniciar sessão</button>
          <button class="${!isLogin ? 'active' : ''}" data-action="auth-switch" data-mode="register">Criar conta</button>
        </div>
        <form id="auth-form">
          <div class="field"><label>Nome de jogador</label><input id="auth-username" maxlength="20" autocomplete="username" placeholder="Ex.: Edgar" required /></div>
          <div class="field"><label>Palavra-passe</label><input id="auth-password" type="password" minlength="4" autocomplete="${isLogin ? 'current-password' : 'new-password'}" placeholder="Mínimo de 4 caracteres" required /></div>
          ${!isLogin ? '<div class="field"><label>Confirmar palavra-passe</label><input id="auth-password2" type="password" minlength="4" autocomplete="new-password" required /></div>' : ''}
          <button class="primary-btn" type="submit">${isLogin ? 'Entrar no reino' : 'Fundar novo reino'}</button>
        </form>
        <div class="auth-message">${escapeHtml(message)}</div>
      </div>
    </section>`;
}

function renderGame() {
  ensureConstructionQueueExtras(profile);
  globalThis.ReinosCastleSystem?.ensureState?.(profile);
  globalThis.ReinosWorkshopSystem?.ensureState?.(profile);
  globalThis.ReinosEquipmentSystem?.ensureState?.(profile);
  globalThis.ReinosOutposts?.ensureState?.(profile, outpostSystemContext());

  // v0.1.125 — não reconstruir a interface inteira enquanto o painel Admin está aberto.
  // A sincronização do servidor chamava renderGame periodicamente e isso fazia o scroll do Admin voltar ao topo.
  if (globalThis.ReinosAdmin?.isPanelOpen?.() && document.querySelector('#app .game-shell') && document.querySelector('#content .admin-view')) {
    updateHud();
    renderRightbar();
    globalThis.ReinosIncomingAttackAlert?.afterRender?.(attackPlannerContext());
    return;
  }

  const isOutpostCastle = activeView === 'castle' && !!globalThis.ReinosOutposts?.isActiveOutpost?.(outpostSystemContext());
  const castleExpansionDisabled = !isOutpostCastle && profile.castleState?.expansionLevel >= (globalThis.ReinosCastleSystem?.MAX_EXPANSION || 6);
  const castleActionNav = activeView === 'castle' ? `
    <button class="nav-btn castle-nav-action" data-action="castle-fortification-open" title="Defesas">
      <span class="nav-icon">🛡️</span><span>Defesas</span>
    </button>
    <button class="nav-btn castle-nav-action" data-action="castle-expand" title="Expandir" ${castleExpansionDisabled ? 'disabled' : ''}>
      <span class="nav-icon">🧱</span><span>Expandir</span>
    </button>
    <button class="nav-btn castle-nav-action" data-action="build-first-empty" title="Construir">
      <span class="nav-icon">🏗️</span><span>Construir</span>
    </button>` : '';
  const navButtonHtml = ([id, icon, label]) => `
    <button class="nav-btn ${activeView === id ? 'active' : ''}" data-action="nav" data-view="${id}">
      <span class="nav-icon">${icon}</span><span>${label}</span>
    </button>`;
  const primaryNav = NAV_ITEMS.slice(0, 2).map(navButtonHtml).join('');
  const secondaryNav = NAV_ITEMS.slice(2).map(navButtonHtml).join('');
  const nav = `${primaryNav}${castleActionNav}${secondaryNav}`;
  $('#app').innerHTML = `
    <div class="game-shell">
      <header class="topbar">
        <div class="mini-brand"><div class="crest">♛</div><div><strong>Reinos de Ferro</strong><small id="kingdom-label">${escapeHtml(profile.kingdomName)}</small></div></div>
        <div class="resource-row" id="resource-row"></div>
        <div class="top-actions">
          <button class="icon-btn" title="Definições" data-action="settings-open">⚙️</button>
          <button class="icon-btn" title="Terminar sessão" data-action="logout">🚪</button>
        </div>
      </header>
      <main class="main-layout">
        <aside class="sidebar">
          <div class="player-card">
            <div class="player-name">${escapeHtml(globalThis.ReinosOwnerPrivacy?.publicPlayerName?.(currentUser.username) || currentUser.username)}</div>
            <div class="level-line"><span>Nível <b id="player-level">${profile.level}</b></span><span id="xp-label">${fmt(profile.xp)}/${fmt(profile.xpToNext)} XP</span></div>
            <div class="progress"><i id="xp-bar" style="width:${(profile.xp/profile.xpToNext)*100}%"></i></div>
            <div class="level-line"><span>Honra</span><b id="honor-label">${fmt(profile.honor)}</b></div>
            <div class="level-line"><span>Poder</span><b id="power-label">${fmt(calculatePower())}</b></div>
          </div>
          <nav>${nav}</nav>
        </aside>
        <section class="content" id="content"></section>
        <aside class="rightbar" id="rightbar"></aside>
      </main>
      <footer class="footer"><span>Multiplayer • progresso guardado no servidor</span><span>Versão ${escapeHtml(appVersion)}</span></footer>
    </div>`;
  renderActiveView();
  updateHud();
  renderRightbar();
  globalThis.ReinosAdmin?.afterRender?.(adminSystemContext());
  globalThis.ReinosIncomingAttackAlert?.afterRender?.(attackPlannerContext());
}

function calculatePower() {
  const buildingPower = profile.buildings.reduce((sum, b) => sum + b.level * 55, 0);
  const troopPower = Object.entries(profile.troops).reduce((sum, [type, count]) => sum + TROOPS[type].attack * count, 0);
  profile.power = Math.round(buildingPower + troopPower);
  return profile.power;
}

function updateHud() {
  if (!profile || !$('#resource-row')) return;
  const rates = resourceRates();
  const cap = warehouseCapacity();
  $('#resource-row').innerHTML = Object.keys(RESOURCE_META).map(key => {
    const meta = RESOURCE_META[key];
    const rate = rates[key] || 0;
    const capText = key === 'gems' ? '' : ` / ${fmt(cap)}`;
    const rateText = globalThis.ReinosTroopUpkeep?.formatSignedRate?.(rate, fmt) || `${rate >= 0 ? '+' : '−'}${fmt(Math.abs(rate))}/h`;
    const rateClass = rate < 0 ? 'is-negative' : rate > 0 ? 'is-positive' : '';
    const recentStarvation = key === 'food' && now() - Number(profile.foodDesertion?.lastEventAt || 0) < 60000;
    return `<div class="resource ${recentStarvation ? 'food-starvation' : ''}" data-resource="${key}"><div class="r-top"><span>${meta.icon} <span class="r-name">${meta.name}</span></span><span class="r-value">${fmt(profile.resources[key])}${capText}</span></div>${rate ? `<div class="r-rate ${rateClass}">${rateText}</div>` : '<div class="r-rate">&nbsp;</div>'}</div>`;
  }).join('');
  const level = $('#player-level'); if (level) level.textContent = profile.level;
  const xpLabel = $('#xp-label'); if (xpLabel) xpLabel.textContent = `${fmt(profile.xp)}/${fmt(profile.xpToNext)} XP`;
  const xpBar = $('#xp-bar'); if (xpBar) xpBar.style.width = `${clamp(profile.xp / profile.xpToNext * 100, 0, 100)}%`;
  const honor = $('#honor-label'); if (honor) honor.textContent = fmt(profile.honor);
  const power = $('#power-label'); if (power) power.textContent = fmt(calculatePower());
  updateDailyRewardIndicators();
}

function isDailyRewardReady() {
  if (!profile) return false;
  const day = 24 * 60 * 60 * 1000;
  return now() - Number(profile.dailyClaimAt || 0) >= day;
}

function updateDailyRewardIndicators() {
  const ready = isDailyRewardReady();
  document.querySelectorAll('[data-action="claim-daily"]').forEach(button => {
    if (ready) {
      button.setAttribute('data-alert', '1');
      button.title = 'Recompensa diária disponível';
    } else {
      button.removeAttribute('data-alert');
      const remaining = Math.max(0, 24 * 60 * 60 * 1000 - (now() - Number(profile?.dailyClaimAt || 0)));
      button.title = remaining ? `Recompensa diária disponível em ${fmtTime(remaining)}` : 'Recompensa diária';
    }
  });
}

globalThis.ReinosIsDailyRewardReady = isDailyRewardReady;
globalThis.ReinosUpdateDailyRewardIndicators = updateDailyRewardIndicators;

function openRecruitmentMenuFromArmyShortcut() {
  const barracks = profile?.buildings?.find(building => building.type === 'barracks' && building.status === 'ready' && Number(building.level) > 0);
  if (!barracks) {
    activeView = 'army';
    renderGame();
    toast('Precisas de um Quartel pronto para recrutar.', 'error');
    return true;
  }
  const opened = globalThis.ReinosBarracksSystem?.open?.(barracks.id, barracksSystemContext());
  if (!opened) toast('Não foi possível abrir o recrutamento do Quartel.', 'error');
  return true;
}

function renderRightbar() {
  const rightbar = $('#rightbar');
  if (!profile || !rightbar) return;

  const activeOutpost = globalThis.ReinosOutposts?.activeOutpost?.(outpostSystemContext()) || null;
  const constructionOwner = activeOutpost || profile;
  const constructionBuildings = Array.isArray(constructionOwner.buildings) ? constructionOwner.buildings : [];
  const constructionQueue = Array.isArray(constructionOwner.constructionQueue) ? constructionOwner.constructionQueue : [];

  const outpostNames = {
    keep: { name: 'Fortaleza', icon: '🏰' },
    outpost_keep: { name: 'Casa do Administrador', icon: '🏯' },
    farm: { name: 'Quinta', icon: '🌾' },
    woodcutter: { name: 'Madeireira', icon: '🪵' },
    quarry: { name: 'Pedreira', icon: '🪨' },
    warehouse: { name: 'Depósito', icon: '📦' },
    house: { name: 'Casa', icon: '🏠' },
    barracks: { name: 'Quartel do Posto', icon: '⚔️' },
    guardhouse: { name: 'Posto de Guarda', icon: '🛡️' },
    watchtower: { name: 'Torre de Vigia', icon: '🗼' },
    marketplace: { name: 'Mercado', icon: '⚖️' },
    workshop: { name: 'Oficina', icon: '🔨' },
    stables: { name: 'Estábulos', icon: '🐎' },
    hospital: { name: 'Hospital', icon: '🏥' },
    tavern: { name: 'Taverna', icon: '🍺' },
    decoration: { name: 'Monumento', icon: '🗿' }
  };

  const buildingInfo = building => {
    const base = BUILDINGS[building?.type] || {};
    const extra = outpostNames[building?.type] || {};
    return {
      name: extra.name || base.name || 'Construção',
      icon: extra.icon || base.icon || '🏗️'
    };
  };

  const slot = ({ kind, icon, title, subtitle, countdown, action, id, view, badge, disabled = false }) => {
    const attrs = disabled
      ? 'disabled aria-disabled="true"'
      : `${action ? `data-action="${escapeHtml(action)}"` : ''}${id ? ` data-id="${escapeHtml(id)}"` : ''}${view ? ` data-view="${escapeHtml(view)}"` : ''}`;
    const timer = countdown ? `<small data-countdown="${countdown}">${fmtTime(countdown - now())}</small>` : `<small>${escapeHtml(subtitle || '')}</small>`;
    const badgeHtml = badge ? `<b class="gge-queue-badge">${escapeHtml(String(badge))}</b>` : '';
    return `<button type="button" class="gge-queue-slot ${kind || ''} ${disabled ? 'is-empty' : ''}" ${attrs} title="${escapeHtml(title || '')}">
      ${badgeHtml}<span class="gge-queue-main-icon">${icon}</span>${timer}
    </button>`;
  };

  const moreSlot = (count, title) => slot({
    kind: 'more',
    icon: `+${count}`,
    title,
    subtitle: 'na fila',
    disabled: true
  });

  const MAX_VISIBLE_PER_SECTION = 3;
  const sortedConstructionQueue = constructionQueue
    .slice()
    .sort((a, b) => Number(a.endAt || 0) - Number(b.endAt || 0));
  ensureConstructionQueueExtras(profile);
  const activeConstructionLimit = constructionQueueLimit(profile);
  const constructionItems = [];
  for (let index = 0; index < MAX_VISIBLE_PER_SECTION; index += 1) {
    const item = sortedConstructionQueue[index];
    if (item) {
      const building = constructionBuildings.find(b => b.id === item.buildingId);
      const info = buildingInfo(building);
      const actionText = item.action === 'build' ? 'A construir' : 'A melhorar';
      constructionItems.push(slot({
        kind: 'construction',
        icon: info.icon,
        title: `${actionText}: ${info.name}`,
        countdown: item.endAt,
        action: 'building-open',
        id: building?.id || item.buildingId,
        badge: '🔨'
      }));
      continue;
    }
    const slotNumber = index + 1;
    if (slotNumber <= activeConstructionLimit) {
      constructionItems.push(slot({
        kind: 'construction is-free',
        icon: '🏗️',
        title: `Fila ${slotNumber} livre`,
        subtitle: 'Livre',
        action: activeView === 'castle' ? 'build-first-empty' : '',
        disabled: activeView !== 'castle'
      }));
      continue;
    }
    const extra = constructionQueueSlot(slotNumber, profile);
    const extraTitle = extra?.expiresAt > now()
      ? `Fila ${slotNumber} ativa até ${new Date(extra.expiresAt).toLocaleString('pt-PT')}`
      : `Comprar Fila ${slotNumber}`;
    constructionItems.push(slot({
      kind: 'construction is-locked',
      icon: '🔒',
      title: extraTitle,
      subtitle: 'Comprar',
      action: 'construction-queue-open',
      id: String(slotNumber),
      badge: '👑'
    }));
  }
  if (sortedConstructionQueue.length > MAX_VISIBLE_PER_SECTION) constructionItems.push(moreSlot(sortedConstructionQueue.length - MAX_VISIBLE_PER_SECTION, 'Mais construções em fila'));

  const recruitmentItems = (profile.recruitQueue || [])
    .slice()
    .sort((a, b) => Number(a.endAt || 0) - Number(b.endAt || 0))
    .slice(0, MAX_VISIBLE_PER_SECTION)
    .map(item => slot({
      kind: 'recruitment',
      icon: TROOPS[item.type]?.icon || '⚔️',
      title: `${item.count} ${TROOPS[item.type]?.name || 'Tropas'}`,
      countdown: item.endAt,
      action: 'nav',
      view: 'army',
      badge: item.count
    }));
  if ((profile.recruitQueue || []).length > MAX_VISIBLE_PER_SECTION) recruitmentItems.push(moreSlot(profile.recruitQueue.length - MAX_VISIBLE_PER_SECTION, 'Mais recrutamentos em fila'));
  if (!recruitmentItems.length) recruitmentItems.push(slot({ kind: 'recruitment', icon: '⚔️', title: 'Nenhum recrutamento em curso', subtitle: 'Livre', disabled: true }));

  const movementIcon = mission => mission.type === 'market_transport'
    ? '🛒'
    : mission.cancelled && mission.state === 'returning'
      ? '↩️'
      : mission.state === 'returning'
        ? '↩️'
        : mission.state === 'resolving'
          ? '⚔️'
          : '🏇';
  const movementLabel = mission => mission.type === 'market_transport'
    ? (mission.state === 'returning' ? 'Carroças regressam de' : 'Transporte para')
    : mission.cancelled && mission.state === 'returning'
      ? 'Ataque cancelado — regresso de'
      : mission.state === 'returning'
        ? 'Regresso de'
        : mission.state === 'resolving'
          ? 'Batalha em'
          : 'Ataque a';
  const movementItems = (profile.missions || [])
    .slice()
    .sort((a, b) => Number(a.endAt || 0) - Number(b.endAt || 0))
    .slice(0, MAX_VISIBLE_PER_SECTION)
    .map(item => slot({
      kind: 'movement',
      icon: movementIcon(item),
      title: `${movementLabel(item)} ${item.targetName}`,
      countdown: item.endAt,
      action: 'mission-open',
      id: item.id,
      badge: item.state === 'returning' ? '↩' : '!' 
    }));
  if ((profile.missions || []).length > MAX_VISIBLE_PER_SECTION) movementItems.push(moreSlot(profile.missions.length - MAX_VISIBLE_PER_SECTION, 'Mais movimentos em curso'));
  if (!movementItems.length) movementItems.push(slot({ kind: 'movement', icon: '🧭', title: 'Nenhum exército em marcha', subtitle: 'Livre', disabled: true }));

  const section = (icon, label, items) => `<section class="gge-queue-section" aria-label="${escapeHtml(label)}">
    <div class="gge-queue-title"><span>${icon}</span><strong>${escapeHtml(label)}</strong></div>
    <div class="gge-queue-slots">${items.join('')}</div>
  </section>`;

  rightbar.innerHTML = `<div class="gge-queue-rail" aria-label="Fila lateral tipo GGE">
    ${section('🏗️', 'Construções', constructionItems)}
    ${section('⚔️', 'Recrutamento', recruitmentItems)}
    ${section('🧭', 'Movimentos', movementItems)}
  </div>`;
}

function renderActiveView() {
  if (!profile) return;
  if (activeView !== 'map') cleanupWorldMap();
  const content = $('#content');
  if (!content) return;
  const renderers = {
    castle: () => globalThis.ReinosOutposts?.renderCastle?.(outpostSystemContext()) || renderCastle(),
    buildings: renderBuildings,
    commanders: () => globalThis.ReinosEquipmentSystem?.render?.('commanders', equipmentSystemContext()),
    governors: () => globalThis.ReinosEquipmentSystem?.render?.('governors', equipmentSystemContext()),
    blacksmith: () => globalThis.ReinosEquipmentSystem?.renderSmith?.(equipmentSystemContext()),
    army: renderArmy,
    map: renderMap,
    alliance: renderAlliance,
    reports: renderReports,
    quests: renderQuests,
    ranking: renderRanking
  };
  renderers[activeView]?.();
}

function viewHeader(title, subtitle, extra = '') {
  const canCloseToCastle = activeView && activeView !== 'castle' && activeView !== 'map';
  const closeButton = canCloseToCastle
    ? '<button class="view-close-btn" type="button" title="Fechar" data-action="nav" data-view="castle">×</button>'
    : '';
  const extraHtml = extra ? `<div class="view-title-extra">${extra}</div>` : '';
  return `<div class="view-title"><div><h2>${title}</h2><p>${subtitle}</p></div>${extraHtml}${closeButton}</div>`;
}

function renderCastle() {
  if (globalThis.ReinosCastleSystem?.render?.(castleSystemContext())) return;
  const slots = Array.from({ length: 30 }, (_, slot) => {
    const building = profile.buildings.find(b => b.slot === slot);
    if (!building) return `<button class="castle-slot empty" data-action="build-slot" data-slot="${slot}" title="Construir neste espaço">＋</button>`;
    const def = BUILDINGS[building.type];
    const queue = profile.constructionQueue.find(q => q.buildingId === building.id);
    return `<button class="castle-slot" data-action="building-open" data-id="${building.id}">
      <div class="building"><div class="b-icon">${def.icon}</div><div class="b-name">${def.name}</div><div class="b-level">Nível ${building.level || 1}</div></div>
      ${queue ? `<div class="timer-badge" data-countdown="${queue.endAt}">${fmtTime(queue.endAt - now())}</div>` : ''}
    </button>`;
  }).join('');
  $('#content').innerHTML = `<div class="view">${viewHeader('O teu castelo', 'Seleciona um espaço vazio para construir ou um edifício para o melhorar.')}<div class="castle-scene"><div class="castle-grid">${slots}</div></div></div>`;
}

function renderBuildings() {
  const cards = Object.entries(BUILDINGS).map(([type, def]) => {
    const count = buildingCount(type);
    const nextLevel = count ? Math.max(...profile.buildings.filter(b => b.type === type).map(b => b.level)) + 1 : 1;
    const cost = buildingCost(type, nextLevel);
    const maxText = def.max ? `${count}/${def.max}` : `${count}`;
    return `<article class="game-card">
      <div class="game-card-head"><div class="game-card-icon">${def.icon}</div><div><h3>${def.name}</h3><small>Construídos: ${maxText}</small></div></div>
      <div class="game-card-body"><p>${def.description}</p><div class="stats-line"><span>Próximo custo estimado</span></div><div class="costs">${costsHtml(cost)}</div><div class="btn-row"><button class="action-btn" data-action="build-type" data-type="${type}" ${type === 'keep' || (def.max && count >= def.max) ? 'disabled' : ''}>Construir</button></div></div>
    </article>`;
  }).join('');
  $('#content').innerHTML = `<div class="view">${viewHeader('Edifícios', 'Expande a economia, defesa e capacidade militar do reino.')}<div class="card-grid">${cards}</div></div>`;
}

function renderArmy() {
  const barracksLevel = profile.buildings.find(b => b.type === 'barracks')?.level || 0;
  const cards = Object.entries(TROOPS).map(([type, troop]) => {
    const locked = !barracksLevel || (troop.buildingRequired && !hasBuilding(troop.buildingRequired));
    return `<article class="game-card">
      <div class="game-card-head"><div class="game-card-icon">${troop.icon}</div><div><h3>${troop.name}</h3><small>Disponíveis: ${fmt(profile.troops[type])}</small></div></div>
      <div class="game-card-body">
        <div class="stats-line"><span>Ataque</span><strong>${troop.attack}</strong></div>
        <div class="stats-line"><span>Defesa</span><strong>${troop.defense}</strong></div>
        <div class="stats-line"><span>Saque</span><strong>${troop.carry}</strong></div>
        <div class="costs">${costsHtml(troop.cost)}</div>
        <div class="btn-row"><button class="action-btn" data-action="recruit-open" data-type="${type}" ${locked ? 'disabled' : ''}>${locked ? 'Bloqueado' : 'Recrutar'}</button></div>
      </div>
    </article>`;
  }).join('');
  const totalAttack = Object.entries(profile.troops).reduce((s,[k,v]) => s + TROOPS[k].attack*v,0);
  const totalDefense = Object.entries(profile.troops).reduce((s,[k,v]) => s + TROOPS[k].defense*v,0);
  const foodUpkeep = globalThis.ReinosTroopUpkeep?.hourlyConsumption?.(profile) || 0;
  $('#content').innerHTML = `<div class="view">${viewHeader('Exército', 'Recruta tropas e prepara os teus ataques.', `<div><b>Ataque:</b> ${fmt(totalAttack)} &nbsp; <b>Defesa:</b> ${fmt(totalDefense)} &nbsp; <span class="troop-upkeep-summary">${RESOURCE_META.food.icon} Consumo: <strong>−${fmt(foodUpkeep)}/h</strong></span></div>`)}<div class="card-grid">${cards}</div></div>`;
}

function cleanupWorldMap() {
  if (typeof worldMapCleanup === 'function') worldMapCleanup();
  worldMapCleanup = null;
}

function centerWorldMap(viewport, coordinateX, coordinateY, smooth = false) {
  if (!viewport) return;
  const x = normalizeWorldCoordinate(coordinateX);
  const y = normalizeWorldCoordinate(coordinateY);
  const zoomController = viewport.__realmWorldMapZoom;

  if (zoomController) {
    zoomController.centerOnBasePixel(worldPixelX(x), worldPixelY(y), smooth);
    return;
  }

  const left = clamp(worldPixelX(x) - viewport.clientWidth / 2, 0, Math.max(0, WORLD_WIDTH - viewport.clientWidth));
  const top = clamp(worldPixelY(y) - viewport.clientHeight / 2, 0, Math.max(0, WORLD_HEIGHT - viewport.clientHeight));
  viewport.scrollTo({ left, top, behavior: smooth ? 'smooth' : 'auto' });
}

function setupWorldMap() {
  cleanupWorldMap();
  const viewport = $('#world-map');
  const stage = $('#world-map-stage');
  const surface = $('#world-space');
  const zoomReadout = $('#world-zoom-readout');
  if (!viewport || !stage || !surface) return;

  let dragging = false;
  let dragged = false;
  let startX = 0;
  let startY = 0;
  let startLeft = 0;
  let startTop = 0;
  let edgeX = 0;
  let edgeY = 0;
  let animationFrame = 0;
  let saveTimer = 0;

  const updateZoomReadout = zoom => {
    if (zoomReadout) zoomReadout.textContent = `Zoom ${Math.round(zoom * 100)}%`;
  };

  const zoomController = globalThis.RealmWorldMapZoom?.create({
    viewport,
    stage,
    surface,
    baseWidth: WORLD_WIDTH,
    baseHeight: WORLD_HEIGHT,
    initialZoom: profile.worldZoom,
    onChange: zoom => {
      profile.worldZoom = zoom;
      updateZoomReadout(zoom);
      clearTimeout(saveTimer);
      saveTimer = setTimeout(() => persist(false), 600);
    }
  });

  profile.worldZoom = zoomController?.getZoom() ?? 1;
  updateZoomReadout(profile.worldZoom);

  const updateCoordinates = () => {
    const center = zoomController
      ? zoomController.getViewportCenterBasePixel()
      : {
          x: viewport.scrollLeft + viewport.clientWidth / 2,
          y: viewport.scrollTop + viewport.clientHeight / 2
        };
    const coordinateX = pixelToWorldX(center.x);
    const coordinateY = pixelToWorldY(center.y);
    profile.worldView = { x: coordinateX, y: coordinateY };
    clearTimeout(saveTimer);
    saveTimer = setTimeout(() => persist(false), 600);
  };

  const onPointerDown = event => {
    if (event.button !== 0 || event.target.closest('button, input, .map-node')) return;
    dragging = true;
    dragged = false;
    startX = event.clientX;
    startY = event.clientY;
    startLeft = viewport.scrollLeft;
    startTop = viewport.scrollTop;
    viewport.classList.add('dragging');
    viewport.setPointerCapture?.(event.pointerId);
    event.preventDefault();
  };

  const onPointerMove = event => {
    const rect = viewport.getBoundingClientRect();
    const edge = 58;
    edgeX = event.clientX < rect.left + edge ? -1 : event.clientX > rect.right - edge ? 1 : 0;
    edgeY = event.clientY < rect.top + edge ? -1 : event.clientY > rect.bottom - edge ? 1 : 0;

    if (!dragging) return;
    const deltaX = event.clientX - startX;
    const deltaY = event.clientY - startY;
    if (Math.abs(deltaX) + Math.abs(deltaY) > 4) dragged = true;
    viewport.scrollLeft = startLeft - deltaX;
    viewport.scrollTop = startTop - deltaY;
  };

  const stopDragging = event => {
    if (!dragging) return;
    dragging = false;
    viewport.classList.remove('dragging');
    if (event?.pointerId !== undefined) viewport.releasePointerCapture?.(event.pointerId);
    updateCoordinates();
  };

  const onMouseLeave = () => {
    if (!dragging) {
      edgeX = 0;
      edgeY = 0;
    }
  };

  const onKeyDown = event => {
    const speeds = { ArrowLeft: [-140, 0], ArrowRight: [140, 0], ArrowUp: [0, -140], ArrowDown: [0, 140], a: [-140, 0], d: [140, 0], w: [0, -140], s: [0, 140] };
    const move = speeds[event.key];
    if (!move) return;
    viewport.scrollBy({ left: move[0], top: move[1], behavior: 'smooth' });
    event.preventDefault();
  };

  const edgeLoop = () => {
    if (!dragging && (edgeX || edgeY)) viewport.scrollBy(edgeX * 16, edgeY * 16);
    globalThis.ReinosWorldMarches?.updateMapMarches?.(attackPlannerContext());
    animationFrame = requestAnimationFrame(edgeLoop);
  };

  viewport.addEventListener('pointerdown', onPointerDown);
  viewport.addEventListener('pointermove', onPointerMove);
  viewport.addEventListener('pointerup', stopDragging);
  viewport.addEventListener('pointercancel', stopDragging);
  viewport.addEventListener('mouseleave', onMouseLeave);
  viewport.addEventListener('scroll', updateCoordinates, { passive: true });
  viewport.addEventListener('keydown', onKeyDown);

  const initialFocus = pendingMapFocus || profile.worldView || profile.worldPosition;
  pendingMapFocus = null;
  requestAnimationFrame(() => {
    centerWorldMap(viewport, initialFocus.x, initialFocus.y, false);
    updateCoordinates();
    viewport.focus({ preventScroll: true });
  });
  animationFrame = requestAnimationFrame(edgeLoop);

  worldMapCleanup = () => {
    cancelAnimationFrame(animationFrame);
    clearTimeout(saveTimer);
    viewport.removeEventListener('pointerdown', onPointerDown);
    viewport.removeEventListener('pointermove', onPointerMove);
    viewport.removeEventListener('pointerup', stopDragging);
    viewport.removeEventListener('pointercancel', stopDragging);
    viewport.removeEventListener('mouseleave', onMouseLeave);
    viewport.removeEventListener('scroll', updateCoordinates);
    viewport.removeEventListener('keydown', onKeyDown);
    zoomController?.destroy();
  };
}

function renderMap() {
  migrateFalkenTargets(profile);
  const nodes = profile.targets.map(target => {
    const currentTime = now();
    const cooling = target.cooldownUntil > currentTime;
    const progression = FALKEN_PROGRESSION?.progress(target);
    const level = progression?.level || target.level;
    const remaining = progression?.maxed ? 0 : (progression?.remaining || 1);
    const progressLabel = progression?.maxed
      ? 'Nível máximo'
      : `${remaining} ${remaining === 1 ? 'vitória' : 'vitórias'} até ao nível ${level + 1}`;
    const tooltip = globalThis.ReinosFalkenMapHover?.tooltipHtml?.(target, {
      escapeHtml,
      formatTime: fmtTime,
      now: currentTime
    }) || '';
    const accessibleLabel = `${target.name}, nível ${level}, ${progressLabel}, coordenadas X ${target.x}, Y ${target.y}`;
    return `<div class="map-node enemy" style="left:${worldPixelX(target.x)}px;top:${worldPixelY(target.y)}px"><button class="falken-node-btn" data-action="falken-open" data-id="${target.id}" aria-label="${escapeHtml(accessibleLabel)}"><img src="../assets/barao_falken.png" alt="" draggable="false" />${cooling ? '<b class="map-cooldown-icon">⏳</b>' : ''}</button>${tooltip}</div>`;
  }).join('');

  const otherPlayers = Object.entries(db.profiles || {}).filter(([userId]) => userId !== currentUser.id).map(([userId, otherProfile]) => {
    const owner = db.users.find(user => user.id === userId);
    if (!otherProfile.worldPosition) otherProfile.worldPosition = worldPositionForSeed(userId);
    const position = {
      x: normalizeWorldCoordinate(otherProfile.worldPosition.x),
      y: normalizeWorldCoordinate(otherProfile.worldPosition.y)
    };
    const otherKingdomName = String(otherProfile?.kingdomName || '').trim();
    const fallbackPlayerName = globalThis.ReinosOwnerPrivacy?.publicPlayerName?.(owner?.username) || owner?.username || 'Jogador';
    const visibleCastleName = otherKingdomName || fallbackPlayerName;
    return globalThis.ReinosPlayerCastleMap?.renderNode?.({
      userId,
      username: visibleCastleName,
      x: position.x,
      y: position.y,
      own: false
    }, { escapeHtml, worldPixelX, worldPixelY }) || `<div class="map-node player player-other" style="left:${worldPixelX(position.x)}px;top:${worldPixelY(position.y)}px"><button class="player-castle-btn" data-action="player-open" data-user-id="${escapeHtml(userId)}" title="Espiar ou atacar ${escapeHtml(visibleCastleName)}">🏰</button><span>Castelo ${escapeHtml(visibleCastleName)}<small>X ${position.x} • Y ${position.y}</small></span></div>`;
  }).join('');

  const ownPosition = profile.worldPosition || worldPositionForSeed(currentUser.id);
  profile.worldPosition = {
    x: normalizeWorldCoordinate(ownPosition.x),
    y: normalizeWorldCoordinate(ownPosition.y)
  };

  const marches = globalThis.ReinosWorldMarches?.renderMapMarches?.(attackPlannerContext()) || '';
  const incomingMarches = globalThis.ReinosIncomingAttackAlert?.renderMap?.(attackPlannerContext()) || '';
  const outpostNodes = globalThis.ReinosOutposts?.renderMapNodes?.(outpostSystemContext()) || '';
  const mapBackground = globalThis.ReinosWorldMapBackground?.render?.() || '';

  const ownCastleName = String(profile.kingdomName || '').trim() || currentUser.username || 'Jogador';
  const ownNode = globalThis.ReinosPlayerCastleMap?.renderNode?.({
    userId: currentUser.id,
    username: ownCastleName,
    x: profile.worldPosition.x,
    y: profile.worldPosition.y,
    own: true
  }, { escapeHtml, worldPixelX, worldPixelY }) || `<div class="map-node player player-own" style="left:${worldPixelX(profile.worldPosition.x)}px;top:${worldPixelY(profile.worldPosition.y)}px"><button class="player-castle-btn" data-action="map-home" title="O teu castelo">🏰</button><span>Castelo ${escapeHtml(ownCastleName)}<small>X ${profile.worldPosition.x} • Y ${profile.worldPosition.y}</small></span></div>`;

  $('#content').innerHTML = `<div class="view map-view">${viewHeader('Mapa Mundial', 'Percorre o reino arrastando o mapa, usa a roda do rato para aproximar ou afastar e consulta as coordenadas de cada castelo.')}<div class="world-toolbar"><div class="coordinate-search"><strong>Ir para coordenadas</strong><label>X <input id="map-x-input" type="number" min="${WORLD_COORD_MIN}" max="${WORLD_COORD_MAX}" value="${profile.worldView?.x ?? profile.worldPosition.x}" /></label><label>Y <input id="map-y-input" type="number" min="${WORLD_COORD_MIN}" max="${WORLD_COORD_MAX}" value="${profile.worldView?.y ?? profile.worldPosition.y}" /></label><button class="action-btn" data-action="map-go">Ir</button></div><button class="secondary-btn" data-action="map-home">🏰 Meu castelo</button></div><div class="world-map-shell"><div class="world-map" id="world-map" tabindex="0"><div class="world-map-stage" id="world-map-stage"><div class="world-space" id="world-space">${mapBackground}${incomingMarches}${ownNode}${otherPlayers}${outpostNodes}${nodes}${marches}</div></div></div><div class="world-zoom-readout" id="world-zoom-readout"></div><div class="world-map-hint">Arrasta para mover • Roda do rato para zoom • Setas ou WASD</div></div></div>`;
  setupWorldMap();
}

function renderAlliance() {
  if (!profile.alliance) {
    $('#content').innerHTML = `<div class="view">${viewHeader('Aliança', 'Cria uma aliança e prepara a base para guerras, pactos e cooperação.')}<div class="game-card" style="max-width:650px;margin:60px auto"><div class="game-card-head"><div class="game-card-icon">🛡️</div><div><h3>Ainda não tens aliança</h3><small>O modo online será ligado a um servidor numa fase seguinte.</small></div></div><div class="game-card-body"><p>Nesta versão local já podes criar a estrutura da tua aliança, doar recursos e usar o chat. Quando o servidor multiplayer for adicionado, os membros serão jogadores reais.</p><button class="action-btn" data-action="alliance-create-open">Criar aliança</button></div></div></div>`;
    return;
  }
  const messages = profile.chat.length ? profile.chat.map(m => `<div class="chat-msg"><b>${escapeHtml(m.user)}</b><small>${new Date(m.at).toLocaleTimeString('pt-PT',{hour:'2-digit',minute:'2-digit'})}</small><div>${escapeHtml(m.text)}</div></div>`).join('') : '<div class="empty-state">Sem mensagens.</div>';
  $('#content').innerHTML = `<div class="view">${viewHeader(`Aliança: ${escapeHtml(profile.alliance.name)}`, `Nível ${profile.alliance.level} • Tesouro ${fmt(profile.alliance.treasury)} • Fundador: ${escapeHtml(currentUser.username)}`)}<div class="alliance-layout"><div class="panel"><h3>💬 Chat da aliança</h3><div class="panel-body"><div class="chat-box">${messages}</div><div class="chat-input"><input id="chat-text" maxlength="160" placeholder="Escreve uma mensagem..."/><button class="action-btn" data-action="chat-send">Enviar</button></div></div></div><div><div class="panel"><h3>🛡️ Membros</h3><div class="panel-body"><div class="mini-item"><strong>${escapeHtml(globalThis.ReinosOwnerPrivacy?.publicPlayerName?.(currentUser.username) || currentUser.username)}</strong><small>Fundador • Poder ${fmt(calculatePower())}</small></div></div></div><div class="panel"><h3>🏦 Doações</h3><div class="panel-body"><p>Doa moedas para subir o nível da aliança.</p><div class="field"><input id="donation-amount" type="number" min="100" value="500" /></div><button class="action-btn" data-action="alliance-donate">Doar moedas</button></div></div></div></div></div>`;
}

function reportPresentationForList(report) {
  const espionageContext = falkenEspionageContext();
  const battleContext = attackPlannerContext();
  const defenseBattlePresentation = globalThis.ReinosDefenderBattleReports?.reportPresentation?.(report, battleContext);
  const battlePresentation = globalThis.RealmBattleReports?.reportPresentation?.(report, battleContext);
  const espionagePresentation = globalThis.RealmFalkenEspionage?.reportPresentation?.(report, espionageContext);
  const playerPresentation = globalThis.ReinosPlayerInteractions?.reportPresentation?.(report, battleContext);
  return defenseBattlePresentation || battlePresentation || espionagePresentation || playerPresentation || null;
}

function reportsFolderContext() {
  return {
    getProfile: () => profile,
    viewHeader,
    escapeHtml,
    format: fmt,
    resourceIcons: RESOURCE_ICONS,
    reportPresentation: reportPresentationForList
  };
}

function renderReports() {
  if (globalThis.ReinosReportFolders?.render) {
    $('#content').innerHTML = globalThis.ReinosReportFolders.render(reportsFolderContext());
    return;
  }

  const rows = [...profile.reports].reverse().map(report => {
    const presentation = reportPresentationForList(report);
    const resultLabel = presentation?.label || (report.result === 'win' ? 'Vitória' : report.result === 'loss' ? 'Derrota' : report.result === 'spy' ? 'Espionagem' : 'Informação');
    const resultClass = presentation?.className || (report.result === 'win' ? 'report-win' : report.result === 'loss' ? 'report-loss' : report.result === 'spy' ? 'report-spy' : '');
    const detailsHtml = presentation?.detailsHtml || (RESOURCE_ICONS?.richText ? RESOURCE_ICONS.richText(report.text, escapeHtml) : escapeHtml(report.text));
    return `<tr><td>${new Date(report.at).toLocaleString('pt-PT')}</td><td>${escapeHtml(report.title)}</td><td class="${resultClass}">${resultLabel}</td><td>${detailsHtml}</td></tr>`;
  }).join('');
  $('#content').innerHTML = `<div class="view">${viewHeader('Relatórios', 'Histórico de batalhas, construções, espionagens e acontecimentos do reino.')}<table class="table"><thead><tr><th>Data</th><th>Assunto</th><th>Resultado</th><th>Detalhes</th></tr></thead><tbody>${rows || '<tr><td colspan="4">Sem relatórios.</td></tr>'}</tbody></table></div>`;
}

function questDefinitions() {
  return [
    { id: 'q_build5', name: 'Mestre construtor', desc: 'Possui pelo menos 10 edifícios.', current: profile.buildings.length, target: 10, reward: { wood: 1200, stone: 1200, coins: 500 } },
    { id: 'q_army100', name: 'Exército em crescimento', desc: 'Reúne 100 unidades militares.', current: Object.values(profile.troops).reduce((a,b)=>a+b,0), target: 100, reward: { food: 1800, coins: 700 } },
    { id: 'q_win3', name: 'Conquistador', desc: 'Vence 3 batalhas.', current: profile.totalVictories, target: 3, reward: { gems: 25, coins: 1500 } },
    { id: 'q_keep3', name: 'Fortaleza imponente', desc: 'Melhora a fortaleza para nível 3.', current: getKeepLevel(), target: 3, reward: { wood: 2500, stone: 2500, gems: 20 } }
  ];
}

function renderQuests() {
  const cards = questDefinitions().map(q => {
    const claimed = profile.questsClaimed.includes(q.id);
    const complete = q.current >= q.target;
    return `<article class="game-card"><div class="game-card-head"><div class="game-card-icon">⭐</div><div><h3>${q.name}</h3><small>${q.desc}</small></div></div><div class="game-card-body"><div class="stats-line"><span>Progresso</span><strong>${Math.min(q.current,q.target)}/${q.target}</strong></div><div class="progress"><i style="width:${clamp(q.current/q.target*100,0,100)}%"></i></div><div class="costs" style="margin-top:10px">${costsHtml(q.reward)}</div><div class="btn-row"><button class="action-btn" data-action="quest-claim" data-id="${q.id}" ${!complete || claimed ? 'disabled' : ''}>${claimed ? 'Recolhida' : complete ? 'Recolher' : 'Em progresso'}</button></div></div></article>`;
  }).join('');
  $('#content').innerHTML = `<div class="view">${viewHeader('Missões', 'Completa objetivos para receber recursos e coroas.')}<div class="card-grid">${cards}</div></div>`;
}

function renderRanking() {
  const competitors = Array.isArray(rankingCache) && rankingCache.length
    ? rankingCache
    : (globalThis.ReinosRealRanking?.entries(db, currentUser?.id, TROOPS) || []);
  const rows = competitors.length
    ? competitors.map((playerEntry, index) => `<tr${playerEntry.you ? ' class="ranking-current-player"' : ''}><td>${index + 1}</td><td>${escapeHtml(globalThis.ReinosOwnerPrivacy?.publicPlayerName?.(playerEntry.name || playerEntry.kingdomName) || playerEntry.name || playerEntry.kingdomName || 'Jogador')}</td><td>${fmt(playerEntry.power)}</td><td>${fmt(playerEntry.honor)}</td></tr>`).join('')
    : '<tr><td colspan="4">Ainda não existem jogadores registados.</td></tr>';
  $('#content').innerHTML = `<div class="view">${viewHeader('Classificação', 'Apenas jogadores com conta criada neste mundo.') }<table class="table"><thead><tr><th>Posição</th><th>Jogador</th><th>Poder</th><th>Honra</th></tr></thead><tbody>${rows}</tbody></table></div>`;
}

function getFirstEmptySlot(type = 'decoration', ignoreId = null) {
  if (globalThis.ReinosCastleSystem?.firstEmptySlot) {
    return globalThis.ReinosCastleSystem.firstEmptySlot(profile, type, ignoreId);
  }
  const used = new Set(profile.buildings.map(b => b.slot));
  for (let i = 0; i < 30; i++) if (!used.has(i)) return i;
  return -1;
}

function showBuildModal(slot) {
  if (globalThis.ReinosCastleSystem?.openBuildModal?.(slot, castleSystemContext())) return;
  if (profile.constructionQueue.length >= constructionQueueLimit()) return toast('A fila de construção está ocupada. Compra uma fila extra temporária na barra lateral.', 'error');
  const keepLevel = getKeepLevel();
  const choices = Object.entries(BUILDINGS).filter(([type, def]) => {
    if (type === 'keep') return false;
    if (def.max && buildingCount(type) >= def.max) return false;
    return true;
  }).map(([type, def]) => {
    const locked = (def.keepRequired || 1) > keepLevel;
    const cost = buildingCost(type, 1);
    return `<div class="choice"><div class="choice-title">${def.icon} ${def.name}</div><small>${def.description}</small><div class="costs">${costsHtml(cost)}</div><div class="stats-line"><span>Tempo</span><strong>${fmtTime(buildingTime(type,1))}</strong></div><button class="action-btn" data-action="build-confirm" data-type="${type}" data-slot="${slot}" ${locked ? 'disabled' : ''}>${locked ? `Fortaleza Nv. ${def.keepRequired}` : 'Construir'}</button></div>`;
  }).join('');
  openModal('Escolher edifício', `<div class="choice-grid">${choices}</div>`);
}

function showBuildingModal(buildingId) {
  const building = profile.buildings.find(b => b.id === buildingId);
  if (!building) return;
  const def = BUILDINGS[building.type];
  const queue = profile.constructionQueue.find(q => q.buildingId === building.id);
  if (queue) {
    const moveButton = globalThis.ReinosCastleSystem?.buildingModalMoveButton?.(building) || '';
    const queueLabel = queue.action === 'upgrade' ? 'Este edifício está a melhorar.' : 'Este edifício está em construção.';
    openModal(`${def.icon} ${def.name}`, `<p>${queueLabel}</p><div class="stats-line"><span>Conclusão</span><strong data-countdown="${queue.endAt}">${fmtTime(queue.endAt-now())}</strong></div><p class="muted">Podes mover o edifício sem cancelar a obra. O tempo continua a contar normalmente.</p>`, `${moveButton}<button class="secondary-btn" data-action="modal-close">Fechar</button><button class="action-btn" data-action="rush-construction" data-id="${queue.id}">Concluir por ${rushCost(queue.endAt)} ${RESOURCE_META.gems.icon}</button>`);
    return;
  }
  const target = building.level + 1;
  const cost = buildingCost(building.type, target);
  const maxLevel = Number(def.maxLevel) || 0;
  const maxed = maxLevel > 0 && building.level >= maxLevel;
  const comparison = globalThis.ReinosBuildingUpgradeBenefits?.comparisonHtml?.(building, {
    profile,
    definitions: BUILDINGS,
    resourceMeta: RESOURCE_META,
    format: fmt,
    formatTime: fmtTime,
    escapeHtml
  }) || '';
  const moveButton = globalThis.ReinosCastleSystem?.buildingModalMoveButton?.(building) || '';
  const marketPanel = globalThis.ReinosMarketSystem?.buildingPanel?.(marketSystemContext(), building) || '';
  const order = maxed ? '' : `<div class="building-upgrade-order"><div><span>Tempo da melhoria</span><strong>${fmtTime(buildingTime(building.type,target))}</strong></div><div><span>Custo da melhoria</span><div class="costs">${costsHtml(cost)}</div></div></div>`;
  openModal(`${def.icon} ${def.name} — Nível ${building.level}`, `<p>${def.description}</p>${marketPanel}${comparison}${order}`, `<button class="danger-btn" data-action="building-demolish" data-id="${building.id}" ${building.type === 'keep' ? 'disabled' : ''}>Demolir</button>${moveButton}<button class="secondary-btn" data-action="modal-close">Cancelar</button><button class="action-btn" data-action="building-upgrade" data-id="${building.id}" ${maxed ? 'disabled' : ''}>${maxed ? 'Nível máximo' : 'Melhorar'}</button>`);
  document.querySelector('.modal')?.classList.add('building-upgrade-modal');
}

function showRecruitModal(type) {
  const troop = TROOPS[type];
  openModal(`Recrutar ${troop.icon} ${troop.name}`, `<p>Define a quantidade. O custo é pago imediatamente e as tropas ficam disponíveis quando o treino terminar.</p><div class="field"><label>Quantidade</label><input id="recruit-count" type="number" min="1" max="500" value="10" /></div><div class="costs">Custo por unidade: ${costsHtml(troop.cost)}</div>`, `<button class="secondary-btn" data-action="modal-close">Cancelar</button><button class="action-btn" data-action="recruit-confirm" data-type="${type}">Iniciar treino</button>`);
}

function showAttackModal(targetId) {
  if (globalThis.RealmAttackPlanner?.open) {
    globalThis.RealmAttackPlanner.open(targetId, attackPlannerContext());
    return;
  }
  const target = profile.targets.find(t => t.id === targetId);
  if (!target) return;
  if (target.cooldownUntil > now()) return toast('Esta fortaleza ainda está protegida após o último ataque.', 'error');
  const rows = Object.entries(TROOPS).map(([type,t]) => `<div class="number-row"><span>${t.icon} ${t.name} <small>(disponíveis: ${fmt(profile.troops[type])})</small></span><input class="attack-count" data-type="${type}" type="number" min="0" max="${profile.troops[type]}" value="0" /></div>`).join('');
  openModal(`Atacar ${escapeHtml(target.name)}`, `<div class="stats-line"><span>Nível inimigo</span><strong>${target.level}</strong></div><div class="stats-line"><span>Progresso do nível</span><strong>${escapeHtml(FALKEN_PROGRESSION?.progress(target)?.maxed ? 'Máximo' : `${FALKEN_PROGRESSION?.progress(target)?.current || 0}/${FALKEN_PROGRESSION?.progress(target)?.needed || 1} vitórias`)}</strong></div><div class="stats-line"><span>Força estimada</span><strong>${fmt(target.strength)}</strong></div><div class="stats-line"><span>Saque máximo</span><strong>${resourceLine(target.loot)}</strong></div><h4>Selecionar tropas</h4>${rows}`, `<button class="secondary-btn" data-action="modal-close">Cancelar</button><button class="action-btn" data-action="attack-confirm" data-id="${target.id}">Enviar ataque</button>`);
}

function rushCost(endAt) {
  return Math.max(1, Math.ceil((endAt - now()) / 10000));
}

function startBuilding(type, slot) {
  if (profile.constructionQueue.length >= constructionQueueLimit()) return toast('A fila de construção está ocupada. Compra uma fila extra temporária na barra lateral.', 'error');
  const def = BUILDINGS[type];
  if (!def) return;
  const targetSlot = Math.max(0, Math.floor(Number(slot)));
  if (!Number.isFinite(targetSlot)) return toast('Escolhe primeiro uma posição válida para o edifício.', 'error');
  if (globalThis.ReinosCastleSystem?.canPlace && !globalThis.ReinosCastleSystem.canPlace(profile, type, targetSlot)) {
    return toast('O edifício não cabe nessa posição ou a parcela ainda está bloqueada.', 'error');
  }
  if ((def.keepRequired || 1) > getKeepLevel()) return toast(`Necessitas da Fortaleza no nível ${def.keepRequired}.`, 'error');
  if (def.max && buildingCount(type) >= def.max) return toast('Atingiste o limite deste edifício.', 'error');
  const cost = buildingCost(type, 1);
  if (!canAfford(cost)) return toast('Não tens recursos suficientes.', 'error');
  spend(cost);
  const building = { id: uid('b'), type, level: 0, slot: targetSlot, status: 'constructing' };
  profile.buildings.push(building);
  const startedAt = now();
  profile.constructionQueue.push({ id: uid('cq'), buildingId: building.id, action: 'build', targetLevel: 1, startedAt, endAt: startedAt + buildingTime(type,1) });
  profile.reports.push({ id: uid('r'), at: startedAt, title: `Construção iniciada: ${def.name}`, result: 'info', text: `O edifício ficará pronto em ${fmtTime(buildingTime(type,1))}.` });
  closeModal();
  renderGame();
  persist(true);
}

function upgradeBuilding(buildingId) {
  if (profile.constructionQueue.length >= constructionQueueLimit()) return toast('A fila de construção está ocupada. Compra uma fila extra temporária na barra lateral.', 'error');
  const building = profile.buildings.find(b => b.id === buildingId);
  if (!building) return;
  const targetLevel = building.level + 1;
  const buildingDefinition = BUILDINGS[building.type];
  if (buildingDefinition?.maxLevel && targetLevel > buildingDefinition.maxLevel) return toast(`${buildingDefinition.name} já atingiu o nível máximo.`, 'error');
  if (building.type !== 'keep' && targetLevel > getKeepLevel() + 2) return toast('Melhora primeiro a Fortaleza.', 'error');
  const cost = buildingCost(building.type, targetLevel);
  if (!canAfford(cost)) return toast('Não tens recursos suficientes.', 'error');
  spend(cost);
  building.status = 'upgrading';
  const startedAt = now();
  profile.constructionQueue.push({ id: uid('cq'), buildingId, action: 'upgrade', targetLevel, startedAt, endAt: startedAt + buildingTime(building.type,targetLevel) });
  closeModal();
  renderGame();
  persist(true);
}

function demolishBuilding(buildingId) {
  const building = profile.buildings.find(b => b.id === buildingId);
  if (!building || building.type === 'keep') return;
  const refund = Object.fromEntries(Object.entries(buildingCost(building.type, building.level)).map(([k,v]) => [k, Math.round(v*0.35)]));
  addResources(refund);
  profile.buildings = profile.buildings.filter(b => b.id !== buildingId);
  profile.reports.push({ id: uid('r'), at: now(), title: `Edifício demolido: ${BUILDINGS[building.type].name}`, result: 'info', text: `Foram recuperados 35% dos materiais.` });
  closeModal();
  renderGame();
  persist(true);
}

function recruitTroops(type, count, options = {}) {
  count = Math.floor(Number(count));
  const troop = TROOPS[type];
  if (!troop || count < 1 || count > 500) { toast('Indica uma quantidade entre 1 e 500.', 'error'); return false; }
  if (!hasBuilding('barracks')) { toast('Precisas de um Quartel.', 'error'); return false; }
  if (troop.buildingRequired && !hasBuilding(troop.buildingRequired)) { toast('Ainda não desbloqueaste esta unidade.', 'error'); return false; }
  const cost = Object.fromEntries(Object.entries(troop.cost).map(([k,v]) => [k,v*count]));
  if (!canAfford(cost)) { toast('Não tens recursos suficientes.', 'error'); return false; }
  spend(cost);
  const startAt = Math.max(now(), ...profile.recruitQueue.map(q=>q.endAt), 0);
  const barracksLevel = Math.max(1, Number(profile.buildings.find(building => building.type === 'barracks')?.level) || 1);
  const baseTrainingMs = troop.seconds * count * 1000;
  const trainingMs = globalThis.ReinosBuildingUpgradeBenefits?.barracksTrainingTimeMs?.(baseTrainingMs, barracksLevel) || baseTrainingMs;
  profile.recruitQueue.push({ id: uid('rq'), type, count, startedAt: startAt, endAt: startAt + trainingMs });
  if (options.keepOpen) {
    updateHud();
    renderRightbar();
    if (activeView === 'army') renderActiveView();
  } else {
    closeModal();
    renderGame();
  }
  persist(true);
  return true;
}

function sendAttack(targetId) {
  const target = profile.targets.find(t => t.id === targetId);
  if (!target || target.cooldownUntil > now()) return;
  const sent = {};
  let total = 0;
  $$('.attack-count').forEach(input => {
    const type = input.dataset.type;
    const count = clamp(Math.floor(Number(input.value)||0),0,profile.troops[type]);
    sent[type] = count;
    total += count;
  });
  if (total < 1) return toast('Seleciona pelo menos uma unidade.', 'error');
  for (const [type,count] of Object.entries(sent)) profile.troops[type] -= count;
  const route = globalThis.ReinosMarchTravelTime?.route?.(profile.worldPosition, { x: target.x, y: target.y });
  const fallbackTravelMs = Math.max(1000, Math.round(Math.hypot((Number(target.x) || 0) - (Number(profile.worldPosition?.x) || 0), (Number(target.y) || 0) - (Number(profile.worldPosition?.y) || 0)) * 6000));
  const travelMs = route?.travelMs ?? fallbackTravelMs;
  const startedAt = now();
  const commanderData = globalThis.ReinosEquipmentSystem?.missionCommander?.(profile) || {};
  const mission = globalThis.ReinosWorldMarches?.createMission?.(attackPlannerContext(), target, sent, commanderData) || { id: uid('m'), targetId, targetName: target.name, troops: sent, state: 'outgoing', targetPosition: { x: target.x, y: target.y }, originPosition: { ...profile.worldPosition }, travelMs, startedAt, launchStartedAt: startedAt, cancelWindowEndsAt: startedAt + (globalThis.ReinosAttackCancellation?.CANCEL_WINDOW_MS || 10 * 60 * 1000), endAt: startedAt + travelMs, ...commanderData };
  profile.missions.push(mission);
  closeModal();
  renderGame();
  toast(`O exército partiu para atacar ${target.name}.`, 'success');
  persist(true);
}

function resolveBattle(mission) {
  if (mission?.attackPlan && globalThis.RealmAttackPlanner?.resolveBattle) {
    const handled = globalThis.RealmAttackPlanner.resolveBattle(mission, attackPlannerContext());
    if (handled) return;
  }
  const target = profile.targets.find(t => t.id === mission.targetId);
  if (!target) return;
  const baseAttack = Object.entries(mission.troops).reduce((sum,[type,count]) => sum + TROOPS[type].attack*count,0);
  const attack = (globalThis.ReinosEquipmentSystem?.applyCommanderAttackPower?.(profile, mission, baseAttack) || baseAttack) * (0.90 + Math.random()*0.22);
  const enemy = target.strength * (0.92 + Math.random()*0.18);
  const win = attack >= enemy;
  const ratio = attack / Math.max(enemy,1);
  const lossRate = win ? clamp(0.30 / Math.max(ratio,0.7),0.08,0.45) : clamp(0.68 + (1-ratio)*0.18,0.55,0.92);
  const survivors = {};
  let lostTotal = 0;
  let carry = 0;
  for (const [type,count] of Object.entries(mission.troops)) {
    const lost = Math.min(count, Math.round(count * lossRate * (0.85+Math.random()*0.25)));
    const survived = count-lost;
    survivors[type] = survived;
    lostTotal += lost;
    carry += survived*TROOPS[type].carry;
    if (!globalThis.ReinosWorldMarches?.finishBattleMission) profile.troops[type] += survived;
  }
  profile.totalBattles += 1;
  if (win) {
    profile.totalVictories += 1;
    const maxLoot = target.loot;
    const lootFactor = carry > 0 ? clamp(carry / 1200,0.18,1) : 0;
    const lootBonus = globalThis.ReinosEquipmentSystem?.lootMultiplier?.(profile, mission) || 1;
    const loot = Object.fromEntries(Object.entries(maxLoot).map(([k,v])=>[k,Math.round(v*lootFactor*lootBonus)]));
    if (!globalThis.ReinosWorldMarches?.finishBattleMission) addResources(loot);
    const defeatedLevel = target.level;
    const honor = 20 + defeatedLevel*12;
    profile.honor += honor;
    gainXp(90 + defeatedLevel*45);
    const progressionResult = FALKEN_PROGRESSION?.registerVictory(target, { strengthFn: falkenStrength, lootFn: falkenLoot }) || { level: target.level, current: 0, needed: 1, leveledUp: false, maxed: false };
    const progressionMessage = FALKEN_PROGRESSION?.victoryMessage(target, progressionResult) || `Progresso do Barão atualizado.`;
    const equipmentDrop = globalThis.ReinosEquipmentSystem?.awardBaronDrop?.(attackPlannerContext(), target, { defeatedLevel, win: true, mission, suppressReport: true }) || null;
    target.cooldownUntil = now()+45000;
    const equipmentText = equipmentDrop ? ` Equipamento encontrado: ${equipmentDrop.name}.` : '';
    profile.reports.push({ id: uid('r'), at: now(), title: `Batalha em ${target.name}`, result: 'win', text: `Vitória contra o Barão Falken de nível ${defeatedLevel}! Perdeste ${lostTotal} unidades e saqueaste Madeira ${fmt(loot.wood)}, Pedra ${fmt(loot.stone)}, Comida ${fmt(loot.food)} e Moedas ${fmt(loot.coins)}. +${honor} honra. ${progressionMessage}${equipmentText}` });
    toast(`Vitória! ${progressionMessage}`, 'success');
    if (globalThis.ReinosWorldMarches?.finishBattleMission) globalThis.ReinosWorldMarches.finishBattleMission(attackPlannerContext(), mission, survivors, loot, true);
  } else {
    profile.honor = Math.max(0, profile.honor - 8);
    gainXp(25 + target.level*10);
    target.cooldownUntil = now()+25000;
    profile.reports.push({ id: uid('r'), at: now(), title: `Batalha em ${target.name}`, result: 'loss', text: `Derrota. Perdeste ${lostTotal} unidades. As tropas sobreviventes regressaram ao castelo.` });
    toast(`O ataque a ${target.name} foi derrotado.`, 'error');
    if (globalThis.ReinosWorldMarches?.finishBattleMission) globalThis.ReinosWorldMarches.finishBattleMission(attackPlannerContext(), mission, survivors, { wood: 0, stone: 0, food: 0, coins: 0 }, false);
  }
}

function processTick() {
  if (!profile) return;
  const time = now();
  const elapsedSec = Math.max(0, (time - (profile.lastTick || time)) / 1000);
  const rates = resourceRates();
  const cap = warehouseCapacity();
  for (const key of ['wood','stone','food','coins']) {
    const current = Math.max(0, Number(profile.resources[key]) || 0);
    const delta = (Number(rates[key]) || 0) * elapsedSec / 3600;
    const next = current + delta;
    // Ofertas do Admin podem deixar recursos acima da capacidade.
    // A produção normal não aumenta acima do armazém, mas também não reduz o excesso oferecido.
    if (current > cap && delta >= 0) profile.resources[key] = current;
    else if (current > cap && delta < 0) profile.resources[key] = Math.max(0, next);
    else profile.resources[key] = clamp(next, 0, cap);
  }
  profile.lastTick = time;

  const finishedBuilds = profile.constructionQueue.filter(q => q.endAt <= time);
  for (const item of finishedBuilds) {
    const building = profile.buildings.find(b => b.id === item.buildingId);
    if (!building) continue;
    building.level = item.targetLevel;
    building.status = 'ready';
    const name = BUILDINGS[building.type].name;
    profile.reports.push({ id: uid('r'), at: time, title: `${name} concluído`, result: 'info', text: `${name} encontra-se agora no nível ${building.level}.` });
    gainXp(item.action === 'build' ? 55 : 35*building.level);
    toast(`${name} está pronto!`, 'success');
  }
  if (finishedBuilds.length) profile.constructionQueue = profile.constructionQueue.filter(q => q.endAt > time);

  const finishedRecruit = profile.recruitQueue.filter(q=>q.endAt<=time);
  for (const item of finishedRecruit) {
    profile.troops[item.type] += item.count;
    profile.reports.push({ id: uid('r'), at: time, title: 'Recrutamento concluído', result:'info', text:`${item.count} ${TROOPS[item.type].name}(s) juntaram-se ao exército.` });
    toast(`${item.count} ${TROOPS[item.type].name}(s) prontos.`, 'success');
  }
  if (finishedRecruit.length) profile.recruitQueue = profile.recruitQueue.filter(q=>q.endAt>time);

  const workshopChanged = globalThis.ReinosWorkshopSystem?.process?.(profile, time, { uid, toast }) || false;
  const outpostChanged = globalThis.ReinosOutposts?.process?.(outpostSystemContext(), time) || false;

  let finishedMissions = [];
  let missionsChanged = false;
  if (globalThis.ReinosWorldMarches?.processMissions) {
    missionsChanged = globalThis.ReinosWorldMarches.processMissions(attackPlannerContext(), resolveBattle);
  } else {
    finishedMissions = profile.missions.filter(m=>m.endAt<=time);
    finishedMissions.forEach(resolveBattle);
    if (finishedMissions.length) profile.missions = profile.missions.filter(m=>m.endAt>time);
    missionsChanged = finishedMissions.length > 0;
  }

  const currentFoodRates = resourceRates();
  const currentFoodUpkeep = globalThis.ReinosTroopUpkeep?.hourlyConsumption?.(profile) || 0;
  const grossFoodPerHour = Math.max(0, (Number(currentFoodRates.food) || 0) + currentFoodUpkeep);
  const desertionResult = globalThis.ReinosFoodDesertion?.process?.({
    profile,
    grossFoodPerHour,
    currentTime: time,
    troopDefinitions: TROOPS,
    uid,
    format: fmt,
    toast
  }) || { changed: false };

  updateHud();
  $$('[data-countdown]').forEach(el => {
    const endAt = Number(el.dataset.countdown);
    el.textContent = fmtTime(endAt-time);
  });
  globalThis.ReinosAttackCancellation?.updateUi?.(attackPlannerContext());
  globalThis.ReinosIncomingAttackAlert?.tick?.(attackPlannerContext());
  if (finishedBuilds.length || finishedRecruit.length || workshopChanged || outpostChanged || missionsChanged || desertionResult.changed) {
    renderActiveView();
    renderRightbar();
    globalThis.ReinosAdmin?.afterRender?.(adminSystemContext());
    globalThis.ReinosIncomingAttackAlert?.afterRender?.(attackPlannerContext());
    persist(true);
  } else {
    persist(false);
  }
}

function claimDaily() {
  const day = 24*60*60*1000;
  if (now()-profile.dailyClaimAt < day) {
    return toast(`A próxima recompensa estará disponível em ${fmtTime(day-(now()-profile.dailyClaimAt))}.`, 'error');
  }
  profile.dailyClaimAt = now();
  addResources({ wood: 1200, stone: 1200, food: 1500, coins: 800, gems: 15 });
  profile.reports.push({ id:uid('r'),at:now(),title:'Recompensa diária',result:'info',text:'Recebeste recursos, moedas e 15 coroas.' });
  updateHud();
  updateDailyRewardIndicators();
  persist(true);
  toast('Recompensa diária recolhida!', 'success');
}

function showSettings() {
  openModal('Definições', `
    <div class="field">
      <label>Nome do reino</label>
      <input id="kingdom-name-input" maxlength="30" value="${escapeHtml(profile.kingdomName)}" />
    </div>

    <div class="settings-password-box">
      <h4>Alterar palavra-passe</h4>
      <p><small>Preenche estes campos apenas se quiseres alterar a palavra-passe da conta.</small></p>
      <div class="field">
        <label>Palavra-passe atual</label>
        <input id="settings-current-password" type="password" autocomplete="current-password" placeholder="Palavra-passe atual" />
      </div>
      <div class="field">
        <label>Nova palavra-passe</label>
        <input id="settings-new-password" type="password" minlength="4" autocomplete="new-password" placeholder="Mínimo de 4 caracteres" />
      </div>
      <div class="field">
        <label>Confirmar nova palavra-passe</label>
        <input id="settings-new-password-confirm" type="password" minlength="4" autocomplete="new-password" placeholder="Repete a nova palavra-passe" />
      </div>
    </div>

    <p><small>Os dados são guardados localmente no perfil do Windows.</small></p>`,
    `<button class="danger-btn" data-action="profile-reset">Reiniciar progresso</button><button class="secondary-btn" data-action="modal-close">Cancelar</button><button class="action-btn" data-action="settings-save">Guardar</button>`
  );
}

async function handleAuthSubmit(event) {
  event.preventDefault();
  const username = $('#auth-username').value.trim();
  const password = $('#auth-password').value;
  if (username.length < 3 || password.length < 4) return renderAuth('O nome deve ter 3 caracteres e a palavra-passe pelo menos 4.');
  const normalized = username.toLocaleLowerCase('pt-PT');

  if (authMode === 'register') {
    const confirm = $('#auth-password2').value;
    if (password !== confirm) return renderAuth('As palavras-passe não coincidem.');
    const userId = uid('u');
    const newProfile = initialProfile(userId, username);
    try {
      if (APP_API.register) {
        const result = await APP_API.register(username, password, userId, newProfile);
        db = result.data || await APP_API.loadData();
        const user = db.users.find(u => u.id === (result.userId || userId)) || db.users.find(u => u.normalized === normalized);
        if (!user) throw new Error('Conta criada, mas não foi possível carregar o utilizador.');
        db.sessionUserId = user.id;
        currentUser = user;
        profile = db.profiles[user.id] || newProfile;
        ensureConstructionQueueExtras(profile);
        globalThis.ReinosAdminPermissions?.ensure?.(db);
        await APP_API.saveData(db);
        renderGame();
        toast('Conta criada. Bem-vindo ao teu novo reino!', 'success');
        return;
      }
    } catch (error) {
      return renderAuth(error?.message || 'Não foi possível criar conta no servidor.');
    }
    if (db.users.some(u => u.normalized === normalized)) return renderAuth('Já existe uma conta com esse nome.');
    const user = { id: userId, username, normalized, passwordHash: await sha256(password), createdAt: now() };
    db.users.push(user);
    db.profiles[user.id] = newProfile;
    db.sessionUserId = user.id;
    globalThis.ReinosAdminPermissions?.ensure?.(db);
    await APP_API.saveData(db);
    currentUser = user;
    profile = db.profiles[user.id];
    renderGame();
    toast('Conta criada. Bem-vindo ao teu novo reino!', 'success');
  } else {
    try {
      if (APP_API.login) {
        const result = await APP_API.login(username, password);
        db = result.data || await APP_API.loadData();
        const user = db.users.find(u => u.id === result.userId) || db.users.find(u => u.normalized === normalized);
        if (!user) throw new Error('Sessão iniciada, mas não foi possível carregar o utilizador.');
        currentUser = user;
        profile = db.profiles[user.id] || initialProfile(user.id,user.username);
        ensureConstructionQueueExtras(profile);
        migrateFalkenTargets(profile);
        globalThis.ReinosOutposts?.ensureState?.(profile, outpostSystemContext());
        profile.lastTick = profile.lastTick || now();
        db.sessionUserId = user.id;
        globalThis.ReinosAdminPermissions?.ensure?.(db);
        await APP_API.saveData(db);
        renderGame();
        return;
      }
    } catch (error) {
      return renderAuth(error?.message || 'Nome de jogador ou palavra-passe incorretos.');
    }
    const user = db.users.find(u => u.normalized === normalized);
    if (!user || user.passwordHash !== await sha256(password)) return renderAuth('Nome de jogador ou palavra-passe incorretos.');
    currentUser = user;
    profile = db.profiles[user.id] || initialProfile(user.id,user.username);
    ensureConstructionQueueExtras(profile);
    migrateFalkenTargets(profile);
    globalThis.ReinosOutposts?.ensureState?.(profile, outpostSystemContext());
    profile.lastTick = profile.lastTick || now();
    db.sessionUserId = user.id;
    globalThis.ReinosAdminPermissions?.ensure?.(db);
    await APP_API.saveData(db);
    renderGame();
  }
}

function isInvalidSessionError(error) {
  return /sess[ãa]o inv[áa]lida|faz login novamente|token inv[áa]lido/i.test(String(error?.message || error || ''));
}

async function init() {
  try {
    db = await APP_API.loadData();
  } catch (error) {
    if (isInvalidSessionError(error)) {
      await APP_API.clearSession?.();
      db = emptyData();
      authMode = 'login';
      renderAuth('Sessão antiga removida. Faz login novamente.');
      globalThis.realmBootReady?.();
      return;
    }
    throw error;
  }

  appVersion = await APP_API.getVersion();
  globalThis.ReinosAdminPermissions?.ensure?.(db);
  let databaseMigrated = false;
  Object.values(db.profiles || {}).forEach(savedProfile => {
    if (migrateFalkenTargets(savedProfile)) databaseMigrated = true;
    if (globalThis.ReinosEquipmentSystem?.ensureState?.(savedProfile)) databaseMigrated = true;
    if (globalThis.ReinosOutposts?.ensureState?.(savedProfile, outpostSystemContext())) databaseMigrated = true;
  });

  const remembered = db.users.find(u => u.id === db.sessionUserId);
  if (remembered && db.profiles[remembered.id]) {
    currentUser = remembered;
    profile = db.profiles[remembered.id];
    try {
      if (databaseMigrated) await persist(true);
    } catch (error) {
      if (isInvalidSessionError(error)) {
        await APP_API.clearSession?.();
        currentUser = null;
        profile = null;
        db.sessionUserId = null;
        authMode = 'login';
        renderAuth('Sessão expirada. Faz login novamente.');
        globalThis.realmBootReady?.();
        return;
      }
      throw error;
    }
    renderGame();
  } else {
    // Em cliente multiplayer, nunca tentar gravar migrações sem uma sessão válida.
    // Isso bloqueava o arranque com "Sessão inválida" quando havia um token antigo.
    db.sessionUserId = null;
    currentUser = null;
    profile = null;
    authMode = 'login';
    renderAuth();
  }
  globalThis.realmBootReady?.();
  if (!new URLSearchParams(location.search).has('static')) {
    setInterval(processTick, 1000);
    setInterval(backgroundRefreshFromServer, 2000);
  }
}

document.addEventListener('submit', event => {
  if (event.target.id === 'auth-form') handleAuthSubmit(event);
});

document.addEventListener('click', async event => {
  const actionEl = event.target.closest('[data-action]');
  if (!actionEl) return;
  const action = actionEl.dataset.action;

  if (action === 'auth-switch') { authMode = actionEl.dataset.mode; renderAuth(); return; }
  if (action === 'logout') { db.sessionUserId = null; await APP_API.clearSession?.(); authMode='login'; renderAuth(); return; }
  if (action === 'nav') {
    const targetView = actionEl.dataset.view;
    if (targetView === 'army') { openRecruitmentMenuFromArmyShortcut(); return; }
    if (targetView === 'ranking') await refreshRankingFromServer();
    activeView = targetView;
    renderGame();
    return;
  }
  if (action === 'modal-close') { closeModal(); return; }
  if (action === 'modal-close-backdrop' && event.target === actionEl) { closeModal(); return; }
  if (!profile) return;

  if (await globalThis.ReinosEquipmentSystem?.handleAction?.(action, actionEl, equipmentSystemContext())) return;
  if (await globalThis.ReinosIncomingAttackAlert?.handleAction?.(action, actionEl, attackPlannerContext())) return;
  if (await globalThis.ReinosMarketSystem?.handleAction?.(action, actionEl, marketSystemContext())) return;
  if (await globalThis.ReinosAttackCancellation?.handleAction?.(action, actionEl, attackPlannerContext())) return;
  if (await globalThis.ReinosDefenderBattleReports?.handleAction?.(action, actionEl, attackPlannerContext())) return;
  if (await globalThis.RealmBattleReports?.handleAction(action, actionEl, attackPlannerContext())) return;
  if (await globalThis.RealmFalkenEspionage?.handleAction(action, actionEl, falkenEspionageContext())) return;
  if (await globalThis.ReinosPlayerInteractions?.handleAction?.(action, actionEl, attackPlannerContext())) return;
  if (await globalThis.ReinosReportFolders?.handleAction?.(action, actionEl, { renderActiveView })) return;
  if (await globalThis.ReinosTravelPlanning?.handleAction?.(action, actionEl, attackPlannerContext())) return;
  if (await globalThis.ReinosOutposts?.handleAction?.(action, actionEl, outpostSystemContext())) return;
  if (await globalThis.RealmAttackPlanner?.handleAction(action, actionEl, attackPlannerContext())) return;
  if (await globalThis.ReinosBarracksSystem?.handleAction?.(action, actionEl, barracksSystemContext())) return;
  if (await globalThis.ReinosWorkshopSystem?.handleAction?.(action, actionEl, workshopSystemContext())) return;
  if (await globalThis.ReinosBuildingDemolitionConfirmation?.handleAction?.(action, actionEl, castleSystemContext())) return;
  if (await globalThis.ReinosCastleSystem?.handleAction?.(action, actionEl, castleSystemContext())) return;

  if (action === 'map-go') {
    const coordinateX = normalizeWorldCoordinate($('#map-x-input')?.value, profile.worldPosition.x);
    const coordinateY = normalizeWorldCoordinate($('#map-y-input')?.value, profile.worldPosition.y);
    pendingMapFocus = { x: coordinateX, y: coordinateY };
    centerWorldMap($('#world-map'), coordinateX, coordinateY, true);
    return;
  }
  if (action === 'map-home') {
    pendingMapFocus = { ...profile.worldPosition };
    centerWorldMap($('#world-map'), profile.worldPosition.x, profile.worldPosition.y, true);
    return;
  }
  if (action === 'construction-queue-open') {
    openConstructionQueueModal(actionEl.dataset.id);
    return;
  }
  if (action === 'construction-queue-buy') {
    await buyConstructionQueue(actionEl.dataset.slot, actionEl.dataset.package);
    return;
  }

  if (action === 'build-slot') showBuildModal(Number(actionEl.dataset.slot));
  if (action === 'build-first-empty') showBuildModal(0);
  if (action === 'build-type') {
    const type = String(actionEl.dataset.type || '');
    const def = BUILDINGS[type];
    const slot = getFirstEmptySlot(type);
    if (!def || slot < 0) return toast('Não existe espaço livre suficiente para este edifício.', 'error');
    if ((def.keepRequired || 1) > getKeepLevel()) return toast(`Necessitas da Fortaleza no nível ${def.keepRequired}.`, 'error');
    if (!canAfford(buildingCost(type, 1))) return toast('Não tens recursos suficientes.', 'error');
    activeView = 'castle';
    renderGame();
    requestAnimationFrame(() => globalThis.ReinosCastleGridPlacement?.beginBuild?.(type, profile, castleSystemContext()));
  }
  if (action === 'build-confirm') startBuilding(actionEl.dataset.type,Number(actionEl.dataset.slot));
  if (action === 'building-open') showBuildingModal(actionEl.dataset.id);
  if (action === 'building-upgrade') upgradeBuilding(actionEl.dataset.id);
  if (action === 'building-demolish') demolishBuilding(actionEl.dataset.id);
  if (action === 'recruit-open') showRecruitModal(actionEl.dataset.type);
  if (action === 'recruit-confirm') recruitTroops(actionEl.dataset.type,$('#recruit-count').value);
  if (action === 'attack-open') showAttackModal(actionEl.dataset.id);
  if (action === 'attack-confirm') sendAttack(actionEl.dataset.id);
  if (action === 'claim-daily') claimDaily();
  if (action === 'settings-open') showSettings();
  if (action === 'settings-save') {
    const name = $('#kingdom-name-input').value.trim();
    const currentPassword = $('#settings-current-password')?.value || '';
    const newPassword = $('#settings-new-password')?.value || '';
    const newPasswordConfirm = $('#settings-new-password-confirm')?.value || '';
    const wantsPasswordChange = currentPassword.length > 0 || newPassword.length > 0 || newPasswordConfirm.length > 0;

    if (name.length < 3) return toast('O nome do reino é demasiado curto.', 'error');

    let passwordChanged = false;
    if (wantsPasswordChange) {
      if (!currentPassword || !newPassword || !newPasswordConfirm) return toast('Preenche a palavra-passe atual, a nova palavra-passe e a confirmação.', 'error');
      if (newPassword.length < 4) return toast('A nova palavra-passe tem de ter pelo menos 4 caracteres.', 'error');
      if (newPassword !== newPasswordConfirm) return toast('A confirmação da nova palavra-passe não coincide.', 'error');
      try {
        if (!APP_API.changePassword) throw new Error('Alteração de palavra-passe indisponível neste cliente.');
        const result = await APP_API.changePassword(currentPassword, newPassword);
        if (result?.data) db = result.data;
        const refreshedUser = db.users.find(user => user.id === currentUser.id);
        if (refreshedUser) currentUser = refreshedUser;
        passwordChanged = true;
      } catch (error) {
        return toast(error?.message || 'Não foi possível alterar a palavra-passe.', 'error');
      }
    }

    profile.kingdomName = name;
    closeModal();
    renderGame();
    await persist(true);
    toast(passwordChanged ? 'Definições e palavra-passe atualizadas.' : 'Nome do reino atualizado.', 'success');
  }
  if (action === 'profile-reset') {
    openModal('Confirmar reinício', '<p>Todo o progresso deste jogador será apagado e o reino voltará ao início. Esta ação não pode ser anulada.</p>', '<button class="secondary-btn" data-action="modal-close">Cancelar</button><button class="danger-btn" data-action="profile-reset-confirm">Reiniciar</button>');
  }
  if (action === 'profile-reset-confirm') {
    try {
      await resetCurrentProfileToNewAccount();
      activeView = 'castle';
      closeModal();
      renderGame();
      toast('O reino foi reiniciado como uma conta nova.', 'success');
    } catch (error) {
      toast(error?.message || 'Não foi possível reiniciar o progresso.', 'error');
    }
  }
  if (action === 'rush-construction') {
    const item = profile.constructionQueue.find(q=>q.id===actionEl.dataset.id);
    if (!item) return;
    const cost = rushCost(item.endAt);
    if (profile.resources.gems < cost) return toast('Não tens coroas suficientes.','error');
    profile.resources.gems -= cost; item.endAt=now(); processTick(); closeModal();
  }
  if (action === 'alliance-create-open') {
    openModal('Criar aliança', `<div class="field"><label>Nome da aliança</label><input id="alliance-name" maxlength="24" placeholder="Ex.: Irmandade de Ferro" /></div><p>Custo de fundação: ${RESOURCE_META.coins.icon} 1.500 moedas.</p>`, '<button class="secondary-btn" data-action="modal-close">Cancelar</button><button class="action-btn" data-action="alliance-create-confirm">Criar</button>');
  }
  if (action === 'alliance-create-confirm') {
    const name = $('#alliance-name').value.trim();
    if (name.length<3) return toast('Escolhe um nome válido.','error');
    if (profile.resources.coins<1500) return toast('Não tens moedas suficientes.','error');
    profile.resources.coins-=1500; profile.alliance={name,level:1,treasury:0}; profile.chat=[{user:'Sistema',text:`${currentUser.username} fundou a aliança.`,at:now()}]; closeModal(); renderGame(); persist(true);
  }
  if (action === 'alliance-donate') {
    const amount=Math.floor(Number($('#donation-amount').value)||0);
    if(amount<100||profile.resources.coins<amount) return toast('Doação inválida ou saldo insuficiente.','error');
    profile.resources.coins-=amount; profile.alliance.treasury+=amount;
    const needed=profile.alliance.level*5000;
    if(profile.alliance.treasury>=needed){profile.alliance.treasury-=needed;profile.alliance.level++;toast('A aliança subiu de nível!','success');}
    renderGame();persist(true);
  }
  if(action==='chat-send'){
    const input=$('#chat-text'); const text=input.value.trim(); if(!text)return;
    profile.chat.push({user:currentUser.username,text,at:now()}); profile.chat=profile.chat.slice(-100); renderActiveView();persist(true);
  }
  if(action==='quest-claim'){
    const q=questDefinitions().find(x=>x.id===actionEl.dataset.id); if(!q||q.current<q.target||profile.questsClaimed.includes(q.id))return;
    profile.questsClaimed.push(q.id);addResources(q.reward);gainXp(80);renderGame();persist(true);toast('Recompensa da missão recolhida!','success');
  }
});

window.addEventListener('beforeunload', () => { if(db&&currentUser&&profile){db.profiles[currentUser.id]=profile;APP_API.saveData(db);} });

init().catch(error => {
  console.error(error);
  $('#app').innerHTML = `<div style="padding:40px;color:white"><h1>Não foi possível iniciar o jogo</h1><pre>${escapeHtml(error.message)}</pre></div>`;
});
