export function registerOcclusionConfig() {
	// Only enable if the setting is turned on
	//const enableOcclusionLayer = game.settings.get(MODULE_ID, "enableOcclusionLayer");
	//if (!enableOcclusionLayer) return;

	Hooks.once('ready', () => {
		TokenOccluder.init();
	});
}

export class TokenOccluder {
	constructor() {
		this.renderer = canvas.app.renderer;
		this.renderTexture = PIXI.RenderTexture.create({
				width: canvas.dimensions.width, 
				height: canvas.dimensions.height
		});
		this.container = new PIXI.Container();
		this.container.sortableChildren = true;
		this.tileToIndexer = new Map();
		this.rendered = false;
		this._initialized = false;
	}

	static async init() {
		if (!game.tokenOccluder) {
			game.tokenOccluder = new TokenOccluder();
			await game.tokenOccluder.init();
		}
		return game.tokenOccluder;
	}

	async init() {
		if (this._initialized) {
			return;
		}

		try {
			// Recria os objetos necessários se foram destruídos
			if (!this.container) {
				this.container = new PIXI.Container();
				this.container.sortableChildren = true;
			}

			if (!this.renderTexture || this.renderTexture.destroyed) {
				this.renderTexture = PIXI.RenderTexture.create({
					width: canvas.dimensions.width, 
					height: canvas.dimensions.height
				});
			}

			if (!this.tileToIndexer) {
				this.tileToIndexer = new Map();
			}

			// Aguarda um tick para garantir que os tiles estejam completamente carregados
			await new Promise(resolve => setTimeout(resolve, 0));
      
			// Força uma atualização inicial em todos os tiles
			await this.forceUpdateAllTiles();
      
			this.registerHooks();
			this._initialized = true;

		} catch (error) {
			console.error("TokenOccluder: Error in init", error);
			this.cleanup();
		}
	}

	async forceUpdateAllTiles() {
		// Guard clause to prevent null reference
		if (!this.container || !this.tileToIndexer) {
			console.warn("TokenOccluder: Container or tileToIndexer is not initialized");
			return;
		}

		try {
			// Limpa o estado atual
			this.container.removeChildren();
			this.tileToIndexer.clear();

			// Força uma atualização em cada tile
			const tiles = canvas.tiles?.objects?.children || [];
			for (let i = 0; i < tiles.length; i++) {
				const tile = tiles[i];
				if (tile && tile.document) {
					// Força o tile a atualizar sua posição
					await tile.document.update({}, {recursive: false});
					
					// Adiciona ao índice
					if (!this.tileToIndexer.has(tile.document._id)) {
						await this.addTileInIndex(tile.document._id, i);
					}
				}
			}

			// Renderiza a textura final
			if (this.renderer && this.renderTexture && this.renderTexture.valid) {
				this.renderer.render(this.container, { renderTexture: this.renderTexture });
			}
		} catch (error){
			console.error("TokenOccluder: Error in forceUpdateAllTiles", error);
			await this.cleanup();
			await this.init();
		}
	}

	async handleCanvasRedraw() {
		try {
			// Limpa filtros existentes antes do redesenho
			this.removeFilters();
		
			// Reinicializa após um pequeno delay para garantir que o canvas está pronto
			await new Promise(resolve => setTimeout(resolve, 100));
      
			// Reinicializa o objeto
			if (!this._initialized) {
				await this.init();
			} else {
				await this.forceUpdateAllTiles();
			}
	
			// Reaplica filtros em todos os tokens
			if (canvas.tokens?.objects?.children) {
				canvas.tokens.objects.children.forEach(token => {
					this.refreshToken(token);
				});
			}
		} catch (error) {
			console.error("TokenOccluder: Error in handleCanvasRedraw", error);
			this._initialized = false;
		}
	}

	async createIndex() {
		// Limpa o container antes de regenerar
		this.container.removeChildren();
		this.tileToIndexer.clear();

		// Regenera os índices
		for (let i = 0; i < canvas.tiles.objects.children.length; i++) {
			const tiles = canvas.tiles.objects.children[i];
			if (tiles && tiles.document && !this.tileToIndexer.has(tiles.document._id)) {
				await this.addTileInIndex(tiles.document._id, i);
			}
		}

		// Renderiza a textura
		if (this.renderer && this.renderTexture) {
			this.renderer.render(this.container, { renderTexture: this.renderTexture });
		}
	}

