// Foundry VTT Occlusion Layer Module for V12

// Global configuration for occlusion layer
const OCCLUSION_CONFIG = {
	layer: null,
	initialized: false
};

// Pixel transparency threshold (adjustable)
const TRANSPARENCY_THRESHOLD = 0.1;

// Advanced pixel intersection detection shader
const createIntersectionShader = () => {
	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 = `
			precision highp float;
			
			uniform sampler2D tokenTexture;
			uniform sampler2D tileTexture;
			uniform vec2 textureSize;
			uniform float transparencyThreshold;
			
			varying vec2 vTextureCoord;
			
			void main(void) {
					// Amostragem das texturas nas mesmas coordenadas
					vec4 tokenPixel = texture2D(tokenTexture, vTextureCoord);
					vec4 tilePixel = texture2D(tileTexture, vTextureCoord);
					
					// Verificação de transparência avançada
					bool tokenVisible = tokenPixel.a > transparencyThreshold;
					bool tileVisible = tilePixel.a > transparencyThreshold;
					
					// Cálculo da interseção
					float intersectionAlpha = float(tokenVisible && tileVisible);
					
					// Mesclagem de cores na interseção
					vec3 intersectionColor = mix(
							tokenPixel.rgb, 
							tilePixel.rgb, 
							0.5
					);
					
					// Saída final
					gl_FragColor = vec4(intersectionColor, intersectionAlpha);
			}
	`;

	return { vertexShader, fragmentShader };
};

// Função para criar filtro de shader de interseção
function createIntersectionFilter(tokenTexture, tileTexture) {
	const { vertexShader, fragmentShader } = createIntersectionShader();
	
	const intersectionFilter = new PIXI.Filter(vertexShader, fragmentShader, {
			tokenTexture: tokenTexture,
			tileTexture: tileTexture,
			textureSize: [tokenTexture.width, tokenTexture.height],
			transparencyThreshold: 0.1
	});

	return intersectionFilter;
}














// Core occlusion layer initialization
function initializeOcclusionLayer() {
	// Remove existing layer if present
	if (OCCLUSION_CONFIG.layer) {
			canvas.stage.removeChild(OCCLUSION_CONFIG.layer);
			OCCLUSION_CONFIG.layer.destroy({ children: true });
	}

	// Create new occlusion layer
	OCCLUSION_CONFIG.layer = new PIXI.Container();
	OCCLUSION_CONFIG.layer.name = "OcclusionLayer";
	OCCLUSION_CONFIG.layer.eventMode = 'passive';
	OCCLUSION_CONFIG.layer.interactiveChildren = false;

	canvas.stage.addChild(OCCLUSION_CONFIG.layer);
	OCCLUSION_CONFIG.initialized = true;
}






// Detect token-tile pixel-level intersection
function detectTokenTileIntersection(token, tile) {
	const tokenBounds = token.mesh.getBounds();
	const tileBounds = tile.mesh.getBounds();

	// Quick bounds check
	if (!(tokenBounds.x < tileBounds.x + tileBounds.width &&
				tokenBounds.x + tokenBounds.width > tileBounds.x &&
				tokenBounds.y < tileBounds.y + tileBounds.height &&
				tokenBounds.y + tokenBounds.height > tileBounds.y)) {
			return false;
	}

	// Advanced pixel-level intersection
	const tempCanvas = document.createElement('canvas');
	const ctx = tempCanvas.getContext('2d');

	// Set canvas to intersection area
	const intersectX = Math.max(tokenBounds.x, tileBounds.x);
	const intersectY = Math.max(tokenBounds.y, tileBounds.y);
	const intersectWidth = Math.min(tokenBounds.x + tokenBounds.width, tileBounds.x + tileBounds.width) - intersectX;
	const intersectHeight = Math.min(tokenBounds.y + tokenBounds.height, tileBounds.y + tileBounds.height) - intersectY;

	tempCanvas.width = intersectWidth;
	tempCanvas.height = intersectHeight;

	// Draw token and tile sections
	ctx.drawImage(
			token.mesh.texture.baseTexture.resource.source,
			intersectX - tokenBounds.x,
			intersectY - tokenBounds.y,
			intersectWidth,
			intersectHeight,
			0,
			0,
			intersectWidth,
			intersectHeight
	);

	ctx.globalCompositeOperation = 'source-in';
	ctx.drawImage(
			tile.mesh.texture.baseTexture.resource.source,
			intersectX - tileBounds.x,
			intersectY - tileBounds.y,
			intersectWidth,
			intersectHeight,
			0,
			0,
			intersectWidth,
			intersectHeight
	);

	// Pixel-level analysis
	const imageData = ctx.getImageData(0, 0, intersectWidth, intersectHeight);
	const pixels = imageData.data;

	for (let i = 3; i < pixels.length; i += 4) {
			if (pixels[i] > TRANSPARENCY_THRESHOLD * 255) {
					return true;
			}
	}

	return false;
}







