// Enhanced Occlusion Layer Module for Foundry VTT

export function registerOcclusionConfig() {
	// Global Hook Registration
	function registerGlobalHooks() {
		const updateTriggers = [ // Update triggers for occlusion layer
			'canvasReady', 
			'canvasPan',
			'canvasTokensRefresh', 
			'updateToken', 
			'controlToken', 
			'refreshToken',
			'preUpdateScene',
			'updateScene'
		];

		updateTriggers.forEach(hookName => {
			Hooks.on(hookName, () => {
				updateOcclusionLayer(); //debouncedUpdate();
			});
		});
	}

	// Initial layer setup
	Hooks.once('ready', () => {
		if (canvas.ready)
			initializeOcclusionLayer();
	});

	// Initialize on canvas setup
	Hooks.on('canvasInit', () => {
		initializeOcclusionLayer();
	});

	// Reset on scene change
	Hooks.on('changeScene', () => {
		if (occlusionConfig.container) {
			canvas.stage.removeChild(occlusionConfig.container);
			occlusionConfig.container.destroy({ children: true });
			occlusionConfig.container = null;
			occlusionConfig.tokensLayer = null;
			occlusionConfig.initialized = false;
		}
	});
	
	// Start the module
	registerGlobalHooks();
}






// Persistent occlusion layer configuration
const occlusionConfig = {
	container: null,
	tokensLayer: null,
	initialized: false
};

// Initialize or reset the occlusion layer
function initializeOcclusionLayer() {
	// Remove existing container if it exists
	if (occlusionConfig.container) {
		canvas.stage.removeChild(occlusionConfig.container);
		occlusionConfig.container.destroy({ children: true });
	}

	// Create the main occlusion container
	occlusionConfig.container = new PIXI.Container();
	occlusionConfig.container.name = "OcclusionContainer";
	occlusionConfig.container.eventMode = 'passive';

	// Create a layer for occlusion tokens
	occlusionConfig.tokensLayer = new PIXI.Container();
	occlusionConfig.tokensLayer.name = "OcclusionTokens";
	occlusionConfig.tokensLayer.sortableChildren = true;

	// Add the layer to the container
	occlusionConfig.container.addChild(occlusionConfig.tokensLayer);

	// Add to canvas stage
	canvas.stage.addChild(occlusionConfig.container);
	canvas.stage.sortChildren();

	occlusionConfig.initialized = true;
}



// Comprehensive update mechanism for occlusion layer
function updateOcclusionLayer() {
	if (!canvas.ready || !occlusionConfig.initialized) {
			if (!canvas.ready) return;
			initializeOcclusionLayer();
	}

	occlusionConfig.tokensLayer.removeChildren();

	const tokens = canvas.tokens.placeables;
	const tiles = canvas.tiles.placeables;
	
	tokens.forEach((token) => {
			const intersectingTiles = tiles.filter(tile => checkTokenTileIntersection(token, tile));
			if (intersectingTiles.length > 0) {
					const sprite = createOcclusionSprite(token, intersectingTiles);
					if (sprite) {
							occlusionConfig.tokensLayer.addChild(sprite);
					}
			}
	});
}



function checkTokenTileIntersection(token, tile) {
	const tokenBounds = token.mesh.getBounds();
	const tileBounds = tile.mesh.getBounds();
	
	const intersection = {
		x: Math.max(tokenBounds.x, tileBounds.x),
		y: Math.max(tokenBounds.y, tileBounds.y),
		width: Math.min(tokenBounds.x + tokenBounds.width, tileBounds.x + tileBounds.width) - Math.max(tokenBounds.x, tileBounds.x),
		height: Math.min(tokenBounds.y + tokenBounds.height, tileBounds.y + tileBounds.height) - Math.max(tokenBounds.y, tileBounds.y)
	};
	
	return intersection.width > 0 && intersection.height > 0;
}








