// Enhanced Occlusion Layer Module for Foundry VTT

export function registerOcclusionLayerConfig2ForUpload() {
	// 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(); 
			});
		});
	}

	// 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, 10); // 16: ~60fps





















// 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)
		return;  // Ensure canvas is ready and layer is initialized
	if (!occlusionConfig.initialized)
		initializeOcclusionLayer(); /// Reinitialize if not yet set up

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

	// Get all tokens and tiles
	const tokens = canvas.tokens.placeables;
	const tiles = canvas.tiles.placeables;

	tokens.forEach(token => {
		// Find tiles that intersect with this token
		const intersectingTiles = tiles.filter(tile => 
			checkTokenTileIntersection(token, tile)
		);

		// If there are intersecting tiles, create an occlusion sprite
		if (intersectingTiles.length > 0) {
			const occlusionSprite = createOcclusionSprite(token, intersectingTiles);
			if (occlusionSprite) {
				occlusionConfig.tokensLayer.addChild(occlusionSprite);
			}
		}
	});
}














// Token-Tile Intersection Check
function checkTokenTileIntersection(token, tile) {
	// Basic intersection check using bounding boxes
	const tokenBounds = token.mesh.getBounds();
	const tileBounds = tile.mesh.getBounds();

	return (
		tokenBounds.x < tileBounds.x + tileBounds.width &&
		tokenBounds.x + tokenBounds.width > tileBounds.x &&
		tokenBounds.y < tileBounds.y + tileBounds.height &&
		tokenBounds.y + tokenBounds.height > tileBounds.y
	);
}






const maskFragmentShader = `
	varying vec2 vTextureCoord;
		
	uniform sampler2D uSampler;        // Token texture
	uniform sampler2D uTileMask;       // Combined tile mask texture
	uniform vec4 inputSize;            // Size of input textures
	uniform vec4 intersectionBounds;   // x, y, width, height of intersection area
		
	void main() {
		vec4 tokenColor = texture2D(uSampler, vTextureCoord);
			
		// Convert texture coordinates to intersection space
		vec2 intersectCoord = (vTextureCoord - intersectionBounds.xy) / intersectionBounds.zw;
			
		// Sample the tile mask
		vec4 maskColor = texture2D(uTileMask, intersectCoord);
			
		// Apply the mask
		gl_FragColor = vec4(tokenColor.rgb, tokenColor.a * maskColor.a);
	}
`;

// Modified createOcclusionSprite to use GPU-based masking
function createOcclusionSprite(token, intersectingTiles) {
	if (!token.mesh || !token.mesh.texture) return null;

	// Calculate intersection bounds
	const bounds = calculateIntersectionBounds(token, intersectingTiles);
	
	// Create tile mask texture
	const tileMaskTexture = createTileMaskTexture(intersectingTiles, bounds);
	
	// Create sprite from token texture
	const sprite = new PIXI.Sprite(token.mesh.texture);
	sprite.position.copyFrom(token.mesh.position);
	sprite.anchor.copyFrom(token.mesh.anchor);
	sprite.angle = token.mesh.angle;
	sprite.scale.copyFrom(token.mesh.scale);

	// Create custom shader material
	const maskShader = new PIXI.Shader(undefined, maskFragmentShader, {
			uSampler: sprite.texture,
			uTileMask: tileMaskTexture,
			inputSize: new Float32Array([
					sprite.texture.width,
					sprite.texture.height,
					tileMaskTexture.width,
					tileMaskTexture.height
			]),
			intersectionBounds: new Float32Array([
					bounds.x,
					bounds.y,
					bounds.width,
					bounds.height
			])
	});

	sprite.shader = maskShader;
	
	// Apply existing visual filters
	sprite.filters = [colorMatrix, outlineFilter];
	sprite.eventMode = 'passive';

	return sprite;
}





