import { registerSceneConfig } from './scene.js';
import { registerTokenConfig } from './token.js';
import { registerTileConfig } from './tile.js';
import { registerHUDConfig } from './hud.js';
//import { registerOcclusionConfig } from './occlusion.js';
import { registerOcclusionLayerConfig } from './occlusion3.js';
import { registerOcclusionLayerConfig2ForUpload } from './occlusion2ForUpload.js';
import { registerSortingConfig } from './autosorting.js';
import { registerDynamicTileConfig, increaseTilesOpacity, decreaseTilesOpacity } from './dynamictile.js';
import { applyIsometricPerspective, applyBackgroundTransformation } from './transform.js';
import { ISOMETRIC_CONST } from './consts.js';
import { isoToCartesian, cartesianToIso } from './utils.js';


// ---------- CONSTANTS ----------
const MODULE_ID = "isometric-perspective";
let DEBUG_PRINT;
let WORLD_ISO_FLAG;

export { MODULE_ID };
export { DEBUG_PRINT };
export { WORLD_ISO_FLAG };


// Hook to register module configuration in Foundry VTT
Hooks.once("init", function() {
  
  // ------------- Registra as configurações do módulo ------------- 
  // Checkbox configuration to enable or disable isometric mode globally
  game.settings.register(MODULE_ID, "worldIsometricFlag", {
    name: game.i18n.localize('isometric-perspective.settings_main_name'), //name: "Enable Isometric Perspective",
    hint: game.i18n.localize('isometric-perspective.settings_main_hint'), //hint: "Toggle whether the isometric perspective is applied to the canvas.",
    scope: "world",  // "world" = sync to db, "client" = local storage
    config: true,    // false if you dont want it to show in module config
    type: Boolean,   // You want the primitive class, e.g. Number, not the name of the class as a string
    default: true, 
    requiresReload: true // true if you want to prompt the user to reload
    //onChange: settings => window.location.reload() // recarrega automaticamente
  });

  game.settings.register(MODULE_ID, 'enableHeightAdjustment', {
    name: game.i18n.localize('isometric-perspective.settings_height_name'), //name: 'Enable Height Adjustment',
    hint: game.i18n.localize('isometric-perspective.settings_height_hint'), //hint: 'Toggle whether token sprites adjust their position to reflect their elevation',
    scope: 'client',
    config: true,
    default: false,
    type: Boolean,
    requiresReload: true
  });

  game.settings.register(MODULE_ID, 'enableTokenVisuals', {
    name: game.i18n.localize('isometric-perspective.settings_visuals_name'), //name: 'Enable Token Visuals',
    hint: game.i18n.localize('isometric-perspective.settings_visuals_hint'), //hint: 'Displays a circular shadow and a vertical red line to indicate token elevation. Requires "Enable Height Adjustment" to be active.',
    scope: 'client',
    config: true,
    default: false,
    type: Boolean,
    requiresReload: true
  });

  game.settings.register(MODULE_ID, 'enableOcclusionDynamicTile', {
    name: game.i18n.localize('isometric-perspective.settings_dynamic_tile_name'), //name: 'Enable Occlusion: Dynamic Tile',
    hint: game.i18n.localize('isometric-perspective.settings_dynamic_tile_hint'), //hint: '(BETA FEATURE. USE WITH CAUTION) Adjusts the visibility of tiles dynamically with the positioning of tokens. See how this feature works here.',
    scope: 'world',
    config: true,
    default: false,
    type: Boolean,
    requiresReload: true
  });

  game.settings.register(MODULE_ID, 'enableAutoSorting', {
    name: game.i18n.localize('isometric-perspective.settings_token_sort_name'), //name: 'Enable Automatic Token Sorting',
    hint: game.i18n.localize('isometric-perspective.settings_token_sort_hint'), //hint: '(BETA FEATURE. USE WITH CAUTION) Automatically adjusts the token\'s sort property value when moving it around the canvas.',
    scope: 'world',
    config: true,
    default: false,
    type: Boolean,
    requiresReload: true
  });

  /*
  game.settings.register(MODULE_ID, 'enableOcclusionTokenSilhouette', {
    name: game.i18n.localize('isometric-perspective.settings_token_silhouette_name'), //name: 'Enable Occlusion: Token Silhouette',
    hint: game.i18n.localize('isometric-perspective.settings_token_silhouette_hint'), //hint: 'Adjusts the visibility of tiles dynamically with the positioning of tokens. See how this feature works here.',
    scope: 'client',
    config: true,
    default: false,
    type: Boolean,
    requiresReload: true
  });
  */
  
  game.settings.register(MODULE_ID, 'debug', {
    name: game.i18n.localize('isometric-perspective.settings_debug_name'), //name: 'Enable Debug Mode',
    hint: game.i18n.localize('isometric-perspective.settings_debug_hint'), //hint: 'Enables debug prints.',
    scope: 'client',
    config: true,
    default: false,
    type: Boolean,
    requiresReload: true
    //onChange: settings => window.location.reload()
  });









  // ------------- Registra os atalhos do módulo ------------- 
  
  game.keybindings.register(MODULE_ID, 'increaseTilesOpacity', {
    name: game.i18n.localize('isometric-perspective.keybindings_increase_tile_opacity'), //name: 'Increase Tile Opacity',
    hint: game.i18n.localize('isometric-perspective.keybindings_increase_tile_opacity_hint'), //hint: 'Increases the opacity of always visible tiles.',
    editable: [
        { key: 'NumpadAdd', modifiers: ['Control'] }
    ],
    onDown: () => {
        increaseTilesOpacity();
    },
    restricted: false,
    reservedModifiers: [],
    precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL
  });

  game.keybindings.register(MODULE_ID, 'decreaseTilesOpacity', {
    name: game.i18n.localize('isometric-perspective.keybindings_decrease_tile_opacity'), //name: 'Decrease Tile Opacity',
    hint: game.i18n.localize('isometric-perspective.keybindings_decrease_tile_opacity_hint'), //hint: 'Decreases the opacity of always visible tiles.',
    editable: [
        { key: 'NumpadSubtract', modifiers: ['Control'] }
    ],
    onDown: () => {
        decreaseTilesOpacity();
    },
    restricted: false,
    reservedModifiers: [],
    precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL
  });

  




  // ------------- Executa os hooks essenciais do módulo -------------
  registerSceneConfig();
  registerTokenConfig();
  registerTileConfig();
  registerHUDConfig();

  // ------------- Executa os hooks de funcionalidades adicionais do módulo -------------
  registerDynamicTileConfig();
  registerSortingConfig();
  //registerOcclusionLayerConfig();
  //registerOcclusionLayerConfig2ForUpload();
  //registerOcclusionConfig();
  

  
  
  
  
  
  // Define global debug print variable
  if (game.settings.get(MODULE_ID, "debug"))
    DEBUG_PRINT = true;
  else DEBUG_PRINT = false;

  if (game.settings.get(MODULE_ID, "worldIsometricFlag"))
    WORLD_ISO_FLAG = true;
  else WORLD_ISO_FLAG = false;
  
});