// vertex e fragment shader that implements the occlusion logic
const vertexShader = /*gls l*/`
	attribute vec2 aVertexPosition;
	attribute vec2 aTextureCoord;
	uniform mat3 projectionMatrix;
	varying vec2 vWorldPos;
	varying vec2 vTextureCoord;

	void main() {
		// Passa as coordenadas de textura para o fragment shader
		vTextureCoord = aTextureCoord;
		
		// Calcula a posição do vértice no espaço do canvas
		vec3 worldPos = projectionMatrix * vec3(aVertexPosition, 1.0);
		vWorldPos = worldPos.xy;
		
		// Define a posição final do vértice
		gl_Position = vec4(worldPos.xy, 0.0, 1.0);
	}
`;
const fragmentShader = `
	// varying vec2 vTextureCoord;
	// uniform sampler2D uSampler;
	// uniform sampler2D tileTexture;
	// uniform vec4 inputFrame;      // x, y, width, height do token
	// uniform vec4 tileFrame;       // x, y, width, height do tile
				
	// void main(void) {
	// 	// Obter cor do pixel atual do token
	// 	vec4 tokenColor = texture2D(uSampler, vTextureCoord);
					
	// 	// Converter coordenadas de textura para coordenadas de mundo
	// 	vec2 worldCoord = vec2(
	// 			inputFrame.x + (vTextureCoord.x * inputFrame.z),
	// 			inputFrame.y + (vTextureCoord.y * inputFrame.w)
	// 	);
					
	// 	// Verificar se está dentro da área do tile
	// 	if (worldCoord.x >= tileFrame.x && 
	// 			worldCoord.x <= tileFrame.x + tileFrame.z && 
	// 			worldCoord.y >= tileFrame.y && 
	// 			worldCoord.y <= tileFrame.y + tileFrame.w) {
							
	// 			// Converter coordenada de mundo para coordenada de textura do tile
	// 			vec2 tileCoord = vec2(
	// 					(worldCoord.x - tileFrame.x) / tileFrame.z,
	// 					(worldCoord.y - tileFrame.y) / tileFrame.w
	// 			);
							
	// 			vec4 tileColor = texture2D(tileTexture, tileCoord);
							
	// 			// Aplicar lógica de oclusão
	// 			if (tokenColor.a > 0.0 || tileColor.a > 0.0) {
	// 					gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
	// 			} else {
	// 					gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
	// 			}
	// 	} else {
	// 			// Fora da área do tile - tornar transparente
	// 			gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
	// 	}
	// }

	varying vec2 vTextureCoord;
	uniform sampler2D uSampler;       // Textura do token (não usada aqui)
	uniform sampler2D uTileMask;      // Textura do tile
	uniform vec4 dimensions;          // [x, y, width, height] da interseção
	uniform vec4 tileDimensions;      // [x, y, width, height] do tile
	uniform vec4 uTileBounds[8];      // Array de bounds dos tiles [x, y, width, height]
	
	uniform sampler2D tileTexture;
	uniform vec4 inputFrame;      // x, y, width, height do token
	uniform vec4 tileFrame;       // x, y, width, height do tile


	void main(void) {
		// Posição local do fragmento na interseção
		// vec2 localPos = gl_FragCoord.xy - dimensions.xy;
				
		// Coordenadas UV normalizadas para a textura do tile
		// vec2 tileCoord = vec2(
		// 		(localPos.x / dimensions.z) * (tileDimensions.z / dimensions.z),
		// 		(localPos.y / dimensions.w) * (tileDimensions.w / dimensions.w)
		// );

		// Ajuste para considerar a posição do tile no canvas
		// tileCoord.x += (dimensions.x - tileDimensions.x) / tileDimensions.z;
		// tileCoord.y += (dimensions.y - tileDimensions.y) / tileDimensions.w;

		// Amostra a textura do tile
		// vec4 tileColor = texture2D(uTileMask, tileCoord);
		vec4 tileColor = texture2D(tileTexture, tileFrame.xy);

		// Amostra a textura do token
		// vec4 tokenColor = texture2D(uSampler, vTextureCoord);
		vec4 tokenColor = texture2D(uSampler, inputFrame.xy);

		// Se o pixel do tile for opaco, cria uma máscara branca opaca
		if (tokenColor.r > 0.1) {
			gl_FragColor.rgb = vec3(0.5, 1.0, 1.0); // Branco opaco
		}

		if (tokenColor.a < 0.1) {
			discard;
			//gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); // Transparente
		}	
	}
`;