// Helper function to calculate intersection bounds
function calculateIntersectionBounds(token, intersectingTiles) {
	let minX, minY, maxX, maxY, sourceX, sourceY, sourceWidth, sourceHeight;
	const tokenBounds = token.mesh.getBounds();
	
	intersectingTiles.forEach(tile => {
		// Access tile texture
		const tileTexture = tile.texture?.baseTexture?.resource?.source;
		if (!tileTexture || tileTexture.width <= 0 || tileTexture.height <= 0) return;

		const tileBounds = tile.mesh.getBounds();
		minX = Math.max(tokenBounds.x, tileBounds.x);
		minY = Math.max(tokenBounds.y, tileBounds.y);
		maxX = Math.min(tokenBounds.x + tokenBounds.width, tileBounds.x + tileBounds.width) - minX;
		maxY = Math.min(tokenBounds.y + tokenBounds.height, tileBounds.y + tileBounds.height) - minY;

		sourceX = (minX - tileBounds.x) * (tileTexture.width / tileBounds.width);
		sourceY = (minY - tileBounds.y) * (tileTexture.height / tileBounds.height);
		sourceWidth = maxX * (tileTexture.width / tileBounds.width);
		sourceHeight = maxY * (tileTexture.height / tileBounds.height);
	});
	
	return {
		x: sourceX,
		y: sourceY,
		width: sourceWidth,
		height: sourceHeight
	};
}

// Create a render texture for the tile mask
function createTileMaskTexture(intersectingTiles, bounds) {
	const renderTexture = PIXI.RenderTexture.create({
			width: bounds.width,
			height: bounds.height
	});
	
	const maskContainer = new PIXI.Container();
	
	intersectingTiles.forEach(tile => {
			const tileSprite = new PIXI.Sprite(tile.texture);
			
			// Transform tile to mask space
			tileSprite.position.set(
					tile.x - bounds.x,
					tile.y - bounds.y
			);
			
			tileSprite.scale.set(
					tile.width / tile.texture.width,
					tile.height / tile.texture.height
			);
			
			maskContainer.addChild(tileSprite);
	});
	
	canvas.app.renderer.render(maskContainer, {
			renderTexture,
			clear: true
	});
	
	return renderTexture;
}

























// 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 = 1, color = 0x000000, alpha = 1) {
				super(vertexShader, fragmentShader);

				this.uniforms.outlineColor = new Float32Array(4);
				this.uniforms.outlineThickness = new Float32Array(2);
				this.uniforms.filterArea = new Float32Array(2);
				this.uniforms.alpha = alpha;

				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) {
				const filterAreaX = this.uniforms.filterArea[0] || 1;
				const filterAreaY = this.uniforms.filterArea[1] || 1;
			
				this.uniforms.outlineThickness[0] = value / filterAreaX;
				this.uniforms.outlineThickness[1] = value / filterAreaY;
		}
	}

	// Adicione o isoOutlineFilter ao namespace PIXI.filters
	PIXI.filters.isoOutlineFilter = IsoOutlineFilter;
} else {
	console.error('PIXI ou PIXI.filters não estão disponíveis.');
}







// Create Deafult Custom Filters 
// Create new outline filter
const outlineFilter = new PIXI.filters.isoOutlineFilter();
outlineFilter.thickness = 0.005;
outlineFilter.color = 0x00ff59;
		
// Create a greyscale/dimming filter
const colorMatrix = new PIXI.ColorMatrixFilter();
colorMatrix.alpha = 1;
colorMatrix.matrix = [
	0.3,  0.0,  0.0,  0.0,  0.0,
	0.0,  0.3,  0.0,  0.0,  0.0,
	0.0,  0.0,  0.3,  0.0,  0.0,
	0.0,  0.0,  0.0,  1.0,  0.0
];
//colorMatrix.matrix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0]; //azul

const alphaFilter = new PIXI.AlphaFilter();
alphaFilter.alpha = 0.5;