/**
 * @param {----- TESTING AREA / ÁREA DE TESTES -----}
*/


/*
// Verifique se o módulo já foi inicializado
Hooks.once('init', async function() {
  console.log("Occlusion Module iniciado!");

  // Registra o filtro do shader
  registerOcclusionShader();
  
  // Garante que as tiles de oclusão sejam desenhadas corretamente
  Hooks.on('renderCanvas', renderCanvasHandler);
  
  // Aplica a oclusão aos tokens quando a cena for atualizada
  Hooks.on('updateScene', (scene) => {
      applyOcclusionToTokens(scene);
  });
});

// Código do Shader GLSL como variável
const occlusionShaderCode = `
  varying vec2 vTextureCoord;
  uniform sampler2D uSampler;
  uniform sampler2D maskTexture;
  uniform float x;
  uniform float y;
  uniform bool enable_outline;
  uniform vec4 outlineColor;

  void main(void) {
      vec4 color = texture2D(uSampler, vTextureCoord);
      vec4 mask = texture2D(maskTexture, vec2(x, y));

      // Modifica a transparência com base na máscara de oclusão
      color.a *= mask.a;

      // Aplica uma borda (outline) opcional
      if (enable_outline) {
          color.rgb = mix(color.rgb, outlineColor.rgb, 0.5);
      }

      gl_FragColor = color;
  }
`;

// Função para registrar o shader de oclusão
function registerOcclusionShader() {
  // Criação do filtro com o shader GLSL
  const occlusionFilter = new PIXI.Filter(null, occlusionShaderCode, {
      uSampler: null,
      maskTexture: null,
      x: 0,
      y: 0,
      enable_outline: true,
      outlineColor: new PIXI.Color(0, 0, 0)
  });

  // Armazena o filtro globalmente para ser utilizado mais tarde
  globalThis.occlusionFilter = occlusionFilter;
}

// Função chamada para renderizar o canvas e aplicar a oclusão
function renderCanvasHandler() {
  const scene = canvas.scene;
  if (!scene) return;

  // Cria uma renderTexture para armazenar a máscara de oclusão
  let renderTexture = PIXI.RenderTexture.create({
      width: canvas.dimensions.width,
      height: canvas.dimensions.height
  });

  let container = new PIXI.Container();
  container.sortableChildren = true;

  // Adiciona as tiles de oclusão (se marcadas como tal) no container
  scene.tiles.forEach(tile => {
    let sprite = createOccluderSprite(tile);
    container.addChild(sprite);
  });

  // Renderiza a máscara de oclusão para a renderTexture
  canvas.app.renderer.render(container, { renderTexture });

  // Aplica a oclusão aos tokens com base na máscara gerada
  applyOcclusionToTokens(scene, renderTexture);
}

// Cria um sprite de oclusão a partir de uma tile
function createOccluderSprite(tile) {
  let sprite = PIXI.Sprite.from(tile.texture);
  sprite.position.set(tile.x, tile.y);
  sprite.anchor.set(0.5, 0.5);
  sprite.scale.set(tile.scale.x, tile.scale.y);
  sprite.rotation = tile.rotation;

  return sprite;
}

// Aplica o filtro de oclusão aos tokens na cena
function applyOcclusionToTokens(scene, renderTexture) {
  // Percorre todos os tokens e aplica o filtro de oclusão
  canvas.tokens.objects.children.forEach(token => {
      if (!token.visible) return;

      console.log(`Aplicando o filtro de oclusão para o token: ${token.name}`);
      
      // Cria um filtro de oclusão para cada token
      const filter = globalThis.occlusionFilter;
      filter.maskTexture = renderTexture;
      
      // Ajusta as coordenadas do filtro para cada token
      filter.x = token.x / canvas.dimensions.width;
      filter.y = token.y / canvas.dimensions.height;

      // Verifica as coordenadas
      console.log(`Coordenadas do token: x = ${token.x}, y = ${token.y}`);

      // Aplica o filtro de oclusão ao token
      token.mesh.filters = [filter];
      
      // Verifica se o filtro foi realmente aplicado
      console.log(`Filtros aplicados ao token ${token.name}:`, token.mesh.filters);
  });
}


// Hook para limpar os filtros quando a cena é carregada
Hooks.on('sceneReady', () => {
  canvas.tokens.objects.children.forEach(token => {
      token.mesh.filters = [];
  });
});*/













































