// Registro do sistema de oclusão
export function registerOcclusionConfig() {
  Hooks.once('ready', () => {
      TokenOcclusionManager.initialize();
  });
}

class TokenOcclusionManager {
  static initialize() {
    Hooks.on("refreshToken", (token) => {
        this.updateTokenOcclusion(token);
    });
  }

  constructor() {
    this.renderer = canvas.app.renderer;
    this.renderTexture = PIXI.RenderTexture.create({
        width: canvas.dimensions.width,
        height: canvas.dimensions.height
    });
    this.container = new PIXI.Container();
    this.container.sortableChildren = true;
    this.tileToIndexer = new Map();
    this.renderTextureSprite = null;
    this.rendered = false;
  }

  /**
   * Processa um tile para criar uma máscara de oclusão
   */
  processTileOccluder(tile_id) {
      let tile = canvas.primary.tiles.get(`Tile.${tile_id}`);
      if (!tile) return false;

      // Criação do sprite
      let sprite = PIXI.Sprite.from(tile.texture);
      sprite.isSprite = true;
      sprite.name = tile.document.id;

      // Cópia de propriedades básicas
      const propertiesToCopy = ["anchor", "width", "height", "position", "scale", "angle", "zIndex"];
      let changed = false;
      for (const key of propertiesToCopy) {
          let diff = false;
          if (sprite[key].equals && tile[key].equals) {
              diff = (!sprite[key].equals(tile[key]));
          } else {
              diff = (sprite[key] !== tile[key]);
          }
          if (diff) {
              changed = true;
              sprite[key] = tile[key];
          }
      }

      if (!changed) return false;

      // Cálculo de bounds
      sprite.calculateBounds();
      let bounds = sprite.getBounds();
      if (!bounds) return false;

      // Cálculo de coordenadas normalizadas
      const x = tile.position.x + bounds.left - (bounds.width / 2);
      const y = tile.position.y + (bounds.height / 2) - (bounds.height - bounds.bottom);

      let normalized_x = (x - canvas.dimensions.width) / (0 - canvas.dimensions.width);
      normalized_x = ((0xff * normalized_x) & 255) / 255;

      let normalized_y = (y - 0) / (canvas.dimensions.height - 0);
      normalized_y = ((0xff * normalized_y) & 255) / 255;

      // Aplicação do shader
      let filter = new PIXI.Filter(
          null,
          `
          varying vec2 vTextureCoord;
          uniform sampler2D uSampler;
          uniform float x;
          uniform float y;

          void main(void) {
              mediump vec4 color = texture2D(uSampler, vTextureCoord);
              color.a = floor(0.5 + color.a);
              gl_FragColor = vec4(color.a, x*color.a, y*color.a, color.a);
          }
          `,
          { x: normalized_x, y: normalized_y }
      );

      sprite.filters = [filter];
      sprite.meta = { x, y, nx: normalized_x, ny: normalized_y };

      // Atualização do container
      if (this.tileToIndexer.has(tile_id)) {
          let [_, oldSprite] = this.tileToIndexer.get(tile_id);
          if (oldSprite) {
              this.container.removeChild(oldSprite);
          }
      }

      this.container.addChild(sprite);
      this.tileToIndexer.set(tile_id, [null, sprite]);

      this.renderer.render(this.container, {
          renderTexture: this.renderTexture
      });

      return true;
  }

  static updateTokenOcclusion(token) {
    if (!token?.mesh) return;
    
    // Remove filtros antigos
    token.mesh.filters = token.mesh.filters?.filter(f => !(f instanceof TokenOcclusionFilter)) || [];
    
    // Obtém tiles da cena
    const tiles = canvas.tiles.placeables;

    // Calcula posição normalizada do token
    let x = token.center.x;
    let y = token.center.y;

    x = Math.max(0, Math.min(x, canvas.dimensions.width));
    y = Math.max(0, Math.min(y, canvas.dimensions.height));

    x = x / canvas.dimensions.width;
    y = y / canvas.dimensions.height;

    // Verifica sobreposição e aplica filtros
    for (const tile of tiles) {
      if (this.checkTextureOverlap(token, tile)) {
        const occlusionFilter = new TokenOcclusionFilter(token, tile, x, y, canvas.app.renderer);
        token.mesh.filters.push(occlusionFilter);
      }
    }
  }

