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

	// 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() {
	// Ensure canvas is ready and layer is initialized
	if (!canvas.ready) return;

	// Reinitialize if not yet set up
	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;

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


// Create Occlusion Sprite with Advanced Masking
function createOcclusionSprite(token, intersectingTiles) {
	if (!token.mesh || !token.mesh.texture) return null;

	// Criar o sprite do token
	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 a máscara com a GPU
	const maskSprite = createOcclusionMask(token, intersectingTiles);
	
	// Se a máscara foi criada, aplicá-la ao sprite do token
	if (maskSprite) {
			sprite.mask = maskSprite;
	}

	// Adicionar filtros adicionais (se necessário)
	sprite.filters = [colorMatrix, outlineFilter]; // Se for necessário adicionar mais filtros

	return sprite;
}











// Shader para criar a máscara na GPU
const occlusionShader = new PIXI.Filter(null, `
	precision mediump float;

	varying vec2 vTextureCoord;
	uniform sampler2D uSampler;
	uniform vec4 uTokenBounds;  // [x, y, width, height]
	uniform vec4 uTileBounds;   // [x, y, width, height]

	void main() {
			// Obter a cor da textura no pixel atual
			vec4 color = texture2D(uSampler, vTextureCoord);

			// Verificar se a coordenada está dentro do tile
			bool isIntersecting = (vTextureCoord.x >= uTileBounds.x && vTextureCoord.x <= uTileBounds.x + uTileBounds.z) &&
														(vTextureCoord.y >= uTileBounds.y && vTextureCoord.y <= uTileBounds.y + uTileBounds.w);

			if (isIntersecting) {
					gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);  // Pixel visível (intersecção)
			} else {
					gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);  // Pixel invisível (fora da intersecção)
			}
	}
`);

function createOcclusionMask(token, intersectingTiles) {
	// Criar uma textura de renderização para a máscara (RenderTexture)
	const maskTexture = PIXI.RenderTexture.create({ width: token.width, height: token.height });
	
	// Criar o sprite da máscara usando a textura gerada
	const maskSprite = new PIXI.Sprite(maskTexture);

	// Criar o filtro de oclusão com o shader
	const shader = new PIXI.Filter(null, occlusionShader.fragmentShader);
	
	// Definir os limites do token para o shader
	shader.uniforms.uTokenBounds = [token.x, token.y, token.width, token.height];
	
	// Renderizar todos os tiles afetando o RenderTexture
	canvas.app.renderer.render(maskSprite, { renderTexture: maskTexture }); // Iniciar a renderização do RenderTexture

	intersectingTiles.forEach(tile => {
			// Definir os limites do tile para o shader
			shader.uniforms.uTileBounds = [tile.x, tile.y, tile.width, tile.height];
			
			// Renderizar o tile com o shader aplicado
			canvas.app.renderer.render(tile, { renderTexture: maskTexture });
	});

	// Aplicar o filtro de oclusão ao sprite da máscara
	maskSprite.filters = [shader];

	return maskSprite;
}



// 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 fragmentShader22 = `
			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;
        
					// Outline externo com mais samples e suavização
					for (float angle = 0.0; angle < 6.28318530718; angle += 0.19634954085) {  // Dividido em 32 samples
							// Múltiplas amostras em diferentes distâncias para cada ângulo
							for (float dist = 0.5; dist <= 1.0; dist += 0.25) {
									displaced.x = vTextureCoord.x + (outlineThickness.x * dist) * cos(angle);
									displaced.y = vTextureCoord.y + (outlineThickness.y * dist) * sin(angle);
									curColor = texture2D(uSampler, displaced);
									maxAlpha = max(maxAlpha, curColor.a * (1.0 - dist * 0.5));  // Peso baseado na distância
							}
					}

					// Outline interno com suavização
					float innerAlpha = 0.0;
					for (float angle = 0.0; angle < 6.28318530718; angle += 0.19634954085) {
							for (float dist = 0.25; dist <= 0.75; dist += 0.25) {
									displaced.x = vTextureCoord.x - (outlineThickness.x * dist) * cos(angle);
									displaced.y = vTextureCoord.y - (outlineThickness.y * dist) * sin(angle);
									curColor = texture2D(uSampler, displaced);
									innerAlpha = max(innerAlpha, step(curColor.a, 0.1) * (1.0 - dist * 0.5));
							}
					}
        
					float resultAlpha = max(maxAlpha, ownColor.a);
					float innerEffect = smoothstep(0.0, 0.5, innerAlpha) * ownColor.a;

					// Suaviza a transição entre cores
					vec3 color = ownColor.rgb;
					if (ownColor.a < 0.1) {
							color = outlineColor.rgb * smoothstep(0.0, 0.3, maxAlpha);
					} else if (innerEffect > 0.0) {
							color = mix(color, outlineColor.rgb, innerEffect * 0.5);
					}

					gl_FragColor = vec4(color, resultAlpha);
			}
	`;

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

				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 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
];

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