	async addTileInIndex(tile_id, index) {
		let tile = canvas.primary.tiles.get(`Tile.${tile_id}`);
		if (!tile || !tile.texture) return;

		// Aguarda a textura carregar se necessário
		if (!tile.texture.valid) {
				await new Promise(resolve => tile.texture.once('update', resolve));
		}

		let cloned_tile = this.createTileSprite(tile);
		if (cloned_tile) {
			this.container.addChild(cloned_tile);
			this.tileToIndexer.set(tile_id, [index, cloned_tile]);
			return [index, cloned_tile];
		}
	}

	createTileSprite(tile) {
		if (!tile.texture || !tile.texture.valid) return null;

		let sprite = PIXI.Sprite.from(tile.texture);
		sprite.isSprite = true;
		sprite.name = tile.id;

		this.cloneTileConfig(sprite, tile);
		return sprite;
	}

	cloneTileConfig(sprite, tile) {
		if (!sprite || !tile) return false;

		sprite.position.set(tile.x, tile.y);
		sprite.scale.set(tile.scale.x, tile.scale.y);
		sprite.rotation = tile.rotation;
		sprite.anchor.set(tile.anchor.x, tile.anchor.y);
		sprite.width = tile.width;
		sprite.height = tile.height;

		return true;
	}

	async updateIndex(tile_id) {
		if (!this.tileToIndexer.has(tile_id)) return;

		let [indexer_index, cloned_tile] = this.tileToIndexer.get(tile_id);
		let tile = canvas.primary.tiles.get(`Tile.${tile_id}`);
		if (!tile) return;

		if (this.cloneTileConfig(cloned_tile, tile)) {
			await this.createIndex();
		}
	}

	//handleRefreshToken(_token, skip_visible = false) {
	refreshToken(token) {
		if (!this.renderTexture || !this.renderTexture.valid) return;
		if (!token?.mesh || !token.visible) return;
		
		
		let x = token.center.x;
		let y = token.center.y;

		if (x < 0) x = 0;
		if (x > canvas.dimensions.width) x = canvas.dimensions.width;
		if (y < 0) y = 0;
		if (y > canvas.dimensions.height) y = canvas.dimensions.height;

		x = x / canvas.dimensions.width;
		y = y / canvas.dimensions.height;

		
		if (!token.mesh.filters) {
			token.mesh.filters = [];
		}

		let occ_idx = token.mesh.filters.findIndex(x => typeof x.getName === "function" && x.getName() === "TextureMaskFilter");
		let lastIndex = token.mesh.filters.length - 1;

		switch (occ_idx) {
				case -1:
						const renderTexture = this.renderTexture;
						const textureMask = new TextureMaskFilter(renderTexture, x, y);
						token.mesh.filters.push(textureMask);
						break;
				case lastIndex:
						token.mesh.filters[occ_idx].updateUniforms(x, y);
						break;
				default:
						const [existingFilter] = token.mesh.filters.splice(occ_idx, 1);
						token.mesh.filters.push(existingFilter);
						token.mesh.filters[lastIndex].updateUniforms(x, y);
						break;
		}
	}







	registerHooks() {
		// Hooks para o canvas
		Hooks.on('canvasReady', () => this.init());
		Hooks.on('canvasTearDown', () => this.cleanup());
		Hooks.on('drawCanvas', () => this.handleCanvasRedraw());
		
		// Hooks para tiles
		Hooks.on('createTile', async (tile) => {
			await this.addTileInIndex(tile.id, this.tileToIndexer.size);
			await this.createIndex();
		});

		Hooks.on('updateTile', async (tile) => {
			await this.updateIndex(tile.id);
			await this.forceUpdateAllTiles();
		});

		Hooks.on('deleteTile', async (tile) => {
			if (this.tileToIndexer.has(tile.id)) {
				const [_, sprite] = this.tileToIndexer.get(tile.id);
				this.container.removeChild(sprite);
				this.tileToIndexer.delete(tile.id);
				await this.forceUpdateAllTiles();
			}
		});
		
		// Hooks para tokens
		Hooks.on('updateToken', (token) => this.refreshToken(token));
		Hooks.on('refreshToken', (token) => this.refreshToken(token));

		Hooks.on('lightingRefresh', async () => {
			if (this._initialized) {
					await this.forceUpdateAllTiles();
			}
		});
	}

