// 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',  // Added to ensure update on scene modifications
			'updateScene'      // Added to ensure update on scene modifications
		];

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

	// Initialize on canvas setup
	// Hooks.on('refreshToken', () => { 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();
}

// Otimização 1: Debounce do updateOcclusionLayer
//const debouncedUpdate = debounce(updateOcclusionLayer, 50);
//const throttledUpdate = throttle(updateOcclusionLayer, 50);







// 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;
}



function debugIntersection(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;
}

// Comprehensive update mechanism for occlusion layer
function updateOcclusionLayer() {
	// Canvas not ready, returning
	if (!canvas.ready) {
		return;
	}

	// Initializing occlusion layer
	if (!occlusionConfig.initialized) {
		initializeOcclusionLayer();
	}

	// Clear existing occlusion tokens
	occlusionConfig.tokensLayer.removeChildren();

	// Get all tokens and tiles
	const tokens = canvas.tokens.placeables;
	const tiles = canvas.tiles.placeables;
	
	// console.log(`Processing ${tokens.length} tokens and ${tiles.length} tiles`);

	tokens.forEach((token, tokenIndex) => {
		// Find intersecting tiles with debugging
		const intersectingTiles = tiles.filter(tile => debugIntersection(token, tile));

		if (intersectingTiles.length > 0) {
			const sprite = createOcclusionSprite(token, intersectingTiles);
			if (sprite) {
				occlusionConfig.tokensLayer.addChild(sprite);
			}
		}
	});
}










// Custom shader for handling both intersection masking and effects
const occlusionVertexShader = `
	attribute vec2 aVertexPosition;
	attribute vec2 aTextureCoord;
	
	uniform mat3 projectionMatrix;
	
	varying vec2 vTextureCoord;
	varying vec2 vWorldPos;
	
	void main() {
		vTextureCoord = aTextureCoord;
		
		// Usa a posição do vértice diretamente, já que o sprite já está posicionado
		gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
		
		// Passa a posição mundial para o fragment shader
		vWorldPos = aVertexPosition;
	}
`;

const occlusionFragmentShader = /*gls l*/`
	varying vec2 vTextureCoord;
	varying vec2 vWorldPos;
	
	uniform sampler2D uSampler;           // Token texture
	uniform sampler2D uTileMasks[8];      // Array of tile textures
	uniform vec4 uTileBounds[8];          // Array of tile bounds [x, y, width, height]
	uniform int uTileCount;               // Number of intersecting tiles
	uniform vec4 uColorMatrix;            // Color matrix for greyscale effect
	uniform vec4 uOutlineColor;           // Outline color
	uniform float uOutlineThickness;      // Outline thickness
	uniform float uAlpha;                 // Global alpha value
	
	bool isInTileBounds(vec2 pos, vec4 bounds) {
		return  pos.x >= bounds.x && 
						pos.x <= bounds.x + bounds.z && 
						pos.y >= bounds.y && 
						pos.y <= bounds.y + bounds.w;
	}
	
	float sampleTileMask(sampler2D tileTex, vec2 worldPos, vec4 bounds) {
		// Convert world position to tile UV coordinates
		vec2 tileUV = (worldPos - bounds.xy) / bounds.zw;
		vec4 tileColor = texture2D(tileTex, tileUV);
		return tileColor.a;
	}
	
	// Function to check if any neighboring pixel is different (for outline)
	float getOutlineStrength(vec2 coord, float baseAlpha) {
		float outline = 0.0;
		vec2 texelSize = vec2(uOutlineThickness);
		
		for(float x = -1.0; x <= 1.0; x += 1.0) {
			for(float y = -1.0; y <= 1.0; y += 1.0) {
				if(x == 0.0 && y == 0.0) continue;
				vec2 offset = vec2(x, y) * texelSize;
				vec4 neighborColor = texture2D(uSampler, coord + offset);
				if(abs(neighborColor.a - baseAlpha) > 0.1) {
						outline = 1.0;
						break;
				}
			}
		}
		return outline;
	}
	
	void main() {
		// Sample token texture
		vec4 tokenColor = texture2D(uSampler, vTextureCoord);
		
		// Check intersection with all tiles
		float maskAlpha = 0.0;
		for(int i = 0; i < 8; i++) {
			if(i >= uTileCount) break;
			
			if(isInTileBounds(vWorldPos, uTileBounds[i])) {
				float tileAlpha = sampleTileMask(uTileMasks[i], vWorldPos, uTileBounds[i]);
				maskAlpha = max(maskAlpha, tileAlpha);
			}
		}
		
		// If pixel is not in any tile intersection, discard
		if(maskAlpha * tokenColor.a <= 0.01) {
			discard;
		}
		
		// Apply greyscale effect
		//vec3 greyColor = uColorMatrix.rgb;
		vec3 greyColor = vec3(
				dot(tokenColor.r, uColorMatrix.r),
				dot(tokenColor.g, uColorMatrix.g),
				dot(tokenColor.b, uColorMatrix.b)
		);
		
		// Calculate outline
		float outlineStrength = getOutlineStrength(vTextureCoord, tokenColor.a);
		
		// Combine effects
		vec3 finalColor = mix(greyColor, uOutlineColor.rgb, outlineStrength);
		float finalAlpha = tokenColor.a * uAlpha;
		
		gl_FragColor = vec4(finalColor, finalAlpha);
	}
`;