/*
// Função para criar a camada de oclusão
function createOcclusionContainer(canvas) {
  const existingContainer = canvas.stage.getChildByName('OcclusionContainer');
  if (existingContainer) {
    canvas.stage.removeChild(existingContainer);
  }

  const occlusionContainer = new PIXI.Container();
  occlusionContainer.name = 'OcclusionContainer';
  occlusionContainer.zIndex = 1000; // Garante que esteja por cima de outras camadas
  canvas.stage.addChild(occlusionContainer);
  
  return occlusionContainer;
}

// Função para aplicar filtro azul em uma textura
function applyBlueFilter(sprite) {
  const blueColorMatrix = new PIXI.ColorMatrixFilter();
  blueColorMatrix.alpha = 0.75;
  blueColorMatrix.matrix = [
      0, 0, 0, 0, 0,
      0, 0, 0, 0, 0,
      1, 1, 1, 0, 0,
      0, 0, 0, 1, 0
  ];
  sprite.filters = [blueColorMatrix];
}

// Função para adicionar tile à camada de oclusão
function addTileToOcclusionLayer(tile, occlusionContainer) {
  const tileSprite = new PIXI.Sprite(tile.mesh.texture);
  
  // Copia exatamente as propriedades do tile original
  tileSprite.position.set(tile.mesh.position.x, tile.mesh.position.y);
  tileSprite.anchor.set(tile.mesh.anchor.x, tile.mesh.anchor.y);
  tileSprite.width = tile.width;
  tileSprite.height = tile.height;
  tileSprite.angle = tile.mesh.angle;
  tileSprite.scale.set(tile.mesh.scale.x, tile.mesh.scale.y);

  // Identificador para o tile na camada
  tileSprite.name = `occlusion-tile-${tile.id}`;


  





  // Encontra tokens sobrepostos
  const overlappingTokens = canvas.tokens.placeables.filter(token => 
    isOverlapping(tile.bounds, token.bounds)
  );
  
  // Cria uma máscara composta dos tokens sobrepostos
  if (overlappingTokens.length > 0) {
    const maskGraphics = new PIXI.Graphics();
    maskGraphics.beginFill(0xffffff);
    
    // Desenha a área total do tile
    maskGraphics.drawRect(0, 0, tileSprite.width, tileSprite.height);
    
    // Subtrai as áreas dos tokens sobrepostos
    overlappingTokens.forEach(token => {
      // Calcula a posição relativa do token em relação ao tile
      const localTokenBounds = {
        left: token.bounds.left - tile.bounds.left,
        top: token.bounds.top - tile.bounds.top,
        width: token.bounds.width,
        height: token.bounds.height
      };
      
      // Cria um "buraco" na máscara para cada token
      maskGraphics.beginHole();
      maskGraphics.drawRect(
        localTokenBounds.left, 
        localTokenBounds.top, 
        localTokenBounds.width, 
        localTokenBounds.height
      );
      maskGraphics.endHole();
    });
    
    maskGraphics.endFill();
    
    // Aplica a máscara invertida
    tileSprite.mask = maskGraphics;
    occlusionContainer.addChild(maskGraphics);
  }
  
  
  







  
  
  applyBlueFilter(tileSprite);
  occlusionContainer.addChild(tileSprite);
}

// Função para remover tile da camada de oclusão
function removeTileFromOcclusionLayer(tileId, occlusionContainer) {
  const existingTileSprite = occlusionContainer.getChildByName(`occlusion-tile-${tileId}`);
  if (existingTileSprite) {
      occlusionContainer.removeChild(existingTileSprite);
  }
}

// Função para atualizar camada de oclusão baseada em tokens
function updateOcclusionLayer(canvas) {
  const occlusionContainer = canvas.stage.getChildByName('OcclusionContainer');
  if (!occlusionContainer) return;

  // Limpa a camada atual
  occlusionContainer.removeChildren();

  // Verifica todos os tiles
  canvas.tiles.placeables.forEach(tile => {
    // Verifica sobreposição com qualquer token
    const overlappingTokens = canvas.tokens.placeables.filter(token => 
      isOverlapping(tile.bounds, token.bounds)
    );

    if (overlappingTokens.length > 0) {
      addTileToOcclusionLayer(tile, occlusionContainer);
    }
  });
}

// Função utilitária para verificar sobreposição de bounds
function isOverlapping(bounds1, bounds2) {
  return !(
      bounds1.right < bounds2.left || 
      bounds1.left > bounds2.right || 
      bounds1.bottom < bounds2.top || 
      bounds1.top > bounds2.bottom
  );
}

// Hook para renderização da cena
Hooks.on('canvasReady', (canvas) => {
  createOcclusionContainer(canvas);
  updateOcclusionLayer(canvas);
});

// Hooks de movimento de token
Hooks.on('updateToken', (document, changes, options, userId) => {
  if (canvas?.ready) {
    updateOcclusionLayer(canvas);
  }
});

Hooks.on('preDeleteToken', (document, options, userId) => {
  if (canvas?.ready) {
    updateOcclusionLayer(canvas);
  }
});
*/















































