'use strict';

(function () {
  const VERSION = 3;
  const MAX_CAPTURED = 3;
  const OUTPOST_COUNT = 24;
  const OUTPOST_QUEUE_LIMIT = 1;

  function queueLimit(ctx) {
    const value = Number(ctx?.constructionQueueLimit?.());
    return Math.max(OUTPOST_QUEUE_LIMIT, Number.isFinite(value) ? value : OUTPOST_QUEUE_LIMIT);
  }
  const CAPTURE_OCCUPATION_MS = 2 * 60 * 60 * 1000;
  const MAX_OUTPOST_EXPANSION = 3;
  const OUTPOST_BASE_SLOTS = 12;
  const OUTPOST_SLOTS_PER_EXPANSION = 4;
  const proxyCache = new WeakMap();

  const TYPES = {
    food_wood: {
      label: 'Comida + Madeira',
      shortLabel: 'Comida/Madeira',
      icon: '🌾',
      resources: ['food', 'wood'],
      workloads: { food: 6, wood: 2, stone: 0 },
      rarity: 'normal',
      description: 'Posto avançado equilibrado para sustentar tropas e obter madeira.'
    },
    wood_food: {
      label: 'Madeira + Comida',
      shortLabel: 'Madeira/Comida',
      icon: '🪵',
      resources: ['wood', 'food'],
      workloads: { food: 2, wood: 6, stone: 0 },
      rarity: 'normal',
      description: 'Posto avançado focado em madeira, mantendo produção de comida.'
    },
    food_stone: {
      label: 'Comida + Pedra',
      shortLabel: 'Comida/Pedra',
      icon: '🌾',
      resources: ['food', 'stone'],
      workloads: { food: 6, wood: 0, stone: 2 },
      rarity: 'normal',
      description: 'Posto avançado equilibrado para sustentar tropas e obter pedra.'
    },
    stone_food: {
      label: 'Pedra + Comida',
      shortLabel: 'Pedra/Comida',
      icon: '🪨',
      resources: ['stone', 'food'],
      workloads: { food: 2, wood: 0, stone: 6 },
      rarity: 'normal',
      description: 'Posto avançado focado em pedra, mantendo produção de comida.'
    },
    shiny_food_wood: {
      label: 'Posto rico: Comida + Madeira',
      shortLabel: 'Rico Comida/Madeira',
      icon: '✨',
      resources: ['food', 'wood'],
      workloads: { food: 8, wood: 2, stone: 0 },
      rarity: 'rare',
      description: 'Posto avançado raro com produção principal de comida reforçada.'
    },
    shiny_food_stone: {
      label: 'Posto rico: Comida + Pedra',
      shortLabel: 'Rico Comida/Pedra',
      icon: '✨',
      resources: ['food', 'stone'],
      workloads: { food: 8, wood: 0, stone: 2 },
      rarity: 'rare',
      description: 'Posto avançado raro com produção principal de comida reforçada.'
    }
  };

  const TYPE_ORDER = ['food_wood', 'wood_food', 'food_stone', 'stone_food', 'shiny_food_wood', 'food_wood', 'food_stone', 'wood_food', 'stone_food', 'shiny_food_stone'];

  const OUTPOST_BUILDINGS = {
    keep: {
      name: 'Fortaleza',
      icon: '🏰',
      description: 'Centro do posto avançado. Funciona como a fortaleza do castelo principal.',
      baseCost: { wood: 900, stone: 900, coins: 350 },
      baseTime: 30,
      max: 1,
      maxLevel: 80
    },
    outpost_keep: {
      name: 'Casa do Administrador',
      icon: '🏯',
      description: 'Centro do posto avançado. Desbloqueia melhorias superiores.',
      baseCost: { wood: 760, stone: 760, coins: 300 },
      baseTime: 20,
      max: 1,
      maxLevel: 30
    },
    farm: {
      name: 'Quinta',
      icon: '🌾',
      description: 'Produz comida no posto avançado.',
      baseCost: { wood: 220, stone: 140, coins: 60 },
      baseTime: 8,
      production: { food: 155 }
    },
    woodcutter: {
      name: 'Madeireira',
      icon: '🪵',
      description: 'Produz madeira no posto avançado.',
      baseCost: { wood: 260, stone: 130, coins: 70 },
      baseTime: 9,
      production: { wood: 140 }
    },
    quarry: {
      name: 'Pedreira',
      icon: '🪨',
      description: 'Produz pedra no posto avançado.',
      baseCost: { wood: 150, stone: 260, coins: 70 },
      baseTime: 9,
      production: { stone: 140 }
    },
    warehouse: {
      name: 'Depósito',
      icon: '📦',
      description: 'Armazém local do posto avançado.',
      baseCost: { wood: 420, stone: 470, coins: 150 },
      baseTime: 12,
      max: 2,
      maxLevel: 20
    },
    house: {
      name: 'Casa',
      icon: '🏠',
      description: 'Gera moedas no posto avançado.',
      baseCost: { wood: 260, stone: 190, coins: 80 },
      baseTime: 10,
      production: { coins: 45 }
    },
    barracks: {
      name: 'Quartel do Posto',
      icon: '⚔️',
      description: 'Instalação militar local para organizar a guarnição do posto.',
      baseCost: { wood: 650, stone: 540, coins: 250 },
      baseTime: 18,
      max: 1,
      maxLevel: 20
    },
    guardhouse: {
      name: 'Posto de Guarda',
      icon: '🛡️',
      description: 'Reforça a defesa local do posto avançado.',
      baseCost: { wood: 500, stone: 700, coins: 240 },
      baseTime: 14,
      max: 2,
      defense: 90
    },
    watchtower: {
      name: 'Torre de Vigia',
      icon: '🗼',
      description: 'Aumenta a defesa do posto avançado.',
      baseCost: { wood: 740, stone: 1050, coins: 390 },
      baseTime: 24,
      max: 4,
      defense: 140
    },
    marketplace: {
      name: 'Mercado',
      icon: '⚖️',
      description: 'Permite trocar e enviar recursos a partir deste posto.',
      baseCost: { wood: 700, stone: 620, coins: 400 },
      baseTime: 22,
      max: 1,
      keepRequired: 2
    },
    workshop: {
      name: 'Oficina',
      icon: '🔨',
      description: 'Produz ferramentas de cerco e defesa no posto avançado.',
      baseCost: { wood: 850, stone: 750, coins: 520 },
      baseTime: 26,
      max: 1,
      keepRequired: 2
    },
    stables: {
      name: 'Estábulos',
      icon: '🐎',
      description: 'Aumenta a velocidade das marchas e desbloqueia 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
    },
    decoration: {
      name: 'Monumento',
      icon: '🗿',
      description: 'Aumenta a ordem local e deixa o posto mais evoluído.',
      baseCost: { wood: 420, stone: 520, coins: 260 },
      baseTime: 14,
      productivity: 0.03
    }
  };

  const NAME_PARTS = ['Norte', 'Leste', 'Sul', 'Oeste', 'Carvalhal', 'Pedreira', 'Vale', 'Serra', 'Rio', 'Fronteira', 'Alto', 'Prado'];

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

  function seededRandom(seedText) {
    let state = hash(seedText) || 1;
    return () => {
      state += 0x6D2B79F5;
      let value = state;
      value = Math.imul(value ^ (value >>> 15), value | 1);
      value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
      return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
    };
  }

  function clamp(value, min, max) {
    return Math.max(min, Math.min(max, value));
  }

  function normalize(ctx, value, fallback = 0) {
    const fn = ctx?.normalizeWorldCoordinate;
    if (typeof fn === 'function') return fn(value, fallback);
    const parsed = Number(value);
    return Math.round(Number.isFinite(parsed) ? parsed : fallback);
  }

  function typeForIndex(index, seed) {
    const offset = hash(seed) % TYPE_ORDER.length;
    return TYPE_ORDER[(index + offset) % TYPE_ORDER.length];
  }

  function outpostLevelFor(index, typeKey) {
    const type = TYPES[typeKey] || TYPES.food_wood;
    const rarityBonus = type.rarity === 'rare' ? 3 : 0;
    return Math.max(2, Math.min(14, 3 + (index % 7) + rarityBonus));
  }

  function outpostStrength(level, typeKey) {
    const type = TYPES[typeKey] || TYPES.food_wood;
    const rarityMultiplier = type.rarity === 'rare' ? 1.18 : 1;
    return Math.round((170 + Number(level) * 80 + Math.pow(Number(level), 1.35) * 32) * rarityMultiplier);
  }

  function outpostLoot(level, typeKey) {
    const type = TYPES[typeKey] || TYPES.food_wood;
    const multiplier = Math.pow(1.28, Math.max(0, Number(level) - 1));
    return {
      wood: Math.round((type.workloads.wood > 0 ? 520 : 180) * multiplier),
      stone: Math.round((type.workloads.stone > 0 ? 520 : 180) * multiplier),
      food: Math.round((type.workloads.food > 0 ? 620 : 150) * multiplier),
      coins: Math.round(180 * multiplier)
    };
  }

  function outpostSlotCount(outpost) {
    return OUTPOST_BASE_SLOTS + clamp(Math.floor(Number(outpost?.expansionLevel) || 0), 0, MAX_OUTPOST_EXPANSION) * OUTPOST_SLOTS_PER_EXPANSION;
  }

  function makeDefaultBuildings(outpost, ctx) {
    const buildings = [
      { id: ctx.uid('opb'), type: 'keep', level: 1, slot: 0, status: 'ready' },
      { id: ctx.uid('opb'), type: 'farm', level: 1, slot: 1, status: 'ready' }
    ];
    if (Number(outpost.workloads?.wood) > 0) buildings.push({ id: ctx.uid('opb'), type: 'woodcutter', level: 1, slot: 2, status: 'ready' });
    if (Number(outpost.workloads?.stone) > 0) buildings.push({ id: ctx.uid('opb'), type: 'quarry', level: 1, slot: 2, status: 'ready' });
    buildings.push({ id: ctx.uid('opb'), type: 'watchtower', level: 1, slot: 3, status: 'ready' });
    return buildings;
  }

  function generatedOutposts(profile, ctx) {
    const base = profile.worldPosition || { x: 0, y: 0 };
    const seed = `${profile.userId || profile.kingdomName || 'jogador'}:${base.x}:${base.y}`;
    const list = [];
    const rings = [150, 245, 355, 480];
    for (let i = 0; i < OUTPOST_COUNT; i += 1) {
      const ring = Math.floor(i / 6);
      const indexInRing = i % 6;
      const angle = ((indexInRing / 6) * Math.PI * 2) + (ring * 0.38) + ((hash(`${seed}:${i}:angle`) % 26 - 13) * Math.PI / 180);
      const radius = rings[ring] + (hash(`${seed}:${i}:radius`) % 54) - 27;
      const typeKey = typeForIndex(i, seed);
      const type = TYPES[typeKey] || TYPES.food_wood;
      const id = `op_${i + 1}`;
      const level = outpostLevelFor(i, typeKey);
      list.push({
        id,
        name: `Posto ${NAME_PARTS[i % NAME_PARTS.length]} ${i + 1}`,
        type: typeKey,
        typeLabel: type.label,
        level,
        x: normalize(ctx, Number(base.x) + Math.cos(angle) * radius, Number(base.x) || 0),
        y: normalize(ctx, Number(base.y) + Math.sin(angle) * radius, Number(base.y) || 0),
        workloads: { ...type.workloads },
        rarity: type.rarity,
        ownerUserId: null,
        ownerName: '',
        capturedAt: 0,
        captureStatus: 'free',
        pendingCapture: null,
        buildings: [],
        constructionQueue: [],
        expansionLevel: 0,
        defenses: { wallLevel: 1, gateLevel: 1, towerLevel: 1 }
      });
    }
    return list;
  }


  function ensureOutpostCastleShape(outpost, ctx = {}) {
    if (!outpost || typeof outpost !== 'object') return false;
    let changed = false;

    if (!Array.isArray(outpost.buildings)) { outpost.buildings = []; changed = true; }
    if (!Array.isArray(outpost.constructionQueue)) { outpost.constructionQueue = []; changed = true; }

    for (const building of outpost.buildings) {
      if (building.type === 'outpost_keep') { building.type = 'keep'; changed = true; }
      if (building.type === 'guardhouse') { building.type = 'watchtower'; changed = true; }
      if (!Number.isFinite(Number(building.slot))) { building.slot = 0; changed = true; }
      if (!building.status) { building.status = 'ready'; changed = true; }
      if (!Number.isFinite(Number(building.level))) { building.level = building.status === 'constructing' ? 0 : 1; changed = true; }
    }

    if (outpost.ownerUserId && outpost.captureStatus === 'owned' && !outpost.buildings.some(building => building.type === 'keep')) {
      const defaults = makeDefaultBuildings(outpost, ctx);
      outpost.buildings = [...defaults, ...outpost.buildings.filter(building => building.type !== 'outpost_keep' && building.type !== 'keep')];
      changed = true;
    }

    if (!outpost.defenses || typeof outpost.defenses !== 'object') {
      outpost.defenses = { wallLevel: 1, gateLevel: 1, towerLevel: 1 };
      changed = true;
    }

    if (!outpost.castleState || typeof outpost.castleState !== 'object') {
      outpost.castleState = {
        version: 2,
        expansionLevel: Math.max(0, Number(outpost.expansionLevel) || 0),
        wallLevel: Math.max(1, Number(outpost.defenses.wallLevel) || 1),
        gateLevel: Math.max(1, Number(outpost.defenses.gateLevel) || 1),
        towerLevel: Math.max(1, Number(outpost.defenses.towerLevel) || 1)
      };
      changed = true;
    }

    outpost.castleState.wallLevel = Math.max(1, Number(outpost.castleState.wallLevel) || Number(outpost.defenses.wallLevel) || 1);
    outpost.castleState.gateLevel = Math.max(1, Number(outpost.castleState.gateLevel) || Number(outpost.defenses.gateLevel) || 1);
    outpost.castleState.towerLevel = Math.max(1, Number(outpost.castleState.towerLevel) || Number(outpost.defenses.towerLevel) || 1);
    outpost.defenses.wallLevel = outpost.castleState.wallLevel;
    outpost.defenses.gateLevel = outpost.castleState.gateLevel;
    outpost.defenses.towerLevel = outpost.castleState.towerLevel;
    outpost.expansionLevel = Math.max(0, Number(outpost.castleState.expansionLevel) || 0);

    return changed;
  }

  function ensureState(profile, ctx = {}) {
    if (!profile) return false;
    let changed = false;
    if (!profile.outposts || typeof profile.outposts !== 'object') {
      profile.outposts = { version: VERSION, list: generatedOutposts(profile, ctx) };
      changed = true;
    }
    if (!Array.isArray(profile.outposts.list) || profile.outposts.list.length < OUTPOST_COUNT) {
      const previous = Array.isArray(profile.outposts.list) ? profile.outposts.list : [];
      const generated = generatedOutposts(profile, ctx);
      profile.outposts.list = generated.map((fresh) => ({ ...fresh, ...(previous.find(item => item?.id === fresh.id) || {}) }));
      changed = true;
    }
    const generated = generatedOutposts(profile, ctx);
    profile.outposts.list.forEach((outpost, index) => {
      const fresh = generated.find(item => item.id === outpost.id) || generated[index];
      const type = TYPES[outpost.type] || TYPES.food_wood;
      if (!outpost.name) { outpost.name = `Posto Avançado ${outpost.id || ''}`.trim(); changed = true; }
      if (!outpost.typeLabel) { outpost.typeLabel = type.label; changed = true; }
      if (!outpost.workloads) { outpost.workloads = { ...type.workloads }; changed = true; }
      if (!Number.isFinite(Number(outpost.level))) { outpost.level = fresh?.level || outpostLevelFor(index, outpost.type); changed = true; }
      if (!Array.isArray(outpost.buildings)) { outpost.buildings = []; changed = true; }
      if (!Array.isArray(outpost.constructionQueue)) { outpost.constructionQueue = []; changed = true; }
      if (!Number.isFinite(Number(outpost.expansionLevel))) { outpost.expansionLevel = 0; changed = true; }
      if (!outpost.defenses || typeof outpost.defenses !== 'object') { outpost.defenses = { wallLevel: 1, gateLevel: 1, towerLevel: 1 }; changed = true; }
      for (const key of ['wallLevel', 'gateLevel', 'towerLevel']) {
        if (!Number.isFinite(Number(outpost.defenses[key])) || Number(outpost.defenses[key]) < 1) { outpost.defenses[key] = 1; changed = true; }
      }
      if (!outpost.captureStatus) { outpost.captureStatus = outpost.ownerUserId ? 'owned' : 'free'; changed = true; }
      if (outpost.ownerUserId && outpost.captureStatus !== 'owned') { outpost.captureStatus = 'owned'; changed = true; }
      if (!outpost.ownerUserId && outpost.captureStatus === 'owned') { outpost.captureStatus = 'free'; changed = true; }
      if (outpost.pendingCapture && Number(outpost.pendingCapture.endAt) <= 0) { outpost.pendingCapture = null; changed = true; }
      if (ensureOutpostCastleShape(outpost, ctx)) changed = true;
      if (!Number.isFinite(Number(outpost.x)) || !Number.isFinite(Number(outpost.y))) {
        outpost.x = fresh?.x ?? 0;
        outpost.y = fresh?.y ?? 0;
        changed = true;
      }
    });
    if (profile.activeLocationId && profile.activeLocationId !== 'main') {
      const active = profile.outposts.list.find(item => item.id === profile.activeLocationId);
      if (!active || active.ownerUserId !== profile.userId) { profile.activeLocationId = 'main'; changed = true; }
    }
    if (Number(profile.outposts.version) !== VERSION) { profile.outposts.version = VERSION; changed = true; }
    return changed;
  }

  function capturedOutposts(profile, userId) {
    return (profile?.outposts?.list || []).filter(outpost => outpost.ownerUserId === userId && outpost.captureStatus === 'owned');
  }

  function pendingOutposts(profile, userId) {
    return (profile?.outposts?.list || []).filter(outpost => outpost.pendingCapture?.ownerUserId === userId && outpost.captureStatus === 'occupying');
  }

  function captureMissionCount(profile) {
    return (profile?.missions || []).filter(mission => mission.targetType === 'outpost' && mission.state !== 'done').length;
  }

  function activeOutpost(ctx) {
    const profile = ctx.getProfile?.();
    const user = ctx.getCurrentUser?.();
    const activeId = profile?.activeLocationId;
    if (!activeId || activeId === 'main') return null;
    const outpost = profile?.outposts?.list?.find(item => item.id === activeId);
    return outpost?.ownerUserId === user?.id && outpost.captureStatus === 'owned' ? outpost : null;
  }

  function isActiveOutpost(ctx) {
    return Boolean(activeOutpost(ctx));
  }

  function resourceName(type, ctx) {
    return ctx.resourceMeta?.[type]?.name || ({ food: 'Comida', wood: 'Madeira', stone: 'Pedra', coins: 'Moedas' }[type] || type);
  }

  function resourceIcon(type, ctx) {
    return ctx.resourceMeta?.[type]?.icon || ({ food: '🌾', wood: '🪵', stone: '🪨', coins: '🪙' }[type] || '');
  }

  function resourceSummaryHtml(type, value, ctx) {
    return `<span class="outpost-resource-chip">${resourceIcon(type, ctx)} <span>${ctx.escapeHtml(resourceName(type, ctx))}</span> <strong>${ctx.format(value)}</strong></span>`;
  }

  function resourceSummaryText(type, value, ctx) {
    return `${resourceName(type, ctx)} ${ctx.format(value)}`;
  }

  function formatCost(cost, ctx) {
    if (ctx.costsHtml) return ctx.costsHtml(cost);
    return Object.entries(cost).map(([key, value]) => `<span class="cost">${resourceIcon(key, ctx)} ${ctx.format(value)}</span>`).join('');
  }

  function outpostKeepLevel(outpost) {
    return outpost.buildings.find(building => building.type === 'keep' || building.type === 'outpost_keep')?.level || 1;
  }

  function buildingCount(outpost, type) {
    return outpost.buildings.filter(building => building.type === type).length;
  }

  function buildingCost(type, level = 1) {
    const def = OUTPOST_BUILDINGS[type] || OUTPOST_BUILDINGS.decoration;
    const factor = Math.pow(1.55, Math.max(0, level - 1));
    return Object.fromEntries(Object.entries(def.baseCost || {}).map(([key, value]) => [key, Math.round(value * factor)]));
  }

  function buildingTime(type, level = 1) {
    const def = OUTPOST_BUILDINGS[type] || OUTPOST_BUILDINGS.decoration;
    return Math.round((def.baseTime || 8) * Math.pow(1.28, Math.max(0, level - 1)) * 1000);
  }

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

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

  function availableBuildingTypes(outpost) {
    const allowed = ['keep', 'farm', 'warehouse', 'house', 'barracks', 'watchtower', 'marketplace', 'workshop', 'stables', 'hospital', 'tavern', 'decoration'];
    if (Number(outpost.workloads?.wood) > 0) allowed.push('woodcutter');
    if (Number(outpost.workloads?.stone) > 0) allowed.push('quarry');
    return allowed;
  }

  function firstEmptySlot(outpost) {
    const used = new Set(outpost.buildings.map(building => Number(building.slot)));
    for (let slot = 0; slot < outpostSlotCount(outpost); slot += 1) if (!used.has(slot)) return slot;
    return -1;
  }

  function productionRates(outpost) {
    const rates = { wood: 0, stone: 0, food: 0, coins: 0 };
    const productivity = 1 + (outpost.buildings || []).filter(b => b.type === 'decoration' && b.status !== 'constructing').reduce((sum, b) => sum + (Number(b.level) || 1) * 0.03, 0);
    for (const building of outpost.buildings || []) {
      if (building.status === 'constructing' || Number(building.level) < 1) continue;
      const def = OUTPOST_BUILDINGS[building.type];
      if (!def?.production) continue;
      for (const [resource, base] of Object.entries(def.production)) {
        const workload = resource === 'coins' ? 2 : Number(outpost.workloads?.[resource]) || 0;
        if (resource !== 'coins' && workload <= 0) continue;
        const workloadMultiplier = resource === 'coins' ? 1 : workload / 2;
        rates[resource] += base * Math.pow(1.38, Number(building.level) - 1) * workloadMultiplier * productivity;
      }
    }
    return rates;
  }

  function applyProduction(rates, ctx) {
    const profile = ctx.getProfile?.();
    const user = ctx.getCurrentUser?.();
    ensureState(profile, ctx);
    for (const outpost of capturedOutposts(profile, user?.id)) {
      const extra = productionRates(outpost);
      for (const key of Object.keys(rates)) rates[key] = (Number(rates[key]) || 0) + (Number(extra[key]) || 0);
    }
  }

  function outpostSummary(outpost, ctx) {
    const type = TYPES[outpost.type] || TYPES.food_wood;
    const parts = Object.entries(outpost.workloads || {})
      .filter(([, value]) => Number(value) > 0)
      .map(([key, value]) => resourceSummaryHtml(key, value, ctx))
      .join('<span class="outpost-resource-separator">·</span>');
    const text = Object.entries(outpost.workloads || {})
      .filter(([, value]) => Number(value) > 0)
      .map(([key, value]) => resourceSummaryText(key, value, ctx))
      .join(' · ');
    return { type, parts, text };
  }

  function totalProductionHtml(outpost, ctx) {
    const rates = productionRates(outpost);
    const entries = Object.entries(rates).filter(([, value]) => value > 0);
    if (!entries.length) return '<div class="empty-state">Sem produção ativa.</div>';
    return `<div class="outpost-production-line">${entries.map(([key, value]) => `<span>${resourceIcon(key, ctx)} ${resourceName(key, ctx)} <strong>+${ctx.format(value)}/h</strong></span>`).join('')}</div>`;
  }

  function outpostStatus(outpost, user, ctx) {
    if (outpost.ownerUserId === user?.id && outpost.captureStatus === 'owned') return 'Teu posto';
    if (outpost.ownerUserId) return `Ocupado por ${outpost.ownerName || 'Jogador'}`;
    if (outpost.captureStatus === 'occupying' && outpost.pendingCapture) {
      const name = outpost.pendingCapture.ownerName || 'Jogador';
      return `Ocupação de ${name} • ${ctx.formatTime(outpost.pendingCapture.endAt - ctx.now())}`;
    }
    return 'Livre para conquistar';
  }

  function renderMapNodes(ctx) {
    const profile = ctx.getProfile?.();
    const user = ctx.getCurrentUser?.();
    ensureState(profile, ctx);
    return (profile?.outposts?.list || []).map(outpost => {
      const own = outpost.ownerUserId === user?.id && outpost.captureStatus === 'owned';
      const occupying = outpost.captureStatus === 'occupying' && !!outpost.pendingCapture;
      const taken = (!!outpost.ownerUserId && !own) || occupying;
      const type = TYPES[outpost.type] || TYPES.food_wood;
      const status = outpostStatus(outpost, user, ctx);
      const rare = type.rarity === 'rare';
      const label = `${outpost.name} — ${type.shortLabel}`;
      const className = `map-node outpost-node ${own ? 'owned' : ''} ${taken ? 'taken' : ''} ${rare ? 'rare' : ''} ${occupying ? 'occupying' : ''}`;
      return `<div class="${className}" style="left:${ctx.worldPixelX(outpost.x)}px;top:${ctx.worldPixelY(outpost.y)}px">
        <button class="outpost-node-btn" data-action="outpost-open" data-id="${ctx.escapeHtml(outpost.id)}" title="${ctx.escapeHtml(label)}">
          <span class="outpost-node-icon">${occupying ? '⏳' : rare ? '✨' : type.icon}</span>
        </button>
        <div class="outpost-map-label">
          <strong>${ctx.escapeHtml(outpost.name)}</strong>
          <small>
            <em>${ctx.escapeHtml(status)}</em>
            <em>Nv. ${ctx.format(outpost.level)} · X: ${ctx.format(outpost.x)} · Y: ${ctx.format(outpost.y)}</em>
          </small>
        </div>
      </div>`;
    }).join('');
  }

  function renderSlots(outpost, ctx) {
    return Array.from({ length: outpostSlotCount(outpost) }, (_, slot) => {
      const building = outpost.buildings.find(item => Number(item.slot) === slot);
      if (!building) return `<button class="outpost-slot empty" data-action="outpost-build-slot" data-id="${ctx.escapeHtml(outpost.id)}" data-slot="${slot}">＋</button>`;
      const def = OUTPOST_BUILDINGS[building.type] || { icon: '🏠', name: building.type };
      const queue = outpost.constructionQueue.find(item => item.buildingId === building.id);
      return `<button class="outpost-slot" data-action="outpost-building-open" data-id="${ctx.escapeHtml(outpost.id)}" data-building-id="${ctx.escapeHtml(building.id)}">
        <strong>${def.icon}</strong>
        <span>${ctx.escapeHtml(def.name)}</span>
        <small>Nível ${Number(building.level) || 0}</small>
        ${queue ? `<em data-countdown="${queue.endAt}">${ctx.formatTime(queue.endAt - ctx.now())}</em>` : ''}
      </button>`;
    }).join('');
  }

  function makeAttackTarget(profile, rawId, ctx = {}) {
    const id = String(rawId || '').replace(/^outpost:/, '');
    const outpost = profile?.outposts?.list?.find(item => item.id === id);
    if (!outpost) return null;
    const type = TYPES[outpost.type] || TYPES.food_wood;
    return {
      id: `outpost:${outpost.id}`,
      outpostId: outpost.id,
      targetType: 'outpost',
      portraitType: 'outpost',
      name: outpost.name,
      level: Number(outpost.level) || 3,
      x: outpost.x,
      y: outpost.y,
      strength: outpostStrength(outpost.level, outpost.type),
      loot: outpostLoot(outpost.level, outpost.type),
      cooldownUntil: outpost.captureStatus === 'occupying' ? Number(outpost.pendingCapture?.endAt || 0) : 0,
      typeLabel: type.label,
      shortLabel: type.shortLabel,
      icon: type.rarity === 'rare' ? '✨' : type.icon
    };
  }

  function generateDefense(target) {
    const defense = globalThis.RealmFalkenEspionage?.generateDefense?.(target);
    if (defense) return defense;
    return {
      totalUnits: Math.max(20, (Number(target?.level) || 1) * 12),
      sectors: [
        { id: 'left', name: 'Flanco esquerdo', wallBonus: 20, gateBonus: 0, rangedBonus: 12, units: { spearman: 10, swordsman: 5, archer: 6, knight: 0 } },
        { id: 'center', name: 'Centro', wallBonus: 25, gateBonus: 20, rangedBonus: 15, units: { spearman: 12, swordsman: 8, archer: 8, knight: 1 } },
        { id: 'right', name: 'Flanco direito', wallBonus: 20, gateBonus: 0, rangedBonus: 12, units: { spearman: 10, swordsman: 5, archer: 6, knight: 0 } }
      ]
    };
  }

  function openOutpost(outpostId, ctx) {
    const profile = ctx.getProfile?.();
    const user = ctx.getCurrentUser?.();
    ensureState(profile, ctx);
    const outpost = profile.outposts.list.find(item => item.id === outpostId);
    if (!outpost) return false;
    const { type, parts } = outpostSummary(outpost, ctx);
    const ownedByPlayer = outpost.ownerUserId === user?.id && outpost.captureStatus === 'owned';
    const capturedCount = capturedOutposts(profile, user?.id).length;
    const pendingCount = pendingOutposts(profile, user?.id).length;
    if (!ownedByPlayer) {
      const occupying = outpost.captureStatus === 'occupying' && outpost.pendingCapture;
      const target = makeAttackTarget(profile, outpost.id, ctx);
      const defense = generateDefense(target);
      const alreadyMarching = (profile.missions || []).some(mission => mission.targetType === 'outpost' && mission.targetId === `outpost:${outpost.id}` && mission.state !== 'done');
      const ownerLine = outpost.ownerUserId
        ? `<div class="outpost-warning">Este posto já pertence a ${ctx.escapeHtml(outpost.ownerName || 'outro jogador')}.</div>`
        : occupying
          ? `<div class="outpost-warning">Ocupação em curso por ${ctx.escapeHtml(outpost.pendingCapture.ownerName || 'Jogador')}. Controlo em <strong data-countdown="${outpost.pendingCapture.endAt}">${ctx.formatTime(outpost.pendingCapture.endAt - ctx.now())}</strong>.</div>`
          : `<div class="outpost-warning">Este posto é guardado por tropas locais. Para o capturar tens de vencer o ataque e aguardar 2 horas de ocupação.</div>`;
      const missionCount = captureMissionCount(profile);
      const limitReached = capturedCount + pendingCount + missionCount >= MAX_CAPTURED;
      const disabled = outpost.ownerUserId || occupying || limitReached || alreadyMarching;
      let disabledText = '';
      if (limitReached) disabledText = `Limite de ${MAX_CAPTURED} postos atingido entre controlados, em marcha e em ocupação.`;
      else if (alreadyMarching) disabledText = 'Já tens um exército a caminho deste posto.';
      ctx.openModal(`🏯 ${ctx.escapeHtml(outpost.name)}`, `
        <div class="outpost-open-card ${type.rarity === 'rare' ? 'rare' : ''}">
          <div class="outpost-open-icon">${type.rarity === 'rare' ? '✨' : type.icon}</div>
          <div><h4>${ctx.escapeHtml(type.label)} • Nível ${ctx.format(outpost.level)}</h4><p>${ctx.escapeHtml(type.description)}</p><div class="outpost-resources">${parts}</div></div>
        </div>
        <div class="outpost-defense-preview"><span>🛡️ Defensores estimados</span><strong>${ctx.format(defense.totalUnits || 0)}</strong><small>Força ${ctx.format(target.strength)}</small></div>
        ${ownerLine}
        ${disabledText ? `<p class="outpost-limit-text">${ctx.escapeHtml(disabledText)}</p>` : ''}
      `, `<button class="secondary-btn" data-action="modal-close">Fechar</button><button class="action-btn" data-action="outpost-capture" data-id="${ctx.escapeHtml(outpost.id)}" ${disabled ? 'disabled' : ''}>⚔️ Atacar para capturar</button>`);
      return true;
    }
    setActiveOutpost(outpost.id, ctx);
    ctx.closeModal?.();
    ctx.renderGame?.();
    return true;
  }

  function openManage(outpost, ctx) {
    const { type, parts } = outpostSummary(outpost, ctx);
    const queue = outpost.constructionQueue[0];
    const queueHtml = queue ? `<div class="outpost-queue">Obras em curso: <strong data-countdown="${queue.endAt}">${ctx.formatTime(queue.endAt - ctx.now())}</strong></div>` : '';
    ctx.openModal(`🏯 ${ctx.escapeHtml(outpost.name)}`, `
      <div class="outpost-manage-head ${type.rarity === 'rare' ? 'rare' : ''}">
        <div><strong>${ctx.escapeHtml(type.label)}</strong><small>${parts}</small></div>
        <div class="outpost-coords">X ${ctx.format(outpost.x)} • Y ${ctx.format(outpost.y)}</div>
      </div>
      ${totalProductionHtml(outpost, ctx)}
      ${queueHtml}
      <div class="outpost-grid">${renderSlots(outpost, ctx)}</div>
    `, `<button class="secondary-btn" data-action="modal-close">Fechar</button><button class="action-btn" data-action="outpost-select-location" data-id="${ctx.escapeHtml(outpost.id)}">Entrar no posto</button>`);
  }

  function startCaptureAttack(outpostId, ctx) {
    const profile = ctx.getProfile?.();
    const user = ctx.getCurrentUser?.();
    ensureState(profile, ctx);
    const outpost = profile?.outposts?.list?.find(item => item.id === outpostId);
    if (!outpost || outpost.ownerUserId || outpost.captureStatus === 'occupying') return true;
    if (capturedOutposts(profile, user?.id).length + pendingOutposts(profile, user?.id).length + captureMissionCount(profile) >= MAX_CAPTURED) {
      ctx.toast(`Só podes ter ${MAX_CAPTURED} postos avançados entre controlados, em marcha e em ocupação.`, 'error');
      return true;
    }
    const targetId = `outpost:${outpost.id}`;
    if ((profile.missions || []).some(mission => mission.targetType === 'outpost' && mission.targetId === targetId && mission.state !== 'done')) {
      ctx.toast('Já tens um exército a caminho deste posto.', 'error');
      return true;
    }
    ctx.closeModal?.();
    ctx.openAttackPlanner?.(targetId);
    return true;
  }

  function openBuildChoice(outpostId, slot, ctx) {
    const profile = ctx.getProfile?.();
    const outpost = profile?.outposts?.list?.find(item => item.id === outpostId);
    if (!outpost || outpost.ownerUserId !== ctx.getCurrentUser?.()?.id || outpost.captureStatus !== 'owned') return false;
    if (outpost.constructionQueue.length >= queueLimit(ctx)) {
      ctx.toast('Este posto avançado já tem uma obra em curso.', 'error');
      return true;
    }
    if (Number(slot) < 0) {
      ctx.toast('Este posto não tem espaço livre. Usa Expandir para ganhar mais parcelas.', 'error');
      return true;
    }
    const keepLevel = outpostKeepLevel(outpost);
    const allowed = availableBuildingTypes(outpost).filter(type => type !== 'outpost_keep' && type !== 'keep');
    const choices = allowed.map(type => {
      const def = OUTPOST_BUILDINGS[type];
      const count = buildingCount(outpost, type);
      const maxedCount = def.max && count >= def.max;
      const cost = buildingCost(type, 1);
      return `<div class="choice">
        <div class="choice-title">${def.icon} ${ctx.escapeHtml(def.name)}</div>
        <small>${ctx.escapeHtml(def.description)}</small>
        <div class="costs">${formatCost(cost, ctx)}</div>
        <div class="stats-line"><span>Tempo</span><strong>${ctx.formatTime(buildingTime(type, 1))}</strong></div>
        <button class="action-btn" data-action="outpost-build-type" data-id="${ctx.escapeHtml(outpost.id)}" data-slot="${slot}" data-type="${ctx.escapeHtml(type)}" ${maxedCount ? 'disabled' : ''}>${maxedCount ? 'Limite atingido' : 'Construir'}</button>
      </div>`;
    }).join('');
    ctx.openModal(`Construir em ${ctx.escapeHtml(outpost.name)}`, `<p>Casa do Administrador nível ${keepLevel}. Este posto só permite edifícios ligados aos recursos que produz.</p><div class="choice-grid">${choices}</div>`, `<button class="secondary-btn" data-action="modal-close">Fechar</button>`);
    return true;
  }

  function openBuilding(outpostId, buildingId, ctx) {
    const profile = ctx.getProfile?.();
    const outpost = profile?.outposts?.list?.find(item => item.id === outpostId);
    const building = outpost?.buildings?.find(item => item.id === buildingId);
    if (!outpost || !building || outpost.ownerUserId !== ctx.getCurrentUser?.()?.id || outpost.captureStatus !== 'owned') return false;
    const def = OUTPOST_BUILDINGS[building.type];
    const queue = outpost.constructionQueue.find(item => item.buildingId === building.id);
    if (queue) {
      ctx.openModal(`${def.icon} ${ctx.escapeHtml(def.name)}`, `<p>Este edifício está em obras.</p><div class="stats-line"><span>Conclusão</span><strong data-countdown="${queue.endAt}">${ctx.formatTime(queue.endAt - ctx.now())}</strong></div>`, `<button class="secondary-btn" data-action="modal-close">Fechar</button>`);
      return true;
    }
    const targetLevel = Number(building.level) + 1;
    const maxLevel = Number(def.maxLevel) || 0;
    const maxed = maxLevel > 0 && Number(building.level) >= maxLevel;
    const blocked = building.type !== 'outpost_keep' && targetLevel > outpostKeepLevel(outpost) + 2;
    const cost = buildingCost(building.type, targetLevel);
    const production = productionRates({ ...outpost, buildings: [building] });
    const prodText = Object.entries(production).filter(([, value]) => value > 0).map(([key, value]) => `${resourceName(key, ctx)} +${ctx.format(value)}/h`).join(' · ') || 'Sem produção direta';
    ctx.openModal(`${def.icon} ${ctx.escapeHtml(def.name)} — Nível ${building.level}`, `
      <p>${ctx.escapeHtml(def.description)}</p>
      <div class="stats-line"><span>Produção atual</span><strong>${ctx.escapeHtml(prodText)}</strong></div>
      ${maxed ? '<div class="outpost-warning">Nível máximo atingido.</div>' : `<div class="building-upgrade-order"><div><span>Tempo da melhoria</span><strong>${ctx.formatTime(buildingTime(building.type, targetLevel))}</strong></div><div><span>Custo da melhoria</span><div class="costs">${formatCost(cost, ctx)}</div></div></div>`}
      ${blocked ? '<div class="outpost-limit-text">Melhora primeiro a Casa do Administrador deste posto.</div>' : ''}
    `, `<button class="danger-btn" data-action="outpost-building-demolish" data-id="${ctx.escapeHtml(outpost.id)}" data-building-id="${ctx.escapeHtml(building.id)}" ${building.type === 'outpost_keep' || building.type === 'keep' ? 'disabled' : ''}>Demolir</button><button class="secondary-btn" data-action="castle-move-start" data-id="${ctx.escapeHtml(building.id)}">Mover</button><button class="secondary-btn" data-action="modal-close">Cancelar</button><button class="action-btn" data-action="outpost-building-upgrade" data-id="${ctx.escapeHtml(outpost.id)}" data-building-id="${ctx.escapeHtml(building.id)}" ${maxed || blocked ? 'disabled' : ''}>${maxed ? 'Nível máximo' : 'Melhorar'}</button>`);
    return true;
  }

  function buildInOutpost(outpostId, slot, type, ctx) {
    const profile = ctx.getProfile?.();
    const outpost = profile?.outposts?.list?.find(item => item.id === outpostId);
    const def = OUTPOST_BUILDINGS[type];
    if (!outpost || !def || outpost.ownerUserId !== ctx.getCurrentUser?.()?.id || outpost.captureStatus !== 'owned') return true;
    if (outpost.constructionQueue.length >= queueLimit(ctx)) return ctx.toast('Este posto avançado já tem uma obra em curso.', 'error'), true;
    if (!availableBuildingTypes(outpost).includes(type) || (type === 'outpost_keep' || type === 'keep')) return ctx.toast('Este edifício não está disponível neste tipo de posto.', 'error'), true;
    if (Number(slot) < 0 || Number(slot) >= outpostSlotCount(outpost)) return ctx.toast('Esse espaço não está disponível neste posto.', 'error'), true;
    if (outpost.buildings.some(building => Number(building.slot) === Number(slot))) return ctx.toast('Este espaço já está ocupado.', 'error'), true;
    if (def.max && buildingCount(outpost, type) >= def.max) return ctx.toast('Atingiste o limite deste edifício no posto.', 'error'), true;
    const cost = buildingCost(type, 1);
    if (!canAfford(profile, cost)) return ctx.toast('Não tens recursos suficientes.', 'error'), true;
    spend(profile, cost);
    const building = { id: ctx.uid('opb'), type, level: 0, slot: Number(slot), status: 'constructing' };
    outpost.buildings.push(building);
    const startedAt = ctx.now();
    outpost.constructionQueue.push({ id: ctx.uid('opq'), buildingId: building.id, action: 'build', targetLevel: 1, startedAt, endAt: startedAt + buildingTime(type, 1) });
    profile.reports.push({ id: ctx.uid('r'), at: startedAt, title: `Construção iniciada no posto`, result: 'info', reportType: 'kingdom', text: `${def.name} começou a ser construído em ${outpost.name}.` });
    ctx.closeModal();
    ctx.renderGame();
    ctx.persist(true);
    return true;
  }

  function upgradeOutpostBuilding(outpostId, buildingId, ctx) {
    const profile = ctx.getProfile?.();
    const outpost = profile?.outposts?.list?.find(item => item.id === outpostId);
    const building = outpost?.buildings?.find(item => item.id === buildingId);
    if (!outpost || !building || outpost.ownerUserId !== ctx.getCurrentUser?.()?.id || outpost.captureStatus !== 'owned') return true;
    if (outpost.constructionQueue.length >= queueLimit(ctx)) return ctx.toast('Este posto avançado já tem uma obra em curso.', 'error'), true;
    const def = OUTPOST_BUILDINGS[building.type];
    const targetLevel = Number(building.level) + 1;
    if (def.maxLevel && targetLevel > def.maxLevel) return ctx.toast(`${def.name} já atingiu o nível máximo.`, 'error'), true;
    if (building.type !== 'outpost_keep' && targetLevel > outpostKeepLevel(outpost) + 2) return ctx.toast('Melhora primeiro a Casa do Administrador deste posto.', 'error'), true;
    const cost = buildingCost(building.type, targetLevel);
    if (!canAfford(profile, cost)) return ctx.toast('Não tens recursos suficientes.', 'error'), true;
    spend(profile, cost);
    building.status = 'upgrading';
    const startedAt = ctx.now();
    outpost.constructionQueue.push({ id: ctx.uid('opq'), buildingId, action: 'upgrade', targetLevel, startedAt, endAt: startedAt + buildingTime(building.type, targetLevel) });
    ctx.closeModal();
    ctx.renderGame();
    ctx.persist(true);
    return true;
  }

  function demolishOutpostBuilding(outpostId, buildingId, ctx) {
    const profile = ctx.getProfile?.();
    const outpost = profile?.outposts?.list?.find(item => item.id === outpostId);
    const building = outpost?.buildings?.find(item => item.id === buildingId);
    if (!outpost || !building || (building.type === 'outpost_keep' || building.type === 'keep') || outpost.ownerUserId !== ctx.getCurrentUser?.()?.id || outpost.captureStatus !== 'owned') return true;
    const def = OUTPOST_BUILDINGS[building.type];
    const refund = Object.fromEntries(Object.entries(buildingCost(building.type, Math.max(1, building.level))).map(([key, value]) => [key, Math.round(value * 0.30)]));
    for (const [key, value] of Object.entries(refund)) profile.resources[key] = (Number(profile.resources[key]) || 0) + value;
    outpost.buildings = outpost.buildings.filter(item => item.id !== buildingId);
    outpost.constructionQueue = outpost.constructionQueue.filter(item => item.buildingId !== buildingId);
    profile.reports.push({ id: ctx.uid('r'), at: ctx.now(), title: `Edifício demolido no posto`, result: 'info', reportType: 'kingdom', text: `${def.name} foi demolido em ${outpost.name}.` });
    ctx.closeModal();
    ctx.renderGame();
    ctx.persist(true);
    return true;
  }

  function outpostExpansionCost(outpost) {
    const next = clamp((Number(outpost.expansionLevel) || 0) + 1, 1, MAX_OUTPOST_EXPANSION);
    return { wood: Math.round(1800 * Math.pow(1.55, next - 1)), stone: Math.round(2100 * Math.pow(1.6, next - 1)), coins: Math.round(500 * Math.pow(1.4, next - 1)) };
  }

  function openOutpostExpansion(ctx) {
    const profile = ctx.getProfile?.();
    const outpost = activeOutpost(ctx);
    if (!outpost) return false;
    if ((Number(outpost.expansionLevel) || 0) >= MAX_OUTPOST_EXPANSION) {
      ctx.toast('Este posto avançado já está no tamanho máximo.', 'error');
      return true;
    }
    const cost = outpostExpansionCost(outpost);
    ctx.openModal('Expandir posto avançado', `<p>Ganha mais ${OUTPOST_SLOTS_PER_EXPANSION} parcelas para construir neste posto.</p><div class="stats-line"><span>Área atual</span><strong>${outpostSlotCount(outpost)} parcelas</strong></div><div class="stats-line"><span>Após expansão</span><strong>${outpostSlotCount(outpost) + OUTPOST_SLOTS_PER_EXPANSION} parcelas</strong></div><div class="costs">${formatCost(cost, ctx)}</div>`, `<button class="secondary-btn" data-action="modal-close">Cancelar</button><button class="action-btn" data-action="outpost-expand-confirm" data-id="${ctx.escapeHtml(outpost.id)}">Expandir</button>`);
    return true;
  }

  function confirmOutpostExpansion(outpostId, ctx) {
    const profile = ctx.getProfile?.();
    const outpost = profile?.outposts?.list?.find(item => item.id === outpostId);
    if (!outpost || outpost.ownerUserId !== ctx.getCurrentUser?.()?.id) return true;
    if ((Number(outpost.expansionLevel) || 0) >= MAX_OUTPOST_EXPANSION) return ctx.toast('Este posto avançado já está no tamanho máximo.', 'error'), true;
    const cost = outpostExpansionCost(outpost);
    if (!canAfford(profile, cost)) return ctx.toast('Não tens recursos suficientes.', 'error'), true;
    spend(profile, cost);
    outpost.expansionLevel = (Number(outpost.expansionLevel) || 0) + 1;
    profile.reports.push({ id: ctx.uid('r'), at: ctx.now(), title: `Posto avançado expandido`, result: 'info', reportType: 'kingdom', text: `${outpost.name} foi expandido para ${outpostSlotCount(outpost)} parcelas.` });
    ctx.closeModal();
    ctx.renderGame();
    ctx.persist(true);
    return true;
  }

  function defenseUpgradeCost(outpost, kind) {
    const key = `${kind}Level`;
    const current = Number(outpost.defenses?.[key]) || 1;
    const base = kind === 'wall' ? 1200 : kind === 'gate' ? 1500 : 1700;
    return { wood: Math.round(base * Math.pow(1.55, current - 1)), stone: Math.round(base * 1.22 * Math.pow(1.6, current - 1)), coins: Math.round(400 * Math.pow(1.45, current - 1)) };
  }

  function openOutpostDefenses(ctx) {
    const outpost = activeOutpost(ctx);
    if (!outpost) return false;
    const rows = [
      ['wall', '🧱', 'Muralha', 'Protege os flancos do posto.'],
      ['gate', '🚪', 'Portão', 'Protege a entrada principal.'],
      ['tower', '🗼', 'Torres', 'Aumenta a defesa à distância.']
    ].map(([kind, icon, name, description]) => {
      const level = Number(outpost.defenses?.[`${kind}Level`]) || 1;
      return `<div class="castle-fortification-row"><span class="castle-fortification-icon">${icon}</span><div class="castle-fortification-main"><strong>${name} — Nível ${level}</strong><small>${description}</small><div class="costs">${formatCost(defenseUpgradeCost(outpost, kind), ctx)}</div></div><button class="action-btn" data-action="outpost-defense-upgrade" data-id="${ctx.escapeHtml(outpost.id)}" data-kind="${kind}">Melhorar</button></div>`;
    }).join('');
    ctx.openModal(`Defesas — ${ctx.escapeHtml(outpost.name)}`, `<div class="castle-fortification-list">${rows}</div>`, '<button class="secondary-btn" data-action="modal-close">Fechar</button>');
    return true;
  }

  function upgradeOutpostDefense(outpostId, kind, ctx) {
    const profile = ctx.getProfile?.();
    const outpost = profile?.outposts?.list?.find(item => item.id === outpostId);
    if (!outpost || outpost.ownerUserId !== ctx.getCurrentUser?.()?.id) return true;
    const key = `${kind}Level`;
    if (!['wallLevel', 'gateLevel', 'towerLevel'].includes(key)) return true;
    const cost = defenseUpgradeCost(outpost, kind);
    if (!canAfford(profile, cost)) return ctx.toast('Não tens recursos suficientes.', 'error'), true;
    spend(profile, cost);
    outpost.defenses[key] = (Number(outpost.defenses[key]) || 1) + 1;
    profile.reports.push({ id: ctx.uid('r'), at: ctx.now(), title: `Defesas melhoradas no posto`, result: 'info', reportType: 'kingdom', text: `${outpost.name}: ${key.replace('Level', '')} ficou no nível ${outpost.defenses[key]}.` });
    ctx.closeModal();
    ctx.renderGame();
    ctx.persist(true);
    return true;
  }

  function troopPower(troops, troopMeta, mode) {
    return Object.entries(troops || {}).reduce((sum, [type, count]) => sum + (Number(count) || 0) * (Number(troopMeta[type]?.[mode]) || 0), 0);
  }

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

  function reduceTroops(troops, lossRate, random) {
    const result = {};
    Object.entries(troops || {}).forEach(([type, count]) => {
      const safeCount = Math.max(0, Math.floor(Number(count) || 0));
      const lost = Math.min(safeCount, Math.round(safeCount * lossRate * (0.86 + random() * 0.24)));
      result[type] = safeCount - lost;
    });
    return result;
  }

  function startOccupation(outpost, context) {
    const profile = context.getProfile();
    const user = context.getCurrentUser?.();
    const startedAt = context.now();
    outpost.captureStatus = 'occupying';
    outpost.pendingCapture = {
      ownerUserId: user?.id || profile.userId,
      ownerName: user?.username || 'Jogador',
      startedAt,
      endAt: startedAt + CAPTURE_OCCUPATION_MS
    };
    outpost.ownerUserId = null;
    outpost.ownerName = '';
    outpost.capturedAt = 0;
    outpost.buildings = [];
    outpost.constructionQueue = [];
  }

  function resolveCaptureBattle(mission, context) {
    const profile = context.getProfile();
    ensureState(profile, context);
    const outpostId = String(mission.targetId || '').replace(/^outpost:/, '');
    const outpost = profile?.outposts?.list?.find(item => item.id === outpostId);
    if (!outpost) return true;
    const target = makeAttackTarget(profile, outpost.id, context);
    const troopMeta = context.getTroopsMeta?.() || {};
    const random = seededRandom(`${mission.id}:${outpost.id}:capture`);
    const sent = Object.fromEntries(Object.entries(mission.troops || {}).map(([type, count]) => [type, Math.max(0, Math.floor(Number(count) || 0))]));
    const sentTotal = totalTroops(sent);
    const baseAttack = troopPower(sent, troopMeta, 'attack');
    const commanderAttack = globalThis.ReinosEquipmentSystem?.applyCommanderAttackPower?.(profile, mission, baseAttack) || baseAttack;
    const attack = commanderAttack * (0.92 + random() * 0.18);
    const defense = target.strength * (0.92 + random() * 0.18);
    const ratio = attack / Math.max(1, defense);
    const win = ratio >= 1;
    const lossRate = win ? clamp(0.24 / Math.max(ratio, 0.75), 0.07, 0.42) : clamp(0.62 + (1 - ratio) * 0.18, 0.52, 0.94);
    const survivors = reduceTroops(sent, lossRate, random);
    const survivedTotal = totalTroops(survivors);
    const lostTotal = Math.max(0, sentTotal - survivedTotal);
    profile.totalBattles = (Number(profile.totalBattles) || 0) + 1;

    if (win) {
      profile.totalVictories = (Number(profile.totalVictories) || 0) + 1;
      profile.honor = (Number(profile.honor) || 0) + 35 + (Number(outpost.level) || 1) * 10;
      context.gainXp?.(120 + (Number(outpost.level) || 1) * 35);
      startOccupation(outpost, context);
      profile.reports.push({
        id: context.uid('r'),
        at: context.now(),
        title: `Captura iniciada: ${outpost.name}`,
        result: 'win',
        reportType: 'attack',
        text: `Vitória no ataque de captura contra ${outpost.name}. Perdeste ${context.format(lostTotal)} unidade(s). A ocupação começou e ficará concluída em 2 horas.`
      });
      context.finishBattleMission?.(mission, survivors, { wood: 0, stone: 0, food: 0, coins: 0 }, true);
      context.toast?.(`Vitória! ${outpost.name} entrou em ocupação por 2 horas.`, 'success');
    } else {
      profile.honor = Math.max(0, (Number(profile.honor) || 0) - 8);
      context.gainXp?.(30 + (Number(outpost.level) || 1) * 10);
      profile.reports.push({
        id: context.uid('r'),
        at: context.now(),
        title: `Captura falhada: ${outpost.name}`,
        result: 'loss',
        reportType: 'attack',
        text: `Derrota no ataque de captura contra ${outpost.name}. Perdeste ${context.format(lostTotal)} unidade(s). As tropas sobreviventes regressaram ao castelo.`
      });
      context.finishBattleMission?.(mission, survivors, { wood: 0, stone: 0, food: 0, coins: 0 }, false);
      context.toast?.(`A captura de ${outpost.name} falhou.`, 'error');
    }
    return true;
  }

  function completeOccupation(outpost, ctx) {
    const profile = ctx.getProfile?.();
    const pending = outpost.pendingCapture;
    if (!pending) return false;
    outpost.ownerUserId = pending.ownerUserId;
    outpost.ownerName = pending.ownerName || 'Jogador';
    outpost.capturedAt = ctx.now();
    outpost.captureStatus = 'owned';
    outpost.pendingCapture = null;
    outpost.buildings = makeDefaultBuildings(outpost, ctx);
    outpost.constructionQueue = [];
    outpost.expansionLevel = 0;
    outpost.castleState = null;
    outpost.defenses = { wallLevel: 1, gateLevel: 1, towerLevel: 1 };
    ensureOutpostCastleShape(outpost, ctx);
    if (pending.ownerUserId === profile?.userId) {
      profile.reports.push({ id: ctx.uid('r'), at: ctx.now(), title: `Posto avançado conquistado`, result: 'info', reportType: 'kingdom', text: `${outpost.name} ficou totalmente sob o teu controlo. Já podes entrar nele como castelo secundário, construir e evoluir edifícios.` });
      ctx.toast(`${outpost.name} ficou sob o teu controlo!`, 'success');
    }
    return true;
  }

  function process(ctx, time) {
    const profile = ctx.getProfile?.();
    const user = ctx.getCurrentUser?.();
    ensureState(profile, ctx);
    let changed = false;
    for (const outpost of profile?.outposts?.list || []) {
      if (outpost.captureStatus === 'occupying' && outpost.pendingCapture && Number(outpost.pendingCapture.endAt) <= time) {
        if (completeOccupation(outpost, ctx)) changed = true;
      }
    }
    for (const outpost of capturedOutposts(profile, user?.id)) {
      const finished = (outpost.constructionQueue || []).filter(item => item.endAt <= time);
      for (const item of finished) {
        const building = outpost.buildings.find(entry => entry.id === item.buildingId);
        if (!building) continue;
        building.level = item.targetLevel;
        building.status = 'ready';
        const def = OUTPOST_BUILDINGS[building.type] || { name: building.type };
        profile.reports.push({ id: ctx.uid('r'), at: time, title: `Obra concluída no posto`, result: 'info', reportType: 'kingdom', text: `${def.name} está pronto em ${outpost.name}, nível ${building.level}.` });
        ctx.toast(`${def.name} pronto em ${outpost.name}!`, 'success');
        changed = true;
      }
      if (finished.length) outpost.constructionQueue = outpost.constructionQueue.filter(item => item.endAt > time);
    }
    return changed;
  }

  function locationLabel(ctx) {
    const profile = ctx.getProfile?.();
    if (ctx.getActiveView?.() === 'map') return 'Mapa Mundial';
    const outpost = activeOutpost(ctx);
    if (outpost) return outpost.name;
    return 'Castelo Principal';
  }

  function renderLocationSelector(ctx) {
    const label = locationLabel(ctx);
    return `<button class="location-selector" data-action="location-menu-open" title="Selecionar castelo ou mapa"><span>${ctx.escapeHtml(label)}</span><b>▲</b></button>`;
  }

  function setActiveOutpost(outpostId, ctx) {
    const profile = ctx.getProfile?.();
    const outpost = profile?.outposts?.list?.find(item => item.id === outpostId);
    if (!outpost || outpost.ownerUserId !== ctx.getCurrentUser?.()?.id || outpost.captureStatus !== 'owned') return false;
    profile.activeLocationId = outpost.id;
    ctx.navigate?.('castle');
    return true;
  }

  function setActiveMain(ctx) {
    const profile = ctx.getProfile?.();
    if (profile) profile.activeLocationId = 'main';
    ctx.navigate?.('castle');
  }

  function openLocationMenu(ctx) {
    const profile = ctx.getProfile?.();
    const user = ctx.getCurrentUser?.();
    ensureState(profile, ctx);
    const owned = capturedOutposts(profile, user?.id);
    const pending = pendingOutposts(profile, user?.id);
    const outpostRows = owned.map(outpost => `<button class="location-choice" data-action="outpost-select-location" data-id="${ctx.escapeHtml(outpost.id)}"><b>🏯 ${ctx.escapeHtml(outpost.name)}</b><small>${ctx.escapeHtml((TYPES[outpost.type] || TYPES.food_wood).shortLabel)} • Nv. ${ctx.format(outpost.level)} • ${outpostSlotCount(outpost)} parcelas</small></button>`).join('') || '<div class="empty-state">Ainda não tens postos avançados controlados.</div>';
    const pendingRows = pending.map(outpost => `<div class="location-choice disabled"><b>⏳ ${ctx.escapeHtml(outpost.name)}</b><small>Ocupação em curso: <span data-countdown="${outpost.pendingCapture.endAt}">${ctx.formatTime(outpost.pendingCapture.endAt - ctx.now())}</span></small></div>`).join('');
    ctx.openModal('Selecionar local', `<div class="location-choice-list"><button class="location-choice" data-action="location-main"><b>🏰 Castelo Principal</b><small>O teu castelo principal</small></button><button class="location-choice" data-action="location-map"><b>🗺️ Mapa Mundial</b><small>Ver castelos, Barões e postos avançados</small></button>${outpostRows}${pendingRows}</div>`, '<button class="secondary-btn" data-action="modal-close">Fechar</button>');
    return true;
  }


  function outpostCastleProxy(profile, outpost, ctx = {}) {
    ensureOutpostCastleShape(outpost, ctx);
    let proxy = proxyCache.get(outpost);
    if (!proxy) {
      proxy = {};
      proxyCache.set(outpost, proxy);
    }
    Object.assign(proxy, profile || {});
    proxy.__isOutpostCastle = true;
    proxy.__outpostId = outpost.id;
    proxy.kingdomName = outpost.name;
    proxy.buildings = outpost.buildings;
    proxy.constructionQueue = outpost.constructionQueue;
    proxy.castleState = outpost.castleState;
    return proxy;
  }

  function outpostBuildingMeta(ctx) {
    const base = typeof ctx.getBuildingsMeta === 'function' ? ctx.getBuildingsMeta() : {};
    const merged = { ...base };
    for (const [type, def] of Object.entries(OUTPOST_BUILDINGS)) {
      if (type === 'outpost_keep' || type === 'guardhouse') continue;
      merged[type] = { ...(merged[type] || {}), ...def };
    }
    return merged;
  }

  function outpostCastleContext(ctx, outpost) {
    const profile = ctx.getProfile?.();
    const proxy = outpostCastleProxy(profile, outpost, ctx);
    const summary = outpostSummary(outpost, ctx);
    const subtitle = `Castelo secundário • ${summary.type.label} • X ${ctx.format(outpost.x)} • Y ${ctx.format(outpost.y)}`;
    return {
      ...ctx,
      getProfile: () => proxy,
      getBuildingsMeta: () => outpostBuildingMeta(ctx),
      getKeepLevel: () => outpostKeepLevel(outpost),
      buildingCount: type => outpost.buildings.filter(building => building.type === type).length,
      buildingCost: (type, level) => typeof ctx.buildingCost === 'function' ? ctx.buildingCost(type, level) : buildingCost(type, level),
      buildingTime: (type, level) => typeof ctx.buildingTime === 'function' ? ctx.buildingTime(type, level) : buildingTime(type, level),
      constructionQueueLimit: () => queueLimit(ctx),
      canAfford: cost => typeof ctx.canAfford === 'function' ? ctx.canAfford(cost) : canAfford(profile, cost),
      spend: cost => typeof ctx.spend === 'function' ? ctx.spend(cost) : spend(profile, cost),
      startBuilding: (type, slot) => startCastleStyleBuilding(outpost.id, type, slot, ctx),
      demolishBuilding: buildingId => demolishOutpostBuilding(outpost.id, buildingId, ctx),
      viewHeader: (_title, _subtitle, extra = '') => ctx.viewHeader(ctx.escapeHtml(outpost.name), ctx.escapeHtml(subtitle), extra)
    };
  }

  function startCastleStyleBuilding(outpostId, type, slot, ctx) {
    const profile = ctx.getProfile?.();
    const outpost = profile?.outposts?.list?.find(item => item.id === outpostId);
    const meta = outpostBuildingMeta(ctx);
    const def = meta[type] || OUTPOST_BUILDINGS[type];
    if (!outpost || !def || outpost.ownerUserId !== ctx.getCurrentUser?.()?.id || outpost.captureStatus !== 'owned') return true;
    ensureOutpostCastleShape(outpost, ctx);
    const proxy = outpostCastleProxy(profile, outpost, ctx);
    if (outpost.constructionQueue.length >= queueLimit(ctx)) return ctx.toast('Este posto avançado já tem uma obra em curso.', 'error'), true;
    if (type === 'keep') return ctx.toast('A Fortaleza deste posto já existe.', 'error'), true;
    if (!availableBuildingTypes(outpost).includes(type)) return ctx.toast('Este edifício não está disponível neste tipo de posto.', 'error'), true;
    if (globalThis.ReinosCastleSystem?.canPlace && !globalThis.ReinosCastleSystem.canPlace(proxy, type, Number(slot))) {
      return ctx.toast('O edifício não cabe nessa posição ou a parcela ainda está bloqueada.', 'error'), true;
    }
    if ((def.keepRequired || 1) > outpostKeepLevel(outpost)) return ctx.toast(`Necessitas da Fortaleza do posto no nível ${def.keepRequired}.`, 'error'), true;
    if (def.max && outpost.buildings.filter(building => building.type === type).length >= def.max) return ctx.toast('Atingiste o limite deste edifício no posto.', 'error'), true;
    const cost = typeof ctx.buildingCost === 'function' ? ctx.buildingCost(type, 1) : buildingCost(type, 1);
    const affordable = typeof ctx.canAfford === 'function' ? ctx.canAfford(cost) : canAfford(profile, cost);
    if (!affordable) return ctx.toast('Não tens recursos suficientes.', 'error'), true;
    if (typeof ctx.spend === 'function') ctx.spend(cost); else spend(profile, cost);
    const building = { id: ctx.uid('opb'), type, level: 0, slot: Number(slot), status: 'constructing' };
    outpost.buildings.push(building);
    const startedAt = ctx.now();
    const duration = typeof ctx.buildingTime === 'function' ? ctx.buildingTime(type, 1) : buildingTime(type, 1);
    outpost.constructionQueue.push({ id: ctx.uid('opq'), buildingId: building.id, action: 'build', targetLevel: 1, startedAt, endAt: startedAt + duration });
    profile.reports.push({ id: ctx.uid('r'), at: startedAt, title: `Construção iniciada no posto`, result: 'info', reportType: 'kingdom', text: `${def.name} começou a ser construído em ${outpost.name}.` });
    ctx.closeModal?.();
    ctx.renderGame?.();
    ctx.persist?.(true);
    return true;
  }

  function renderCastle(ctx) {
    const outpost = activeOutpost(ctx);
    if (!outpost) return false;
    ensureOutpostCastleShape(outpost, ctx);
    const castleCtx = outpostCastleContext(ctx, outpost);
    if (globalThis.ReinosCastleSystem?.render?.(castleCtx)) return true;
    const { type, parts } = outpostSummary(outpost, ctx);
    const queue = outpost.constructionQueue[0];
    const queueHtml = queue ? `<div class="outpost-queue">Obras em curso: <strong data-countdown="${queue.endAt}">${ctx.formatTime(queue.endAt - ctx.now())}</strong></div>` : '';
    const production = totalProductionHtml(outpost, ctx);
    const subtitle = `Castelo secundário • ${type.label} • X ${ctx.format(outpost.x)} • Y ${ctx.format(outpost.y)}`;
    ctx.getContent().innerHTML = `<div class="view outpost-castle-view">
      ${ctx.viewHeader(ctx.escapeHtml(outpost.name), ctx.escapeHtml(subtitle))}
      <div class="outpost-castle-strip ${type.rarity === 'rare' ? 'rare' : ''}">
        <div><span>${type.rarity === 'rare' ? '✨' : type.icon} Tipo</span><strong>${ctx.escapeHtml(type.label)}</strong></div>
        <div><span>Recursos</span><strong>${parts}</strong></div>
        <div><span>Área</span><strong>${outpostSlotCount(outpost)} parcelas</strong></div>
        <div><span>Defesas</span><strong>🧱 ${outpost.defenses.wallLevel} · 🚪 ${outpost.defenses.gateLevel} · 🗼 ${outpost.defenses.towerLevel}</strong></div>
      </div>
      ${production}
      ${queueHtml}
      <div class="outpost-castle-board"><div class="outpost-grid outpost-castle-grid">${renderSlots(outpost, ctx)}</div></div>
    </div>`;
    return true;
  }

  async function handleAction(action, element, ctx) {
    if (action === 'location-menu-open') return openLocationMenu(ctx);
    if (action === 'location-main') { ctx.closeModal?.(); setActiveMain(ctx); ctx.persist?.(true); return true; }
    if (action === 'location-map') { ctx.closeModal?.(); ctx.navigate?.('map'); ctx.persist?.(true); return true; }
    if (action === 'outpost-select-location') { ctx.closeModal?.(); setActiveOutpost(element.dataset.id, ctx); ctx.persist?.(true); return true; }

    if (isActiveOutpost(ctx)) {
      const outpost = activeOutpost(ctx);
      const castleCtx = outpostCastleContext(ctx, outpost);
      const castleActions = new Set([
        'castle-build-placement-start',
        'castle-expansion-select',
        'castle-expansion-buy',
        'castle-expand-confirm',
        'castle-fortification-upgrade',
        'castle-move-start',
        'castle-move-cancel'
      ]);

      if (action === 'build-first-empty') {
        const slot = globalThis.ReinosCastleSystem?.firstEmptySlot?.(castleCtx.getProfile(), 'decoration') ?? 0;
        return globalThis.ReinosCastleSystem?.openBuildModal?.(slot, castleCtx) || openBuildChoice(outpost.id, firstEmptySlot(outpost), ctx);
      }
      if (action === 'build-slot') {
        const slot = Number(element.dataset.slot || 0);
        return globalThis.ReinosCastleSystem?.openBuildModal?.(slot, castleCtx) || openBuildChoice(outpost.id, slot, ctx);
      }
      if (action === 'building-open') return openBuilding(outpost.id, element.dataset.id, ctx);
      if (action === 'building-upgrade') return upgradeOutpostBuilding(outpost.id, element.dataset.id, ctx);
      if (action === 'building-demolish') return globalThis.ReinosBuildingDemolitionConfirmation?.handleAction?.(action, element, castleCtx) || demolishOutpostBuilding(outpost.id, element.dataset.id, ctx);
      if (action === 'building-demolish-confirm') return globalThis.ReinosBuildingDemolitionConfirmation?.handleAction?.(action, element, castleCtx) || demolishOutpostBuilding(outpost.id, element.dataset.id, ctx);
      if (action === 'castle-expand') return globalThis.ReinosCastleSystem?.handleAction?.(action, element, castleCtx) || openOutpostExpansion(ctx);
      if (action === 'castle-fortification-open') return globalThis.ReinosCastleSystem?.handleAction?.(action, element, castleCtx) || openOutpostDefenses(ctx);
      if (castleActions.has(action)) return globalThis.ReinosCastleSystem?.handleAction?.(action, element, castleCtx) || true;
    }

    if (!String(action || '').startsWith('outpost-')) return false;
    const outpostId = element.dataset.id;
    if (action === 'outpost-open') return openOutpost(outpostId, ctx);
    if (action === 'outpost-capture') return startCaptureAttack(outpostId, ctx);
    if (action === 'outpost-build-slot') return openBuildChoice(outpostId, Number(element.dataset.slot), ctx);
    if (action === 'outpost-build-type') return buildInOutpost(outpostId, Number(element.dataset.slot), element.dataset.type, ctx);
    if (action === 'outpost-building-open') return openBuilding(outpostId, element.dataset.buildingId, ctx);
    if (action === 'outpost-building-upgrade') return upgradeOutpostBuilding(outpostId, element.dataset.buildingId, ctx);
    if (action === 'outpost-building-demolish') return demolishOutpostBuilding(outpostId, element.dataset.buildingId, ctx);
    if (action === 'outpost-expand-confirm') return confirmOutpostExpansion(outpostId, ctx);
    if (action === 'outpost-defense-upgrade') return upgradeOutpostDefense(outpostId, element.dataset.kind, ctx);
    return false;
  }

  globalThis.ReinosOutposts = {
    VERSION,
    MAX_CAPTURED,
    CAPTURE_OCCUPATION_MS,
    TYPES,
    OUTPOST_BUILDINGS,
    ensureState,
    renderMapNodes,
    renderLocationSelector,
    renderCastle,
    applyProduction,
    process,
    handleAction,
    makeAttackTarget,
    generateDefense,
    resolveCaptureBattle,
    activeOutpost,
    isActiveOutpost
  };
})();