// Custom occlusion filter implementation
class OcclusionFilter extends PIXI.Filter {
	constructor(token, tile) {
			super(vertexShader, fragmentShader);
			
			// Guardar referências
			this.token = token;
			this.tile = tile;
			
			// Configurar uniforms iniciais
			this.uniforms.tileTexture = tile.texture;
			this.uniforms.inputFrame = new Float32Array([0, 0, 0, 0]);
			this.uniforms.tileFrame = new Float32Array([0, 0, 0, 0]);
			
			// Atualizar frames
			this.updateFrames();
	}
	
	updateFrames() {
			// Atualizar frame do token
			const tokenBounds = this.token.mesh.getBounds();
			this.uniforms.inputFrame[0] = tokenBounds.x;
			this.uniforms.inputFrame[1] = tokenBounds.y;
			this.uniforms.inputFrame[2] = tokenBounds.width;
			this.uniforms.inputFrame[3] = tokenBounds.height;
			
			// Atualizar frame do tile
			const tileBounds = this.tile.getBounds();
			this.uniforms.tileFrame[0] = tileBounds.x;
			this.uniforms.tileFrame[1] = tileBounds.y;
			this.uniforms.tileFrame[2] = tileBounds.width;
			this.uniforms.tileFrame[3] = tileBounds.height;
	}

	apply(filterManager, input, output, clear) {
			// Atualizar frames antes de renderizar
			this.updateFrames();
			
			// Aplicar o filtro
			filterManager.applyFilter(this, input, output, clear);
	}
}



// Modified createOcclusionSprite function
function createOcclusionSprite(token, intersectingTiles) {
	if (!token.mesh || !token.mesh.texture) return null;

	const sprite = new PIXI.Sprite(token.mesh.texture);
	sprite.position.set(token.mesh.position.x, token.mesh.position.y);
	sprite.anchor.set(token.mesh.anchor.x, token.mesh.anchor.y);
	sprite.angle = token.mesh.angle;
	sprite.scale.set(token.mesh.scale.x, token.mesh.scale.y);

	// Create occlusion filters for each intersecting tile
	const occlusionFilters = intersectingTiles.map(tile => new OcclusionFilter(token, tile));

	// Add outline and color matrix filters
	const filters = [
			occlusionFilters,
			//colorMatrix,
			//isoOutlineFilter
	];

	sprite.filters = filters;
	sprite.eventMode = 'passive';

	// if (sprite) {
	// 	addDebugVisuals(token, intersectingTiles);
	// }

	return sprite;
}







// Adicionar um visual debug helper
function addDebugVisuals(token, intersectingTiles) {
	const debugGraphics = new PIXI.Graphics();
	
	// Desenhar bounds do token em vermelho
	const tokenBounds = token.mesh.getBounds();
	debugGraphics.lineStyle(2, 0xFF0000);
	debugGraphics.drawRect(tokenBounds.x, tokenBounds.y, tokenBounds.width, tokenBounds.height);
	
	// Desenhar bounds dos tiles em verde
	intersectingTiles.forEach(tile => {
		const tileBounds = tile.mesh.getBounds();
		debugGraphics.lineStyle(2, 0x00FF00);
		debugGraphics.drawRect(tileBounds.x, tileBounds.y, tileBounds.width, tileBounds.height);
	});
	
	// Adicionar ao stage
	canvas.stage.addChild(debugGraphics);
	
	// Remover após 5 segundos
	setTimeout(() => {
		canvas.stage.removeChild(debugGraphics);
		debugGraphics.destroy();
	}, 1000);
}







