// Enhanced Occlusion Layer Module for Foundry VTT - Optimized Version

// Utility Functions
function norm(value, min, max) {
	return (value - min) / (max - min);
}

// Persistent occlusion configuration
const occlusionConfig = {
	renderer: null,
	renderTexture: null,
	container: null,
	tokensLayer: null,
	tileIndexer: new Map(),
	initialized: false
};

// Initialize Rendering Context
function initializeOcclusionContext() {
	// Create renderer if not exists
	if (!occlusionConfig.renderer) {
			occlusionConfig.renderer = canvas.app.renderer;
	}

	// Create render texture
	occlusionConfig.renderTexture = PIXI.RenderTexture.create({
			width: canvas.dimensions.width,
			height: canvas.dimensions.height
	});

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

	// Create tokens layer
	occlusionConfig.tokensLayer = new PIXI.Container();
	occlusionConfig.tokensLayer.name = "OcclusionTokens";
	occlusionConfig.container.addChild(occlusionConfig.tokensLayer);

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

	occlusionConfig.initialized = true;
}

// Advanced Bounds Calculation
function calculateOptimizedBounds(sprite, tile) {
	// Create a temporary canvas for pixel-level analysis
	const tempCanvas = document.createElement('canvas');
	const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });

	sprite.calculateBounds();
	const bounds = sprite.getBounds();
	const size = Math.max(Math.ceil(bounds.width), Math.ceil(bounds.height));

	// Create render texture
	const renderTexture = PIXI.RenderTexture.create({ 
			width: size, 
			height: size 
	});

	// Temporarily center the sprite
	const originalPosition = sprite.position.clone();
	const originalAnchor = sprite.anchor.clone();
	sprite.anchor.set(0.5, 0.5);
	sprite.position.set(size / 2, size / 2);

	// Render sprite to texture
	occlusionConfig.renderer.render(sprite, { renderTexture });
	const pixels = occlusionConfig.renderer.extract.pixels(renderTexture);

	// Restore original sprite positioning
	sprite.position.copy(originalPosition);
	sprite.anchor.copy(originalAnchor);

	// Pixel-level bounds detection
	let left = size, top = size, right = 0, bottom = 0;
	
	for (let y = 0; y < size; y++) {
			for (let x = 0; x < size; x++) {
					const index = (y * size + x) * 4 + 3; // Alpha channel
					if (pixels[index] > 128) { // Threshold for non-transparent
							left = Math.min(left, x);
							top = Math.min(top, y);
							right = Math.max(right, x);
							bottom = Math.max(bottom, y);
					}
			}
	}

	// Clean up
	renderTexture.destroy(true);
	tempCanvas.remove();

	return {
			left, top, 
			right, bottom, 
			width: right - left, 
			height: bottom - top
	};
}

// Create Optimized Occlusion Sprite
function createOptimizedOcclusionSprite(token, intersectingTiles) {
	if (!token.mesh || !token.mesh.texture) return null;

	// Clone token sprite
	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 advanced pixel-based mask
	const mask = createPixelMask(token, intersectingTiles);
	if (mask) {
			sprite.mask = mask;
	}

	// Custom shader filter for occlusion
	const occlusionFilter = createOcclusionShader();
	sprite.filters = [occlusionFilter];

	sprite.eventMode = 'passive';
	return sprite;
}

// Create Pixel-Based Mask
function createPixelMask(token, intersectingTiles) {
	const maskGraphics = new PIXI.Graphics();
	maskGraphics.beginFill(0xffffff);

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

			// Calculate precise intersection
			const intersectionX = Math.max(tokenBounds.x, tileBounds.x);
			const intersectionY = Math.max(tokenBounds.y, tileBounds.y);
			const intersectionWidth = Math.min(
					tokenBounds.x + tokenBounds.width, 
					tileBounds.x + tileBounds.width
			) - intersectionX;
			const intersectionHeight = Math.min(
					tokenBounds.y + tokenBounds.height, 
					tileBounds.y + tileBounds.height
			) - intersectionY;

			// Skip if no meaningful intersection
			if (intersectionWidth <= 0 || intersectionHeight <= 0) return;

			// Use advanced bounds calculation
			const tileSprite = new PIXI.Sprite(tile.texture);
			const spriteBounds = calculateOptimizedBounds(tileSprite, tile);

			if (spriteBounds) {
					maskGraphics.drawRect(
							intersectionX, 
							intersectionY, 
							intersectionWidth, 
							intersectionHeight
					);
			}
	});

	return maskGraphics;
}

// Create Custom Occlusion Shader
function createOcclusionShader() {
	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 sampler2D occlusionMask;

			void main(void) {
					vec4 color = texture2D(uSampler, vTextureCoord);
					vec4 maskColor = texture2D(occlusionMask, vTextureCoord);

					// Advanced occlusion logic
					float occlusionFactor = 1.0 - maskColor.a;
					vec3 occludedColor = color.rgb * (1.0 - occlusionFactor);

					gl_FragColor = vec4(occludedColor, color.a * (1.0 - occlusionFactor * 0.5));
			}
	`;

	return new PIXI.Filter(vertexShader, fragmentShader);
}

// Update Occlusion Layer
function updateOcclusionLayer() {
	// Ensure canvas is ready
	if (!canvas.ready) return;

	// Initialize if not set up
	if (!occlusionConfig.initialized) {
			initializeOcclusionContext();
	}

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

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

	tokens.forEach(token => {
			// Find intersecting tiles
			const intersectingTiles = tiles.filter(tile => 
					checkTokenTileIntersection(token, tile)
			);

			// Create occlusion sprite if intersections exist
			if (intersectingTiles.length > 0) {
					const occlusionSprite = createOptimizedOcclusionSprite(token, intersectingTiles);
					if (occlusionSprite) {
							occlusionConfig.tokensLayer.addChild(occlusionSprite);
					}
			}
	});

	// Render occlusion layer
	occlusionConfig.renderer.render(
			occlusionConfig.container, 
			{ renderTexture: occlusionConfig.renderTexture }
	);
}

// Intersection Check
function checkTokenTileIntersection(token, tile) {
	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
	);
}

// Global Hook Registration
function registerOcclusionLayerConfig() {
	const updateTriggers = [
			'canvasReady', 
			'canvasPan',
			'canvasTokensRefresh', 
			'updateToken', 
			'controlToken', 
			'refreshToken',
			'preUpdateScene',
			'updateScene'
	];

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

	// Initialize on canvas setup
	Hooks.once('ready', () => {
			if (canvas.ready) initializeOcclusionContext();
	});

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

// Export function to register module
export function registerOcclusionLayerConfigOcclusion4() {
	registerOcclusionLayerConfig();
}
