'use strict';

(() => {
  const VERSION = 3;
  const DRAG_THRESHOLD = 7;
  const TILE_W = 78;
  const TILE_H = 40;
  const ORIGIN_X = 3000;
  const ORIGIN_Y = 170;

  let mounted = null;
  let abortController = null;
  let session = null;
  let suppressBuildingClickUntil = 0;
  let highlightedTiles = [];

  function castleSystem() {
    return globalThis.ReinosCastleSystem;
  }

  function isoSystem() {
    return globalThis.ReinosCastleIsometricView;
  }

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

  function worldPointFromClient(clientX, clientY) {
    const world = mounted?.world;
    if (!world) return null;
    const rect = world.getBoundingClientRect();
    const baseWidth = Number(world.dataset.baseWidth) || world.offsetWidth || 6000;
    const scale = rect.width > 0 ? rect.width / baseWidth : 1;
    return {
      x: (clientX - rect.left) / Math.max(0.01, scale),
      y: (clientY - rect.top) / Math.max(0.01, scale)
    };
  }

  function inverseProject(pixelX, pixelY) {
    const tileW = Number(isoSystem()?.TILE_W) || TILE_W;
    const tileH = Number(isoSystem()?.TILE_H) || TILE_H;
    const dx = (pixelX - ORIGIN_X) / (tileW / 2);
    const dy = (pixelY - ORIGIN_Y) / (tileH / 2);
    return {
      x: (dx + dy) / 2,
      y: (dy - dx) / 2
    };
  }

  function project(x, y) {
    return isoSystem()?.project?.(x, y) || {
      x: ORIGIN_X + (x - y) * TILE_W / 2,
      y: ORIGIN_Y + (x + y) * TILE_H / 2
    };
  }

  function metricsFor(type, level = 1) {
    const illustrated = globalThis.ReinosIllustratedBuildings?.metrics?.(type, level);
    if (illustrated) return illustrated;
    if (type === 'keep') return { width: 292, height: 232 };
    if (type === 'watchtower') return { width: 235, height: 225 };
    if (type === 'decoration') return { width: 205, height: 195 };
    return { width: 248, height: 205 };
  }

  function currentElements() {
    const view = document.querySelector('.castle-view');
    const shell = view?.querySelector('.castle-world-shell');
    const viewport = view?.querySelector('#castle-world-viewport');
    const world = view?.querySelector('#castle-isometric-world');
    return { view, shell, viewport, world };
  }

  function clearGridClasses() {
    mounted?.view?.classList.remove('castle-grid-placement-active', 'castle-grid-existing-drag', 'castle-grid-build-mode');
    mounted?.world?.querySelectorAll('.castle-ground-tile').forEach(tile => {
      tile.classList.remove('castle-cell-occupied', 'castle-placement-valid', 'castle-placement-invalid', 'castle-placement-footprint');
    });
    highlightedTiles = [];
  }

  function removePlacementUi() {
    mounted?.shell?.querySelectorAll('.castle-placement-toolbar,.castle-placement-help').forEach(element => element.remove());
  }

  function removeGhost() {
    session?.ghost?.remove();
    if (session?.sourceElement) session.sourceElement.classList.remove('castle-building-drag-source');
  }

  function resetSession() {
    removeGhost();
    removePlacementUi();
    clearGridClasses();
    session = null;
  }

  function occupiedCells(ignoreId = null) {
    const cells = new Set();
    const system = castleSystem();
    for (const building of mounted?.profile?.buildings || []) {
      if (ignoreId && building.id === ignoreId) continue;
      const position = system.slotToPosition(building.slot);
      const size = system.sizeFor(building.type);
      for (let y = position.y; y < position.y + size.h; y++) {
        for (let x = position.x; x < position.x + size.w; x++) cells.add(`${x}:${y}`);
      }
    }
    return cells;
  }

  function markOccupied(ignoreId = null) {
    const occupied = occupiedCells(ignoreId);
    mounted?.world?.querySelectorAll('.castle-ground-tile').forEach(tile => {
      const x = Number(tile.dataset.gridX);
      const y = Number(tile.dataset.gridY);
      tile.classList.toggle('castle-cell-occupied', occupied.has(`${x}:${y}`));
    });
  }

  function footprintCells(slot, type) {
    const system = castleSystem();
    const position = system.slotToPosition(slot);
    const size = system.sizeFor(type);
    const result = [];
    for (let y = position.y; y < position.y + size.h; y++) {
      for (let x = position.x; x < position.x + size.w; x++) result.push({ x, y });
    }
    return result;
  }

  function highlightFootprint(slot, valid, type) {
    highlightedTiles.forEach(tile => tile.classList.remove('castle-placement-valid', 'castle-placement-invalid', 'castle-placement-footprint'));
    highlightedTiles = [];
    for (const cell of footprintCells(slot, type)) {
      const tile = mounted?.tileMap?.get(`${cell.x}:${cell.y}`);
      if (!tile) continue;
      tile.classList.add('castle-placement-footprint', valid ? 'castle-placement-valid' : 'castle-placement-invalid');
      highlightedTiles.push(tile);
    }
  }

  function setGhostPosition(slot) {
    if (!session?.ghost) return;
    const system = castleSystem();
    const position = system.slotToPosition(slot);
    const size = system.sizeFor(session.type);
    const centerX = position.x + (size.w - 1) / 2;
    const centerY = position.y + (size.h - 1) / 2;
    const point = project(centerX, centerY);
    const width = session.width;
    const height = session.height;
    session.ghost.style.left = `${point.x - width / 2}px`;
    session.ghost.style.top = `${point.y - height + TILE_H / 2}px`;
    session.ghost.style.zIndex = String(2600 + Math.round((centerX + centerY) * 12));
    session.ghost.dataset.slot = String(slot);
  }

  function updateCandidateFromClient(clientX, clientY) {
    if (!session || !mounted?.world) return;
    const point = worldPointFromClient(clientX, clientY);
    if (!point) return;
    const size = castleSystem().sizeFor(session.type);
    const centerPixelX = point.x - (session.grabOffsetX || 0);
    const centerPixelY = point.y - (session.grabOffsetY || 0);
    const gridCenter = inverseProject(centerPixelX, centerPixelY);
    const gridSize = Number(castleSystem().GRID_SIZE) || 60;
    const x = clamp(Math.round(gridCenter.x - (size.w - 1) / 2), 0, Math.max(0, gridSize - size.w));
    const y = clamp(Math.round(gridCenter.y - (size.h - 1) / 2), 0, Math.max(0, gridSize - size.h));
    const slot = castleSystem().positionToSlot(x, y);
    const valid = castleSystem().canPlace(mounted.profile, session.type, slot, session.ignoreId || null);
    session.candidateSlot = slot;
    session.valid = valid;
    if (session.mode === 'build-drag') session.placed = true;
    session.ghost.classList.toggle('is-valid-placement', Boolean(valid && (session.mode !== 'build-drag' || session.placed)));
    session.ghost.classList.toggle('is-invalid-placement', !valid || (session.mode === 'build-drag' && !session.placed));
    setGhostPosition(slot);
    highlightFootprint(slot, valid, session.type);
    updateToolbarState();
  }

  function makeToolbar({ buildMode = false } = {}) {
    removePlacementUi();
    const shell = mounted?.shell;
    if (!shell) return;
    const helper = document.createElement('div');
    helper.className = 'castle-placement-help';
    helper.innerHTML = buildMode
      ? '<strong>Colocação do edifício</strong><span>Arrasta-o para um quadrado livre e confirma só depois. Verde significa posição válida; vermelho significa ocupado.</span>'
      : '<strong>Mover edifício</strong><span>Podes mover mesmo durante melhorias. Arrasta e larga-o nos quadrados verdes.</span>';
    shell.appendChild(helper);

    if (!buildMode) return;
    const toolbar = document.createElement('div');
    toolbar.className = 'castle-placement-toolbar';
    toolbar.innerHTML = `
      <button type="button" class="castle-placement-cancel" data-castle-placement="cancel" title="Cancelar">✕</button>
      <div><strong data-castle-placement-name></strong><span data-castle-placement-size></span></div>
      <button type="button" class="castle-placement-confirm" data-castle-placement="confirm" title="Construir aqui">✓</button>`;
    shell.appendChild(toolbar);
  }

  function updateToolbarState() {
    const toolbar = mounted?.shell?.querySelector('.castle-placement-toolbar');
    if (!toolbar || !session) return;
    const confirm = toolbar.querySelector('[data-castle-placement="confirm"]');
    if (confirm) confirm.disabled = !(session.valid && (session.mode?.startsWith('existing') || session.placed));
    const name = toolbar.querySelector('[data-castle-placement-name]');
    const size = toolbar.querySelector('[data-castle-placement-size]');
    const meta = mounted.ctx.getBuildingsMeta()[session.type];
    if (name) name.textContent = meta?.name || session.type;
    if (size) {
      const dimensions = castleSystem().sizeFor(session.type);
      size.textContent = session.placed ? `${dimensions.w} × ${dimensions.h} quadrados` : `${dimensions.w} × ${dimensions.h} quadrados · escolhe posição`;
    }
  }

  function cloneExistingBuilding(element) {
    const ghost = element.cloneNode(true);
    ghost.removeAttribute('data-action');
    ghost.removeAttribute('data-id');
    ghost.removeAttribute('title');
    ghost.setAttribute('aria-hidden', 'true');
    ghost.classList.add('castle-building-drag-ghost');
    ghost.querySelectorAll('[data-countdown]').forEach(node => node.removeAttribute('data-countdown'));
    return ghost;
  }

  function createBuildGhost(type) {
    const meta = mounted.ctx.getBuildingsMeta()[type] || { name: type };
    const metrics = metricsFor(type, 1);
    const size = castleSystem().sizeFor(type);
    const ghost = document.createElement('button');
    ghost.type = 'button';
    ghost.className = `castle-building castle-iso-building castle-building-${type} castle-building-tier-1 has-illustrated-building-art castle-building-drag-ghost castle-building-new-ghost`;
    ghost.setAttribute('aria-label', `Colocar ${meta.name}`);
    ghost.style.width = `${metrics.width}px`;
    ghost.style.height = `${metrics.height}px`;
    ghost.style.setProperty('--foot-w', `${(size.w + size.h) * TILE_W / 2}px`);
    ghost.style.setProperty('--foot-h', `${(size.w + size.h) * TILE_H / 2}px`);
    ghost.innerHTML = `
      <span class="castle-iso-building-shadow"></span>
      <span class="castle-iso-building-art"><img class="illustrated-building-image illustrated-building-image-isometric" src="${globalThis.ReinosIllustratedBuildings?.assetFor?.(type, 1) || ('../assets/buildings/levels/' + type + '/level_01.png')}" alt="" draggable="false" /></span>
      <span class="castle-building-level-badge">1</span>
      <span class="castle-building-label"><strong>${String(meta.name).replace(/[&<>'"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[c]))}</strong><em>Nv. 1</em></span>`;
    return ghost;
  }

  function activateExistingDrag(pending, event) {
    const building = pending.building;
    const system = castleSystem();
    const size = system.sizeFor(building.type);
    const position = system.slotToPosition(building.slot);
    const center = project(position.x + (size.w - 1) / 2, position.y + (size.h - 1) / 2);
    const pointer = worldPointFromClient(event.clientX, event.clientY);
    const ghost = cloneExistingBuilding(pending.sourceElement);
    mounted.world.appendChild(ghost);
    pending.sourceElement.classList.add('castle-building-drag-source');

    session = {
      mode: 'existing-drag',
      pointerId: pending.pointerId,
      building,
      type: building.type,
      ignoreId: building.id,
      sourceElement: pending.sourceElement,
      ghost,
      width: Number.parseFloat(pending.sourceElement.style.width) || pending.sourceElement.offsetWidth,
      height: Number.parseFloat(pending.sourceElement.style.height) || pending.sourceElement.offsetHeight,
      originalSlot: building.slot,
      candidateSlot: building.slot,
      valid: true,
      grabOffsetX: pointer ? pointer.x - center.x : 0,
      grabOffsetY: pointer ? pointer.y - center.y : 0
    };

    mounted.view.classList.add('castle-grid-placement-active', 'castle-grid-existing-drag');
    markOccupied(building.id);
    makeToolbar({ buildMode: false });
    setGhostPosition(building.slot);
    updateCandidateFromClient(event.clientX, event.clientY);
  }

  function applyExistingMove(active) {
    if (!active || !mounted?.profile) return false;
    const targetSlot = Math.max(0, Math.floor(Number(active.candidateSlot) || 0));
    const buildingId = String(active.building?.id || '');
    const building = (mounted.profile.buildings || []).find(item => String(item?.id) === buildingId);
    if (!building) return false;
    building.slot = targetSlot;
    if (active.building && active.building !== building) active.building.slot = targetSlot;
    return true;
  }

  function lockMovedSlotAfterServerSync(ctx, profile, buildingId, targetSlot) {
    const safeId = String(buildingId || '');
    const safeSlot = Math.max(0, Math.floor(Number(targetSlot) || 0));
    Promise.resolve(ctx?.persist?.(true)).then(() => {
      const liveProfile = ctx?.getProfile?.() || profile;
      const liveBuilding = (liveProfile?.buildings || []).find(item => String(item?.id) === safeId);
      if (liveBuilding && Number(liveBuilding.slot) !== safeSlot) {
        liveBuilding.slot = safeSlot;
        ctx?.persist?.(false);
        ctx?.renderActiveView?.();
      }
    }).catch(error => {
      ctx?.toast?.(`Não foi possível guardar a nova posição: ${error?.message || error}`, 'error');
    });
  }

  function finishExistingDrag(event, cancelled = false) {
    if (!session || session.mode !== 'existing-drag') return;
    const active = session;
    const ctx = mounted?.ctx;
    const profile = mounted?.profile;
    const captureElement = active.sourceElement || active.ghost;
    suppressBuildingClickUntil = Date.now() + 450;
    if (!cancelled && active.valid) {
      const targetSlot = Math.max(0, Math.floor(Number(active.candidateSlot) || 0));
      const changed = targetSlot !== Math.max(0, Math.floor(Number(active.originalSlot) || 0));
      const moved = applyExistingMove({ ...active, candidateSlot: targetSlot });
      resetSession();
      if (!moved) {
        ctx?.toast?.('Não foi possível encontrar esse edifício para mover.', 'error');
      } else {
        ctx?.renderActiveView?.();
        ctx?.toast?.(changed ? 'Edifício reposicionado.' : 'O edifício manteve a posição.', changed ? 'success' : 'info');
        lockMovedSlotAfterServerSync(ctx, profile, active.building?.id, targetSlot);
      }
    } else {
      resetSession();
      ctx?.toast?.(cancelled ? 'Movimento cancelado.' : 'Essa posição está ocupada ou fora da muralha.', cancelled ? 'info' : 'error');
    }
    try { captureElement?.releasePointerCapture?.(event?.pointerId); } catch (_) {}
  }

  function beginExistingPointer(event, element, building) {
    if (session || event.button !== 0 || !building) return;
    session = {
      mode: 'existing-pending',
      pointerId: event.pointerId,
      startX: event.clientX,
      startY: event.clientY,
      sourceElement: element,
      building
    };
    try { element.setPointerCapture?.(event.pointerId); } catch (_) {}
  }

  function beginBuild(type, profile, ctx) {
    // v0.1.135 — a abertura do modal/refresh do castelo podia deixar o sistema de
    // colocação com uma referência antiga do perfil ou sem montagem ativa. Antes
    // de iniciar a construção, remonta a grelha se necessário em vez de falhar.
    if (!profile || !ctx) return false;
    const needsMount = !mounted
      || mounted.profile !== profile
      || !mounted.world
      || !mounted.view
      || !mounted.world.isConnected
      || !mounted.view.isConnected;
    if (needsMount && !mount({ profile, ctx })) return false;
    mounted.profile = profile;
    mounted.ctx = ctx;
    resetSession();
    const firstSlot = castleSystem().firstEmptySlot(profile, type);
    if (firstSlot < 0) return false;
    const ghost = createBuildGhost(type);
    mounted.world.appendChild(ghost);
    const metrics = metricsFor(type, 1);
    session = {
      mode: 'build-place',
      type,
      ignoreId: null,
      ghost,
      width: metrics.width,
      height: metrics.height,
      candidateSlot: firstSlot,
      valid: castleSystem().canPlace(profile, type, firstSlot),
      placed: false,
      grabOffsetX: 0,
      grabOffsetY: 0
    };
    mounted.view.classList.add('castle-grid-placement-active', 'castle-grid-build-mode');
    markOccupied();
    makeToolbar({ buildMode: true });
    setGhostPosition(firstSlot);
    highlightFootprint(firstSlot, session.valid, type);
    session.ghost.classList.toggle('is-valid-placement', false);
    session.ghost.classList.toggle('is-invalid-placement', false);
    updateToolbarState();
    ctx.toast('Arrasta o edifício para uma posição livre. O visto verde só ativa depois de escolheres o local.', 'info');
    return true;
  }

  function beginBuildGhostDrag(event) {
    if (!session || session.mode !== 'build-place' || event.button !== 0) return;
    const position = castleSystem().slotToPosition(session.candidateSlot);
    const size = castleSystem().sizeFor(session.type);
    const center = project(position.x + (size.w - 1) / 2, position.y + (size.h - 1) / 2);
    const pointer = worldPointFromClient(event.clientX, event.clientY);
    session.mode = 'build-drag';
    session.pointerId = event.pointerId;
    session.grabOffsetX = pointer ? pointer.x - center.x : 0;
    session.grabOffsetY = pointer ? pointer.y - center.y : 0;
    try { session.ghost.setPointerCapture?.(event.pointerId); } catch (_) {}
    event.preventDefault();
  }

  function finishBuildGhostDrag(event) {
    if (!session || session.mode !== 'build-drag') return;
    session.mode = 'build-place';
    try { session.ghost.releasePointerCapture?.(event.pointerId); } catch (_) {}
    event.preventDefault();
  }

  function confirmBuild() {
    if (!session || !['build-place', 'build-drag'].includes(session.mode)) return;
    if (!session.placed || !session.valid) {
      mounted.ctx.toast('Arrasta o edifício para uma posição verde antes de construir.', 'error');
      return;
    }
    const { type, candidateSlot } = session;
    resetSession();
    mounted.ctx.startBuilding(type, candidateSlot);
  }

  function cancelBuild() {
    if (!session || !['build-place', 'build-drag'].includes(session.mode)) return;
    resetSession();
    mounted.ctx.toast('Colocação cancelada.', 'info');
  }

  function armExisting(buildingId, profile, ctx) {
    if (!mounted || mounted.profile !== profile) return false;
    const element = mounted.world?.querySelector(`.castle-iso-building[data-id="${CSS.escape(String(buildingId))}"]`);
    const building = profile.buildings.find(item => item.id === buildingId);
    if (!element || !building) return false;
    element.classList.add('castle-building-awaiting-drag');
    ctx.toast('Seleciona o edifício e arrasta-o para a nova posição.', 'info');
    setTimeout(() => element.classList.remove('castle-building-awaiting-drag'), 2400);
    return true;
  }

  function onPointerDown(event) {
    if (!mounted?.view?.isConnected) return;
    const toolbarButton = event.target.closest?.('[data-castle-placement]');
    if (toolbarButton) return;

    if (session && ['build-place', 'build-drag'].includes(session.mode)) {
      if (event.target.closest?.('.castle-building-new-ghost')) beginBuildGhostDrag(event);
      return;
    }

    const element = event.target.closest?.('.castle-iso-building[data-id]');
    if (!element || !mounted.world.contains(element)) return;
    const building = mounted.profile.buildings.find(item => item.id === element.dataset.id);
    beginExistingPointer(event, element, building);
  }

  function onPointerMove(event) {
    if (!session) return;
    if (session.mode === 'existing-pending') {
      if (event.pointerId !== session.pointerId) return;
      if (Math.hypot(event.clientX - session.startX, event.clientY - session.startY) < DRAG_THRESHOLD) return;
      const pending = session;
      session = null;
      activateExistingDrag(pending, event);
      event.preventDefault();
      return;
    }
    if (session.mode === 'existing-drag' && event.pointerId === session.pointerId) {
      updateCandidateFromClient(event.clientX, event.clientY);
      event.preventDefault();
      return;
    }
    if (session.mode === 'build-drag' && event.pointerId === session.pointerId) {
      updateCandidateFromClient(event.clientX, event.clientY);
      event.preventDefault();
    }
  }

  function onPointerUp(event) {
    if (!session) return;
    if (session.mode === 'existing-pending' && event.pointerId === session.pointerId) {
      session = null;
      return;
    }
    if (session.mode === 'existing-drag' && event.pointerId === session.pointerId) {
      finishExistingDrag(event, false);
      event.preventDefault();
      event.stopImmediatePropagation();
      return;
    }
    if (session.mode === 'build-drag' && event.pointerId === session.pointerId) finishBuildGhostDrag(event);
  }

  function onPointerCancel(event) {
    if (!session) return;
    if (session.mode === 'existing-pending') session = null;
    else if (session.mode === 'existing-drag') finishExistingDrag(event, true);
    else if (session.mode === 'build-drag') finishBuildGhostDrag(event);
  }

  function onClickCapture(event) {
    if (session && event.target.closest?.('[data-action="nav"], [data-action="logout"]')) resetSession();
    const control = event.target.closest?.('[data-castle-placement]');
    if (control) {
      event.preventDefault();
      event.stopImmediatePropagation();
      if (control.dataset.castlePlacement === 'confirm') confirmBuild();
      if (control.dataset.castlePlacement === 'cancel') cancelBuild();
      return;
    }

    if (Date.now() < suppressBuildingClickUntil && event.target.closest?.('.castle-building')) {
      event.preventDefault();
      event.stopImmediatePropagation();
      return;
    }

    if (session && ['build-place', 'build-drag'].includes(session.mode)) {
      const tile = event.target.closest?.('.castle-ground-tile');
      if (tile) {
        const slot = Number(tile.dataset.slot);
        session.candidateSlot = slot;
        session.valid = castleSystem().canPlace(mounted.profile, session.type, slot);
        session.placed = true;
        session.ghost.classList.toggle('is-valid-placement', session.valid);
        session.ghost.classList.toggle('is-invalid-placement', !session.valid);
        setGhostPosition(slot);
        highlightFootprint(slot, session.valid, session.type);
        updateToolbarState();
        event.preventDefault();
        event.stopImmediatePropagation();
      }
    }
  }

  function onKeyDown(event) {
    if (event.key !== 'Escape' || !session) return;
    if (session.mode === 'existing-pending') session = null;
    else if (session.mode === 'existing-drag') finishExistingDrag(event, true);
    else cancelBuild();
  }

  function mount({ profile, ctx }) {
    abortController?.abort();
    resetSession();
    const elements = currentElements();
    if (!profile || !ctx || !elements.view || !elements.world || !elements.viewport) {
      mounted = null;
      return false;
    }
    const tileMap = new Map();
    elements.world.querySelectorAll('.castle-ground-tile').forEach(tile => tileMap.set(`${tile.dataset.gridX}:${tile.dataset.gridY}`, tile));
    mounted = { profile, ctx, ...elements, tileMap };
    abortController = new AbortController();
    const options = { capture: true, signal: abortController.signal };
    document.addEventListener('pointerdown', onPointerDown, options);
    document.addEventListener('pointermove', onPointerMove, options);
    document.addEventListener('pointerup', onPointerUp, options);
    document.addEventListener('pointercancel', onPointerCancel, options);
    document.addEventListener('click', onClickCapture, options);
    document.addEventListener('keydown', onKeyDown, { signal: abortController.signal });
    return true;
  }

  globalThis.ReinosCastleGridPlacement = {
    VERSION,
    mount,
    beginBuild,
    armExisting,
    cancel: resetSession
  };
})();