  static checkTextureOverlap(token, tile) {
    const tokenTextureBounds = token.mesh.getBounds();
    const tileTextureBounds = tile.mesh.getBounds();
    
    return !(
        tokenTextureBounds.right < tileTextureBounds.left ||
        tokenTextureBounds.left > tileTextureBounds.right ||
        tokenTextureBounds.bottom < tileTextureBounds.top ||
        tokenTextureBounds.top > tileTextureBounds.bottom
    ) && token.texture?.valid && tile.texture?.valid;
  }

  reindexTile(tile_id) {
    if (!this.tileToIndexer.has(tile_id)) return;
    this.processTileOccluder(tile_id);
  }

  getSpriteMask() {
    return this.renderTexture;
  }

  destructor() {
    this.tileToIndexer = new Map();
    this.container.removeChildren();
  }
}










const vertexShader = `
  attribute vec2 aVertexPosition;
  attribute vec2 aTextureCoord;
  uniform mat3 projectionMatrix;
  uniform mat3 otherMatrix;
  varying vec2 vMaskCoord;
  varying vec2 vTextureCoord;

  void main(void) {
    gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
    vTextureCoord = aTextureCoord;
    vMaskCoord = (otherMatrix * vec3(aTextureCoord, 1.0)).xy;
  }
`;

const fragmentShader = `
  uniform sampler2D uSampler;
  uniform sampler2D mask;
  uniform vec4 uColorMatrix;
  uniform float x;
  uniform float y;
  varying vec2 vTextureCoord;
  varying vec2 vMaskCoord;

  void main(void) {
    vec4 color = texture2D(uSampler, vTextureCoord);
    vec4 mask = texture2D(mask, vMaskCoord);
    if (mask.a >= x) {
      float output_alpha = uColorMatrix.a;
      vec3 output_color = uColorMatrix.rgb * output_alpha;
      gl_FragColor = vec4(output_color.rgb * color.rgb, output_alpha) * (color.a);
    } else {
      gl_FragColor = color;
    }
  }
`;

// Shader para oclusão
class TokenOcclusionFilter extends PIXI.Filter {
  constructor(token, tile, x, y, renderer) {
    super(vertexShader, fragmentShader);
    
    this.token = token;
    this.tile = tile;
    this.maskMatrix = new PIXI.Matrix();
    this.uniforms.tileTexture = tile.texture;
    this.uniforms.uColorMatrix = new Float32Array([0.2, 0.3, 0.9, 1.0]);
    this.uniforms.x = x;
    this.uniforms.y = y;
    this.maskTexture = renderer;
  }

  calculateSpriteMatrix(outputMatrix, texture, filterManager) {
    const {sourceFrame, destinationFrame} = filterManager.activeState;
    const {orig} = texture;
    const mappedMatrix = outputMatrix.set(destinationFrame.width, 0, 0, destinationFrame.height, sourceFrame.x, sourceFrame.y);
    const worldTransform = canvas.stage.worldTransform.copyTo(PIXI.Matrix.TEMP_MATRIX);

    worldTransform.invert();
    mappedMatrix.prepend(worldTransform);
    mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height);

    return mappedMatrix;
  }

  apply(filterManager, input, output, clearMode) {
    const tex = this.maskTexture;
    
    if (!tex?.valid || !tex.baseTexture || tex.baseTexture.destroyed) {
      return;
    }

    if (!tex.uvMatrix) {
      tex.uvMatrix = new PIXI.TextureMatrix(tex, 0.0);
    }
    tex.uvMatrix.update();

    this.uniforms.mask = tex;
    this.uniforms.otherMatrix = this.calculateSpriteMatrix(
      this.maskMatrix,
      tex,
      filterManager
    );

    filterManager.applyFilter(this, input, output, clearMode);
  }
}

export { TokenOcclusionManager, TokenOcclusionFilter };