/*
// Encapsula o código para evitar conflitos no escopo global
class TokenOcclusionModule {
  constructor() {
    this.occlusionLayer = null;
    this.updateOcclusion = this.updateOcclusion.bind(this);
  }

  // Inicializa o módulo
  initialize() {
    this._createOcclusionLayer();
    this._hookEvents();
    this.updateOcclusion(); // Atualiza inicialmente
  }

  // Cria a camada occlusionLayer sobre o canvas
  _createOcclusionLayer() {
    this.occlusionLayer = new PIXI.Container();
    this.occlusionLayer.name = "occlusionLayer";
    this.occlusionLayer.visible = true;

    // Define a posição e escala para acompanhar o canvas
    this.occlusionLayer.scale.set(10);
    this.occlusionLayer.position.set(0, 0);

    // Adiciona a camada ao stage do Canvas, acima das tiles e tokens
    canvas.stage.addChild(this.occlusionLayer);
  }

  // Configura os hooks para ouvir eventos de atualização de tokens e tiles
  _hookEvents() {
    Hooks.on('updateToken', this.updateOcclusion);
    Hooks.on('createToken', this.updateOcclusion);
    Hooks.on('deleteToken', this.updateOcclusion);
    Hooks.on('updateTile', this.updateOcclusion);
    Hooks.on('createTile', this.updateOcclusion);
    Hooks.on('deleteTile', this.updateOcclusion);
    Hooks.on('canvasPan', this.updateOcclusion);
    Hooks.on('canvasZoom', this.updateOcclusion);
    Hooks.on('canvasReady', this.updateOcclusion);
    // Adiciona hooks para transformação do canvas
    canvas.stage.on('scale', this.updateOcclusion);
    canvas.stage.on('move', this.updateOcclusion);
  }

  // Atualiza a camada de oclusão, recalculando as máscaras
  updateOcclusion() {
    // Limpa a camada de oclusão antes de refazer as máscaras
    this.occlusionLayer.removeChildren();

    // Itera sobre todos os tokens e tiles para detectar interseções
    for (let token of canvas.tokens.placeables) {
      for (let tile of canvas.tiles.placeables) {
        if (this._checkIntersection(token, tile)) {
          const mask = this._createOcclusionMask(token, tile);
          if (mask) {
            this.occlusionLayer.addChild(mask);
          }
        }
      }
    }
  }

  // Verifica se o token e o tile estão se sobrepondo
  _checkIntersection(token, tile) {
    const tokenBounds = token.getBounds();
    const tileBounds = tile.getBounds();

    // Verifica se há interseção entre os limites do token e do tile
    return tokenBounds.intersects(tileBounds);
  }

  // Cria a máscara de oclusão baseada na interseção das texturas
  _createOcclusionMask(token, tile) {
    // Calcula a interseção
    const tokenBounds = token.getBounds();
    const tileBounds = tile.getBounds();

    const intersectX = Math.max(tokenBounds.x, tileBounds.x);
    const intersectY = Math.max(tokenBounds.y, tileBounds.y);
    const intersectWidth = Math.min(tokenBounds.right, tileBounds.right) - intersectX;
    const intersectHeight = Math.min(tokenBounds.bottom, tileBounds.bottom) - intersectY;

    if (intersectWidth <= 0 || intersectHeight <= 0) return null;

    // Cria um sprite a partir da textura do token
    const tokenTexture = token.document.texture.src;
    const texture = PIXI.Texture.from(tokenTexture);

    // Calcula as coordenadas relativas para o recorte
    const relativeX = (intersectX - tokenBounds.x) / token.scale.x;
    const relativeY = (intersectY - tokenBounds.y) / token.scale.y;
    const relativeWidth = intersectWidth / token.scale.x;
    const relativeHeight = intersectHeight / token.scale.y;

    // Define a área de recorte na textura
    const rectangle = new PIXI.Rectangle(relativeX, relativeY, relativeWidth, relativeHeight);
    const intersectTexture = new PIXI.Texture(texture, rectangle);

    // Cria o sprite com a textura recortada
    const sprite = new PIXI.Sprite(intersectTexture);

    // Aplica as transformações para alinhar o sprite corretamente
    sprite.scale.set(token.scale.x, token.scale.y);
    sprite.position.set(intersectX, intersectY);
    sprite.alpha = 0.5; // Opacidade 50%
    sprite.tint = 0xFFFFFF; // Cor branca

    // Retorna o sprite com a silhueta
    return sprite;
  }
}

// Instancia o módulo
const tokenOcclusionModule = new TokenOcclusionModule();

// Inicializa o módulo quando o canvas estiver pronto
Hooks.once('canvasReady', () => {
  tokenOcclusionModule.initialize();
});
*/





















































/*
class OcclusionLayer extends PIXI.Container {
  constructor() {
    super();
    this.name = 'occlusionLayer';
  }

  // Este método irá adicionar a camada ao canvas
  static create() {
    const layer = new OcclusionLayer();
    canvas.stage.addChild(layer);
    return layer;
  }

  // Sincronização das transformações do canvas
  static syncTransformations() {
    const canvasWidth = canvas.dimensions.width;
    const canvasHeight = canvas.dimensions.height;
    this.width = canvasWidth;
    this.height = canvasHeight;

    // Aplicando as transformações do canvas na oclusão
    this.position.set(canvas.worldTransform.tx, canvas.worldTransform.ty);
    this.scale.set(canvas.stage.scale.x, canvas.stage.scale.y);
  }
}

function checkTokenTileIntersection(token, tile) {
  const tokenBounds = token.getBounds();  // Pega o bounding box do token
  const tileBounds = tile.getBounds();    // Pega o bounding box do tile
  
  // Verifica se há interseção entre os dois
  return tokenBounds.intersects(tileBounds);
}

function createOcclusionMask(token, tile) {
  const tokenTexture = token.texture;
  const tileTexture = tile.texture;

  // Usando uma máscara baseada na interseção das texturas
  const mask = new PIXI.Graphics();
  mask.beginFill(0xFFFFFF, 0.5); // Cor branca com 50% de opacidade
  mask.drawRect(token.x, token.y, token.width, token.height);  // Desenha o retângulo de interseção
  mask.endFill();

  // Aplicando a máscara ao token
  token.texture = tokenTexture;  // Usa a textura original do token com a máscara
  tile.texture = tileTexture;    // Faz o mesmo com o tile
  canvas.occlusionLayer.addChild(mask);
}

Hooks.on("canvasReady", () => {
  const occlusionLayer = OcclusionLayer.create();
  
  // Sincronizar transformações do canvas
  occlusionLayer.syncTransformations();
});

Hooks.on("canvasPan", () => {
  OcclusionLayer.syncTransformations();  // Atualiza as transformações durante o pan
});

Hooks.on("canvasResize", () => {
  OcclusionLayer.syncTransformations();  // Atualiza as dimensões durante o resize
});

Hooks.on("updateToken", (token, updateData) => {
  canvas.tiles.placeables.forEach(tile => {
    if (checkTokenTileIntersection(token, tile)) {
      createOcclusionMask(token, tile);
    }
  });
});
*/



