// Update occlusion layer based on current scene state
function updateOcclusionLayer() {
	if (!canvas.ready || !OCCLUSION_CONFIG.initialized) {
			initializeOcclusionLayer();
	}

	// Clear previous occlusion elements
	OCCLUSION_CONFIG.layer.removeChildren();

	const tokens = canvas.tokens.placeables;
	const tiles = canvas.tiles.placeables;

	tokens.forEach(token => {
			const intersectingTiles = tiles.filter(tile => detectTokenTileIntersection(token, tile));

			if (intersectingTiles.length > 0) {
					const occlusionSprite = createOcclusionSprite(token, intersectingTiles);
					if (occlusionSprite) {
							OCCLUSION_CONFIG.layer.addChild(occlusionSprite);
					}
			}
	});
}




// Create occlusion sprite with advanced masking
function createOcclusionSprite(token, intersectingTiles) {
	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);

	const mask = createIntersectionMask(token, intersectingTiles);
	if (mask) {
			sprite.mask = mask;
	}

	// Apply filters
	sprite.filters = [colorMatrixFilter, outlineFilter];
	sprite.eventMode = 'passive';

	return sprite;
}


// Create advanced pixel-level intersection mask
function createIntersectionMask(token, intersectingTiles) {
	const maskGraphics = new PIXI.Graphics();
	maskGraphics.beginFill(0xffffff);

	intersectingTiles.forEach(tile => {
			const tokenBounds = token.mesh.getBounds();
			const tileBounds = tile.mesh.getBounds();

			const x = Math.max(tokenBounds.x, tileBounds.x);
			const y = Math.max(tokenBounds.y, tileBounds.y);
			const width = Math.min(tokenBounds.x + tokenBounds.width, tileBounds.x + tileBounds.width) - x;
			const height = Math.min(tokenBounds.y + tokenBounds.height, tileBounds.y + tileBounds.height) - y;

			if (width > 0 && height > 0) {
					maskGraphics.drawRect(x, y, width, height);
			}
	});

	return maskGraphics;
}

// Hook registrations
Hooks.on('canvasReady', updateOcclusionLayer);
Hooks.on('canvasPan', updateOcclusionLayer);
Hooks.on('updateToken', updateOcclusionLayer);
Hooks.on('updateTile', updateOcclusionLayer);
Hooks.on('canvasInit', initializeOcclusionLayer);
Hooks.on('changeScene', initializeOcclusionLayer);

// Module initialization
Hooks.once('ready', () => {
	if (canvas.ready) {
			initializeOcclusionLayer();
	}
});


























// Definição do isoOutlineFilter
if (typeof PIXI !== 'undefined' && PIXI.filters) {
	const IsoOutlineVertexShader =`
		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 IsoOutlineFragmentShader = `
			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(IsoOutlineVertexShader, IsoOutlineFragmentShader);

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

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







// Create global filters outside of initialization to prevent repeated creation
function createOcclusionFilters() {
	// Blue ColorMatrix Filter
	const colorMatrixFilter = new PIXI.ColorMatrixFilter();
	colorMatrixFilter.alpha = 1;
	colorMatrixFilter.matrix = [
			0.7, 0, 0, 0, 0,
			0, 0.7, 0, 0, 0.2,
			0, 0, 1, 0, 0.3,
			0, 0, 0, 1, 0
	];

	// Red Outline Filter with thin border
	//const outlineFilter = new PIXI.filters.OutlineFilter(1, 0xff0000);
	const outlineFilter = new PIXI.filters.isoOutlineFilter();
	outlineFilter.thickness = 0.005;
	outlineFilter.color = 0x00ff59;

	return { colorMatrixFilter, outlineFilter };
}

const { colorMatrixFilter, outlineFilter } = createOcclusionFilters();