// Código do filtro de outline customizado (adicionar depois)

// Definição do isoOutlineFilter
if (typeof PIXI !== 'undefined' && PIXI.filters) {
	const vertexShader =`
		attribute vec2 aVertexPosition;
		attribute vec2 aTextureCoord;
		uniform mat3 projectionMatrix;
		varying vec2 vTextureCoord;

		void main(void) {
			gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
			vTextureCoord = aTextureCoord;
		}
	`;
	const fragmentShader = `
		varying vec2 vTextureCoord;

		uniform sampler2D uSampler;
		uniform float alpha;
		uniform vec2 outlineThickness;
		uniform vec4 outlineColor;
		uniform vec4 filterArea;
		uniform vec4 filterClamp;

		void main(void) {
			vec4 ownColor = texture2D(uSampler, vTextureCoord);
			vec4 curColor;
			float maxAlpha = 0.0;
			vec2 displaced;

			for (float angle = 0.0; angle < 6.28318530718; angle += 0.78539816339) {
				displaced.x = vTextureCoord.x + outlineThickness.x * cos(angle);
				displaced.y = vTextureCoord.y + outlineThickness.y * sin(angle);
				curColor = texture2D(uSampler, displaced);
				maxAlpha = max(maxAlpha, curColor.a);
			}
			float resultAlpha = max(maxAlpha, ownColor.a);
			gl_FragColor = vec4((ownColor.rgb * ownColor.a + outlineColor.rgb * (1.0 - ownColor.a)) * resultAlpha, resultAlpha);
		 }
	`;

	// Defina a classe isoOutlineFilter
	class IsoOutlineFilter extends PIXI.Filter {
		constructor(thickness = 0.5, color = 0x000000, alpha = 0.5) {
			super(vertexShader, fragmentShader);

			// Inicialize os uniforms
			this.uniforms.outlineColor = new Float32Array(4);     // Para armazenar RGBA
			this.uniforms.outlineThickness = new Float32Array(2); // Para armazenar X e Y
			this.uniforms.filterArea = new Float32Array(2);       // Para área de filtro
			this.uniforms.alpha = alpha;

			// Configure as propriedades iniciais
			this.color = color;
			this.thickness = thickness;
		}

		get alpha() { return this.uniforms.alpha; }
		set alpha(value) { this.uniforms.alpha = value; }

		get color() { return PIXI.utils.rgb2hex(this.uniforms.outlineColor); }
		set color(value) { PIXI.utils.hex2rgb(value, this.uniforms.outlineColor); }

		get thickness() { return this.uniforms.outlineThickness[0]; }
		set thickness(value) {
			// Certifique-se de que filterArea tenha valores válidos
			const filterAreaX = this.uniforms.filterArea[0] || 1; // Evite divisão por 0
			const filterAreaY = this.uniforms.filterArea[1] || 1; // Evite divisão por 0
			
			this.uniforms.outlineThickness[0] = value / filterAreaX;
			this.uniforms.outlineThickness[1] = value / filterAreaY;
		}
	}

	// Add the isooutlinefilter to the PIXI.filters namepace
	PIXI.filters.isoOutlineFilter = IsoOutlineFilter;
} else {
	console.error('PIXI ou PIXI.filters não estão disponíveis.');
}

// Create new outline filter
const isoOutlineFilter = new PIXI.filters.isoOutlineFilter();
isoOutlineFilter.thickness = 0.005;
isoOutlineFilter.color = 0x00ff59;

// Create a greyscale/dimming filter
const colorMatrix = new PIXI.ColorMatrixFilter();
colorMatrix.alpha = 1;
colorMatrix.matrix = [
	0.0,  0.0,  0.0,  0.0,  0.0,
	0.0,  0.0,  0.0,  0.0,  0.0,
	1.0,  1.0,  1.0,  0.0,  0.0,
	0.0,  0.0,  0.0,  1.0,  0.0
];