// Create the enhanced occlusion filter
class EnhancedOcclusionFilter extends PIXI.Filter {
	constructor() {
		super(occlusionVertexShader, occlusionFragmentShader); //occlusionVertexShader, occlusionFragmentShader
		
		// Initialize uniforms
		this.uniforms.uTileMasks = [];
		this.uniforms.uTileBounds = new Float32Array(32);
		this.uniforms.uTileCount = 0;
		this.uniforms.uColorMatrix = new Float32Array([0.2, 0.2, 0.2, 0.5]);
		this.uniforms.uOutlineColor = new Float32Array([0.0, 1.0, 0.35, 1.0]);
		this.uniforms.uOutlineThickness = 0.001;
		this.uniforms.uAlpha = 1.0;

		// Token transform uniforms
		// this.uniforms.scale = new Float32Array([1.0, 1.0]);
		// this.uniforms.translation = new Float32Array([0.0, 0.0]);
		// this.uniforms.rotation = 0.0;
	}
	
	updateTiles(intersectingTiles) {
		const count = Math.min(intersectingTiles.length, 8);
		this.uniforms.uTileCount = count;
		
		for(let i = 0; i < count; i++) {
			const tile = intersectingTiles[i];
			const bounds = tile.mesh.getBounds();
			
			// Update tile texture
			this.uniforms.uTileMasks[i] = tile.texture;
			
			// Update tile bounds
			const idx = i * 4;
			this.uniforms.uTileBounds[idx] = bounds.x;
			this.uniforms.uTileBounds[idx + 1] = bounds.y;
			this.uniforms.uTileBounds[idx + 2] = bounds.width;
			this.uniforms.uTileBounds[idx + 3] = bounds.height;
		}
	}
	
	// updateTokenTransform(token) {
	// 	const bounds = token.mesh.getBounds();
	// 	this.uniforms.scale[0] = token.mesh.scale.x;
	// 	this.uniforms.scale[1] = token.mesh.scale.y;
	// 	this.uniforms.translation[0] = token.mesh.position.x;
	// 	this.uniforms.translation[1] = token.mesh.position.y;
	// 	// this.uniforms.translation[0] = bounds.x;
	// 	// this.uniforms.translation[1] = bounds.y;
	// 	this.uniforms.rotation = token.mesh.rotation;
	// }
}





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

	// Criar sprite com logs
	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);

	// Criar e configurar filtro
	const occlusionFilter = new EnhancedOcclusionFilter();
	occlusionFilter.updateTiles(intersectingTiles);
	// occlusionFilter.updateTokenTransform(token);
	
	// Aplicar filtro
	sprite.filters = [occlusionFilter];
	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);
}

// Função de debug para verificar se os shaders compilaram corretamente
function validateShader(gl, shader) {
	if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
			const info = gl.getShaderInfoLog(shader);
			console.error('Shader compile error:', info);
			return false;
	}
	return true;
}