/*
class OcclusionLayer extends CanvasLayer {
  static get layerOptions() {
      return mergeObject(super.layerOptions, {
          name: "occlusion",
          canDragCreate: false,
          zIndex: 180
      });
  }

  constructor() {
      super();
  }

  draw() {
    const p = super.draw();

    // Criar um container PIXI para armazenar nossas cópias
    this.occlusionContainer = new PIXI.Container();
    this.addChild(this.occlusionContainer);

    // Ajustar dimensões e posição inicial
    this.position.set(canvas.dimensions.sceneX, canvas.dimensions.sceneY);
    this.width = canvas.dimensions.width;
    this.height = canvas.dimensions.height;

    // Inicializar arrays para armazenar nossas cópias
    this.tokenCopies = [];
    this.tileCopies = [];

    return p;
  }

  detectCollisions() {
      // Limpar máscaras anteriores
      this.clearMasks();

      // Garantir que temos arrays válidos
      if (!this.tokenCopies || !this.tileCopies) {
          this.tokenCopies = [];
          this.tileCopies = [];
          return;
      }

      this.tokenCopies.forEach(token => {
          if (!token) return;
          
          this.tileCopies.forEach(tile => {
              if (!tile) return;
              
              if (this.checkIntersection(token, tile)) {
                  this.applyRedFilter(token, tile);
              }
          });
      });
  }

  clearMasks() {
      // Remover máscaras antigas
      const masksToRemove = this.occlusionContainer.children.filter(child => child.isMask);
      masksToRemove.forEach(mask => {
          this.occlusionContainer.removeChild(mask);
          mask.destroy();
      });
  }

  checkIntersection(token, tile) {
      const tokenBounds = token.getBounds();
      const tileBounds = tile.getBounds();
      return tokenBounds.intersects(tileBounds);
  }

  applyRedFilter(token, tile) {
      // Calcular área de interseção
      const tokenBounds = token.getBounds();
      const tileBounds = tile.getBounds();
      
      const x = Math.max(tokenBounds.x, tileBounds.x);
      const y = Math.max(tokenBounds.y, tileBounds.y);
      const width = Math.min(tokenBounds.right, tileBounds.right) - x;
      const height = Math.min(tokenBounds.bottom, tileBounds.bottom) - y;

      // Criar máscara para a área de interseção
      const mask = new PIXI.Graphics();
      mask.beginFill(0xFFFFFF);
      mask.drawRect(x, y, width, height);
      mask.endFill();
      mask.isMask = true;

      // Criar sprite com filtro vermelho
      const intersectionSprite = new PIXI.Sprite(token.texture);
      intersectionSprite.position.set(x, y);
      intersectionSprite.width = width;
      intersectionSprite.height = height;
      intersectionSprite.mask = mask;

      // Aplicar filtro vermelho com opacidade 0.5
      const redFilter = new PIXI.filters.ColorMatrixFilter();
      redFilter.matrix = [
          1, 0, 0, 0,
          0, 0, 0, 0,
          0, 0, 0, 0,
          0, 0, 0, 0.5
      ];
      intersectionSprite.filters = [redFilter];

      this.occlusionContainer.addChild(mask);
      this.occlusionContainer.addChild(intersectionSprite);
  }

  copyTokens() {
      // Limpar cópias antigas
      this.tokenCopies.forEach(copy => {
          this.occlusionContainer.removeChild(copy);
          copy.destroy();
      });
      this.tokenCopies = [];

      if (!canvas.tokens?.placeables) return;
      
      canvas.tokens.placeables.forEach(token => {
          if (!token.texture) return;
          
          const tokenCopy = new PIXI.Sprite(token.texture.clone());
          // Aplicar filtro branco
          const colorMatrix = new PIXI.filters.ColorMatrixFilter();
          colorMatrix.matrix = [
              1, 1, 1, 0,
              1, 1, 1, 0,
              1, 1, 1, 0,
              0, 0, 0, 1
          ];
          tokenCopy.filters = [colorMatrix];
          
          // Copiar posição e dimensões
          tokenCopy.position.copyFrom(token.position);
          tokenCopy.width = token.width;
          tokenCopy.height = token.height;
          
          this.tokenCopies.push(tokenCopy);
          this.occlusionContainer.addChild(tokenCopy);
      });
  }

  copyTiles() {
      // Limpar cópias antigas
      this.tileCopies.forEach(copy => {
          this.occlusionContainer.removeChild(copy);
          copy.destroy();
      });
      this.tileCopies = [];

      if (!canvas.tiles?.placeables) return;
      
      canvas.tiles.placeables.forEach(tile => {
          if (!tile.texture) return;
          
          const tileCopy = new PIXI.Sprite(tile.texture.clone());
          // Aplicar filtro azul
          const colorMatrix = new PIXI.filters.ColorMatrixFilter();
          colorMatrix.matrix = [
              0, 0, 1, 0,
              0, 0, 1, 0,
              0, 0, 1, 0,
              0, 0, 0, 0.5
          ];
          tileCopy.filters = [colorMatrix];
          
          // Copiar posição e dimensões
          tileCopy.position.copyFrom(tile.position);
          tileCopy.width = tile.width;
          tileCopy.height = tile.height;
          
          this.tileCopies.push(tileCopy);
          this.occlusionContainer.addChild(tileCopy);
      });
  }

  refresh() {
      this.copyTokens();
      this.copyTiles();
      this.detectCollisions();
  }
}

// Registro do módulo
Hooks.once('init', () => {
  CONFIG.Canvas.layers.occlusion = {
      layerClass: OcclusionLayer,
      group: "interface"
  };
});

Hooks.on('canvasInit', async () => {
  // Aguardar o canvas estar pronto
  await canvas.ready;
  
  // Certificar-se de que a camada existe
  if (!canvas.occlusion) return;
  
  // Inicializar a camada
  await canvas.occlusion.draw();
  
  // Atualizar a camada
  canvas.occlusion.refresh();
});

// Eventos para manter sincronização
Hooks.on('canvasPan', () => {
  if (!canvas.occlusion?.occlusionContainer) return;
  canvas.occlusion.occlusionContainer.position.copyFrom(canvas.stage.position);
});

Hooks.on('canvasZoom', () => {
  if (!canvas.occlusion?.occlusionContainer) return;
  canvas.occlusion.occlusionContainer.scale.copyFrom(canvas.stage.scale);
});

// Hooks para atualizar a camada quando tokens ou tiles são modificados
Hooks.on('createToken', () => {
  if (canvas.occlusion) canvas.occlusion.refresh();
});

Hooks.on('updateToken', () => {
  if (canvas.occlusion) canvas.occlusion.refresh();
});

Hooks.on('deleteToken', () => {
  if (canvas.occlusion) canvas.occlusion.refresh();
});

Hooks.on('createTile', () => {
  if (canvas.occlusion) canvas.occlusion.refresh();
});

Hooks.on('updateTile', () => {
  if (canvas.occlusion) canvas.occlusion.refresh();
});

Hooks.on('deleteTile', () => {
  if (canvas.occlusion) canvas.occlusion.refresh();
});
*/



