	cleanup() {
		this.removeFilters();
		
		if (this.renderTexture) {
			this.renderTexture.destroy(true);
			this.renderTexture = null;
		}
		
		if (this.container) {
			this.container.removeChildren();
			this.container.destroy(true);
			this.container = null;
		}

		if (this.tileToIndexer) {
			this.tileToIndexer.clear();
		}

		this._initialized = false;
	}

	removeFilters() {
		if (!canvas?.tokens?.objects?.children) return;

		canvas.tokens.objects.children.forEach(token => {
			if (!token?.mesh?.filters) return;
			
			let index = token.mesh.filters.findIndex(x => x && typeof x.getName === "function" && x.getName() === "TextureMaskFilter");
			if (index >= 0) {
				token.mesh.filters[index] = null;
			}
		});
	}
}

























export const vertex = `
	attribute vec2 aVertexPosition;
	attribute vec2 aTextureCoord;

	uniform mat3 projectionMatrix;
	uniform mat3 otherMatrix;

	varying vec2 vMaskCoord;
	varying vec2 vTextureCoord;

	void main(void)
	{
		gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
		vTextureCoord = aTextureCoord;
		vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0)  ).xy;
	}
`

const outline_occlusion_shader = `
	uniform sampler2D uSampler;
	uniform sampler2D mask;
	uniform vec4 fill_color;
	uniform float x;
	uniform float y;
	varying vec2 vTextureCoord;
	varying vec2 vMaskCoord;

	void main(void) {
		vec4 color = texture2D(uSampler, vTextureCoord);
		vec4 mask = texture2D(mask, vMaskCoord);
		if (mask.a >= 0.1){
			float output_alpha = fill_color.a;
			vec3 output_color = fill_color.rgb * output_alpha;
			gl_FragColor = vec4(output_color.rgb*color.rgb, output_alpha)*(color.a);
		} 
		else {
			gl_FragColor = color;
		}
	}
`;

export class TextureMaskFilter extends PIXI.Filter {
	constructor(renderTexture, x, y) {
		super(vertex, outline_occlusion_shader, undefined);

		this.renderTexture = renderTexture;
		this.maskMatrix = new PIXI.Matrix();
		this.uniforms.fill_color = new Float32Array([0.0, 0.0, 0.9, 1.0]);

		this.updateUniforms(x, y);
	}

	getName() {
		return "TextureMaskFilter";
	}

	updateUniforms(x, y) {
		this.uniforms.x = x;
		this.uniforms.y = y;
	}

	apply(filterManager, input, output, clearMode) {
		const renderTexture = this.renderTexture;

		if ((!renderTexture) || (!renderTexture.valid) || (!renderTexture.baseTexture) || (renderTexture.baseTexture.destroyed)) {
			console.warn("bad texture");
			return;
		}

		if (!renderTexture.uvMatrix) {
			renderTexture.uvMatrix = new PIXI.TextureMatrix(renderTexture, 0.0);
		}
		renderTexture.uvMatrix.update();

		// Calculate the sprite matrix
		const a = filterManager.activeState.destinationFrame.width;
		const d = filterManager.activeState.destinationFrame.height;
		const tx = filterManager.activeState.sourceFrame.x;
		const ty = filterManager.activeState.sourceFrame.y;
		const worldTransform = canvas.stage.worldTransform.copyTo(PIXI.Matrix.TEMP_MATRIX);

		// Prepend the UV map coordinates
		this.uniforms.mask = renderTexture;

		this.uniforms.otherMatrix = this.maskMatrix
			.set(a, 0, 0, d, tx, ty)
			.prepend(worldTransform.invert())
			.scale(1/renderTexture.width, 1/renderTexture.height)
			.prepend(renderTexture.uvMatrix.mapCoord);

		filterManager.applyFilter(this, input, output, clearMode);
	}
}