/*
// Registra o hook para quando um token se move
Hooks.on("updateToken", async (token, changes) => {
  // Verifica se houve mudança na posição
  if (!changes.x && !changes.y) return;
  
  // Obtém a cena atual
  const scene = game.scenes.current;
  if (!scene) return;
  
  
  
  // Wait for the movement animation to complete -----------------------------------------------------------------------------------------
  // Wait for the movement animation to complete -----------------------------------------------------------------------------------------
  // Wait for the movement animation to complete -----------------------------------------------------------------------------------------
  // Wait for the movement animation to complete -----------------------------------------------------------------------------------------
  //const token = tokenSprite.document
  const anim = CanvasAnimation.getAnimation(token.animationName);
  if(anim?.promise) await anim.promise;



  
  // Obtém o token atualizado
  const tokenDoc = scene.tokens.get(token.id);
  const tokenSprite = tokenDoc.object;
  
  // Obtém todos os tiles da cena
  const tiles = scene.tiles.contents;
  
  // Variável para controlar se o token está tocando algum tile
  let touchedTiles = [];
  
  // Para cada tile, verifica sobreposição
  for (const tile of tiles) {
    const tileSprite = tile.object;
    
    if (checkBoundsOverlap(tokenSprite, tileSprite)) {
      console.log(`Token "${tokenDoc.name}" está sobreposto com o tile ID: ${tile.id}`);
      touchedTiles.push(tile);
    }
  }

  // Remove máscara anterior se existir
  removeOcclusionMask(scene);

  // Se houver tiles tocados, cria a máscara de oclusão
  if (touchedTiles.length > 0) {
    // Cor da máscara pode ser ajustada aqui (no formato hexadecimal)
    const maskColor = 0xFF00F0;  // Vermelho como padrão, mas pode ser modificado
    const maskAlpha = 0.5;  // Opacidade (0-1)
    
    await createOcclusionMask(scene, tokenSprite, touchedTiles, maskColor, maskAlpha);
    //await createOcclusionMask(scene, tokenSprite, touchedTiles);
    console.log(`Máscara de oclusão criada para o token "${tokenDoc.name}"`);
  } else {
    console.log(`Token "${tokenDoc.name}" NÃO está tocando nenhum tile.`);
  }

});

// Função para verificar sobreposição básica de bounds
function checkBoundsOverlap(token, tile) {
  const tokenSprite = token.mesh;
  const tileSprite = tile.mesh;
	
  // Primeiro faz uma verificação rápida de bounds
  const tokenBounds = tokenSprite.getBounds();
  const tileBounds = tileSprite.getBounds();
  
  return !(tokenBounds.left > tileBounds.right ||
           tokenBounds.right < tileBounds.left ||
           tokenBounds.top > tileBounds.bottom ||
           tokenBounds.bottom < tileBounds.top);
}



// Função para criar máscara de oclusão
async function createOcclusionMask(scene, tokenSprite, touchedTiles, maskColor, maskAlpha) {
  // Verifica se o mundo está em perspectiva isométrica
  const isometricWorldEnabled = game.settings.get(MODULE_ID, "worldIsometricFlag");
  const isSceneIsometric = scene.getFlag(MODULE_ID, "isometricEnabled");
  
  // Cria um novo container para a máscara
  const maskContainer = new PIXI.Container();
  maskContainer.name = "occlusion-mask-container";
  
  // Cria um gráfico para desenhar a máscara
  const maskGraphics = new PIXI.Graphics();
  maskGraphics.beginFill(maskColor, maskAlpha);  // Usa a cor e alpha fornecidos
  
  // Obtém os bounds do token
  const tokenBounds = tokenSprite.mesh.getBounds();
  
  // Para cada tile tocado, adiciona sua área de interseção à máscara
  touchedTiles.forEach(tile => {
    const tileBounds = tile.object.mesh.getBounds();
    
    // Calcula a intersecção
    const intersectionLeft = Math.max(tokenBounds.left, tileBounds.left);
    const intersectionRight = Math.min(tokenBounds.right, tileBounds.right);
    const intersectionTop = Math.max(tokenBounds.top, tileBounds.top);
    const intersectionBottom = Math.min(tokenBounds.bottom, tileBounds.bottom);
    
    // Desenha o retângulo de intersecção
    maskGraphics.drawRect(
      intersectionLeft, 
      intersectionTop, 
      intersectionRight - intersectionLeft, 
      intersectionBottom - intersectionTop
    );
  });
  
  maskGraphics.endFill();
  
  // Adiciona o gráfico ao container
  maskContainer.addChild(maskGraphics);
  
  // Adiciona o container à camada de interface do Foundry
  const interfaceLayer = canvas.stage;
  interfaceLayer.addChild(maskContainer);
  
  
  
  
  
  
  

  // Se o mundo estiver em perspectiva isométrica, aplica as mesmas transformações
  if (isometricWorldEnabled && isSceneIsometric) {
    // Aplica a mesma rotação e skew do canvas
    maskContainer.rotation = Math.PI/4;
    maskContainer.skew.set(0, 0);
    
    // Ajusta a escala para corresponder à transformação isométrica
    const gridSize = canvas.scene.grid.size;
    const isoScale = scene.getFlag(MODULE_ID, 'isometricScale') ?? 1;
    maskContainer.scale.set(
      isoScale,
      isoScale * Math.sqrt(3)
    );
    
    // Posiciona a máscara considerando os offsets isométricos
    // Você pode precisar ajustar isso dependendo de como exatamente as outras transformações são aplicadas
    const tokenDoc = tokenSprite.document;
    const scaleX = tokenDoc.width;  // scale for 2x2, 3x3 tokens
    const scaleY = tokenDoc.height; // scale for 2x2, 3x3 tokens
    const elevation = tokenDoc.elevation ?? 0;
    const gridDistance = canvas.scene.grid.distance;
    const gridSizeRatio = gridSize / 100;
    
    // Calcula offsets isométricos (baseado na função cartesianToIso do seu código)
    let offsetX = tokenDoc.texture.anchorX;
    let offsetY = tokenDoc.texture.anchorY;
    
    offsetX += elevation * (1/gridDistance) * 100 * Math.sqrt(2) * (1/scaleX);
    offsetX *= gridSizeRatio;
    offsetY *= gridSizeRatio;
    const isoOffsets = cartesianToIso(offsetX, offsetY);
    
    maskContainer.position.set(
      tokenDoc.x + isoOffsets.x,
      tokenDoc.y + isoOffsets.y
    );
  }
  
  
  
  
  // Posiciona a máscara no topo de todas as outras camadas
  maskContainer.zIndex = Number.MAX_SAFE_INTEGER;
  interfaceLayer.sortChildren();
}

// Função para remover a máscara de oclusão existente
function removeOcclusionMask(scene) {
  const interfaceLayer = canvas.stage;
  const existingMask = interfaceLayer.getChildByName("occlusion-mask-container");
  
  if (existingMask) {
    interfaceLayer.removeChild(existingMask);
  }
}*/

































/*
// Função para criar a máscara de sobreposição
function createOverlayMask(baseObject, overlappingObject) {
  // Obter os bounds dos objetos
  const baseBounds = baseObject.bounds;
  const overlapBounds = overlappingObject.bounds;

  // Verificar se há sobreposição
  if (!baseBounds.intersects(overlapBounds)) return null;
  
  // Criar o container para a máscara
  const maskContainer = new PIXI.Container();

  // Criar sprite para o baseObject
  const baseSprite = new PIXI.Sprite(baseObject.texture);
  baseSprite.position.set(baseBounds.x, baseBounds.y);

  // Extrair a máscara baseada nos pixels não transparentes do overlappingObject
  const texture = overlappingObject.texture;
  const bitmap = texture.baseTexture.resource.source;

  // Criar um canvas para processar os dados de transparência da textura
  const canvas = document.createElement('canvas');
  canvas.width = texture.width;
  canvas.height = texture.height;
  const context = canvas.getContext('2d');
  
  // Desenhar a textura do token no canvas para analisar os pixels
  context.drawImage(bitmap, 0, 0);
  
  // Obter os dados dos pixels (imagem bruta)
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
  const pixels = imageData.data;
  console.log(pixels);

  // Criar a máscara com base nos pixels não transparentes
  const maskGraphics = new PIXI.Graphics();
  maskGraphics.beginFill(0xffffff);

  // Iterar sobre os pixels da textura e identificar áreas não transparentes
  for (let y = 0; y < texture.height; y++) {
    for (let x = 0; x < texture.width; x++) {
      const index = (y * texture.width + x) * 4;
      const alpha = pixels[index + 3]; // Canal alpha
      if (alpha > 0) {
        // Se o pixel não for transparente, desenha na máscara
        maskGraphics.drawRect(x, y, 1, 1);
      }
    }
  }

  maskGraphics.endFill();

  // Aplicar a máscara ao sprite
  baseSprite.mask = maskGraphics;

  // Criar filtro azul (filtro ColorMatrix)
  const blueOverlayFilter = new PIXI.ColorMatrixFilter();
  blueOverlayFilter.matrix = [
    0, 0, 0, 0, 0,    // Vermelho
    0, 0, 0.5, 0, 0,  // Verde
    0, 0, 1, 0, 0,    // Azul
    0, 0, 0, 1, 0     // Alpha
  ];
  baseSprite.filters = [blueOverlayFilter];

  // Adicionar o sprite e a máscara ao container
  maskContainer.addChild(baseSprite);
  maskContainer.addChild(maskGraphics);

  return maskContainer;
}

// Função para atualizar a camada de sobreposição
function updateOverlayLayer() {
  // Remover a camada de sobreposição existente
  if (canvas.overlayLayer) {
    canvas.overlayLayer.removeChildren();
  } else {
    // Criar a camada de sobreposição se não existir
    canvas.overlayLayer = canvas.stage.addChildAt(new PIXI.Container(), canvas.stage.children.length);
  }

  // Obter todos os tiles e tokens no canvas
  const tiles = canvas.tiles.placeables;
  const tokens = canvas.tokens.placeables;
  
  // Verificar sobreposições entre tiles e tokens
  for (const tile of tiles) {
    for (const token of tokens) {
      const overlayMask = createOverlayMask(tile, token);
      if (overlayMask) {
        canvas.overlayLayer.addChild(overlayMask);
      }
    }
  }
}

Hooks.on('canvasReady', updateOverlayLayer);
Hooks.on('updateToken', (document, changes, options) => { updateOverlayLayer() });
Hooks.on('updateTile', (document, changes, options) => { updateOverlayLayer() });

Hooks.on('init', () => { console.log('Overlay Layer Module | Inicializado') });

// Registrar hooks para atualização da camada de sobreposição
Hooks.on('canvasInit', () => {
  if (!canvas.overlayLayer) { // Garantir que a camada de sobreposição seja criada durante a inicialização do canvas
    canvas.overlayLayer = canvas.stage.addChildAt(new PIXI.Container(), canvas.stage.children.length);
  }
});





/*
blueOverlayFilter.alpha = 1;
blueOverlayFilter.matrix = [
  0, 0, 0, 0.5, 0,
  0, 0, 0, 0.5, 0,
  0, 0, 0, 0.5, 0,
  0, 0, 0,   1, 0
];
*/



























/*
class OverlayCanvasLayer extends CanvasLayer {
  constructor() {
      super();
      this.overlayContainer = new PIXI.Container();
      this.addChild(this.overlayContainer);
      this.overlaySprites = new Map();
  }

  // Cria ou atualiza o sprite de sobreposição para um elemento
  createOrUpdateOverlay(element, type) {
      const key = `${type}_${element.id}`;
      let sprite = this.overlaySprites.get(key);

      // Cria novo sprite se não existir
      if (!sprite) {
          sprite = new PIXI.Sprite(element.texture);
          this.overlayContainer.addChild(sprite);
          this.overlaySprites.set(key, sprite);
      }

      // Atualiza posição e propriedades do sprite
      sprite.position.set(element.x, element.y);
      sprite.width = element.width;
      sprite.height = element.height;

      return sprite;
  }

  // Verifica sobreposição considerando pixels transparentes
  checkPixelOverlap(sprite1, sprite2) {
      // Verifica dimensões válidas
      if (!sprite1 || !sprite2 || 
          sprite1.width <= 0 || sprite1.height <= 0 || 
          sprite2.width <= 0 || sprite2.height <= 0) {
          return false;
      }

      try {
          // Converte sprites para renderTextures para processamento de pixel
          const renderTexture1 = PIXI.RenderTexture.create({
              width: Math.floor(sprite1.width),
              height: Math.floor(sprite1.height)
          });
          const renderTexture2 = PIXI.RenderTexture.create({
              width: Math.floor(sprite2.width),
              height: Math.floor(sprite2.height)
          });

          const renderer = canvas.app.renderer;
          renderer.render(sprite1, { renderTexture: renderTexture1 });
          renderer.render(sprite2, { renderTexture: renderTexture2 });

          // Obtém dados de pixel
          const pixels1 = renderer.plugins.extract.pixels(renderTexture1);
          const pixels2 = renderer.plugins.extract.pixels(renderTexture2);

          // Libera recursos
          renderTexture1.destroy(true);
          renderTexture2.destroy(true);

          // Verifica sobreposição de pixels não transparentes
          for (let y = 0; y < Math.min(sprite1.height, sprite2.height); y++) {
              for (let x = 0; x < Math.min(sprite1.width, sprite2.width); x++) {
                  const index1 = (y * Math.floor(sprite1.width) + x) * 4;
                  const index2 = (y * Math.floor(sprite2.width) + x) * 4;

                  // Verifica se ambos os pixels têm alfa > 0 (não transparentes)
                  if (pixels1[index1 + 3] > 0 && pixels2[index2 + 3] > 0) {
                      return true;
                  }
              }
          }

          return false;
      } catch (error) {
          console.error('Erro na verificação de sobreposição de pixels:', error);
          return false;
      }
  }

  // Aplica filtro de sobreposição azul quando tokens sobrepõem tiles
  checkAndApplyOverlay() {
      const tiles = canvas.tiles.placeables;
      const tokens = canvas.tokens.placeables;

      tiles.forEach(tile => {
          // Verifica se o tile é válido e tem dimensões
          if (!tile || !tile.texture) return;

          const tileSprite = this.createOrUpdateOverlay(tile, 'tile');
          
          tokens.forEach(token => {
              // Verifica se o token é válido e tem dimensões
              if (!token || !token.texture) return;

              const tokenSprite = this.createOrUpdateOverlay(token, 'token');
              
              // Verifica sobreposição considerando transparência
              if (this.checkPixelOverlap(tileSprite, tokenSprite)) {
                  const blueOverlayFilter = new PIXI.filters.ColorMatrixFilter();
                  blueOverlayFilter.alpha = 1;
                  blueOverlayFilter.matrix = [
                    0, 0, 0, 0.5, 0,
                    0, 0, 0, 0.5, 0,
                    0, 0, 0, 0.5, 0,
                    0, 0, 0,   1, 0
                  ];
                  //blueOverlayFilter.blue(1.5); // Ajuste a intensidade conforme necessário
                  tokenSprite.filters = [blueOverlayFilter];
              } else {
                  tokenSprite.filters = [];
              }
          });
      });
  }

  // Registra listeners para atualizações
  registerListeners() {
      Hooks.on('updateToken', () => this.checkAndApplyOverlay());
      Hooks.on('updateTile', () => this.checkAndApplyOverlay());
      Hooks.on('canvasPan', () => this.checkAndApplyOverlay());
      Hooks.on('refreshToken', () => this.checkAndApplyOverlay());
      Hooks.on('refreshTile', () => this.checkAndApplyOverlay());
  }
}

// Inicialização da camada customizada
Hooks.on('canvasInit', () => {
  canvas.overlayLayer = canvas.stage.addChild(new OverlayCanvasLayer());
  canvas.overlayLayer.registerListeners();
});
*/





























/*
function createOverlayLayer() {
  const sprites = new Map();

  function updateSpriteForDocument(document) {
      const existingSprite = sprites.get(document.id);
      if (existingSprite) {
          canvas.overlayLayer.removeChild(existingSprite);
          sprites.delete(document.id);
      }

      const sourceSprite = document.object.sprite;
      if (!sourceSprite) return;

      const overlaySprite = new PIXI.Sprite(sourceSprite.texture);
      overlaySprite.position.copyFrom(sourceSprite.position);
      overlaySprite.anchor.copyFrom(sourceSprite.anchor);
      overlaySprite.width = sourceSprite.width;
      overlaySprite.height = sourceSprite.height;

      const blueOverlay = new PIXI.filters.ColorMatrixFilter();
      blueOverlay.tint = 0x0000FF;
      blueOverlay.brightness(0.8);

      const isOverlapped = canvas.tokens.placeables.some(token => 
          checkSpriteOverlap(overlaySprite, token.sprite)
      );

      if (isOverlapped) {
          overlaySprite.filters = [blueOverlay];
      }

      canvas.overlayLayer.addChild(overlaySprite);
      sprites.set(document.id, overlaySprite);
  }

  function checkSpriteOverlap(sprite1, sprite2) {
      const bounds1 = sprite1.getBounds();
      const bounds2 = sprite2.getBounds();
      return !(
          bounds1.right < bounds2.left || 
          bounds1.left > bounds2.right || 
          bounds1.bottom < bounds2.top || 
          bounds1.top > bounds2.bottom
      );
  }

  function initOverlayLayer() {
      canvas.overlayLayer = canvas.stage.addChild(new PIXI.Container());
      
      Hooks.on('updateTile', updateSpriteForDocument);
      Hooks.on('updateToken', updateSpriteForDocument);
  }

  Hooks.once('canvasReady', initOverlayLayer);
}

Hooks.once('init', createOverlayLayer);
*/
