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

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

	async forceUpdateAllTiles() {
		// 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.#addTileToIndex(tile.document._id, i);
				}
			}
		}

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

	async handleCanvasRedraw() {
		this.unload_filters();
		await new Promise(resolve => setTimeout(resolve, 100));
      
		// Após um canvas.draw(), força uma atualização completa
		await this.forceUpdateAllTiles();
      
		// Reaplica os filtros
		canvas.tokens.objects.children.forEach(token => {
			this.refreshToken(token);
		});
	}

	//async createIndex() {
	async generateAlphaMaskIndex() {
		// 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 t = canvas.tiles.objects.children[i];
			if (t && t.document && !this.tileToIndexer.has(t.document._id)) {
				await this.#addTileToIndex(t.document._id, i);
			}
		}

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

	//async addTileInIndex(tile_id, index) {
	async #addTileToIndex(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.#cloneAlphaTileSprite(tile);
		if (cloned_tile) {
			this.container.addChild(cloned_tile);
			this.tileToIndexer.set(tile_id, [index, cloned_tile]);
			return [index, cloned_tile];
		}
	}

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

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

		this.#copyTileToSprite(sprite, tile);
		return sprite;
	}

	//cloneTileConfig(sprite, tile) {
	#copyTileToSprite(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;
	}
	// #copyTileToSprite(sprite, tile) {
	//     console.log('Original tile position:', {
	//       x: tile.x,
	//       y: tile.y,
	//       width: tile.width,
	//       height: tile.height,
	//       scale: tile.scale,
	//       anchor: tile.anchor,
	//       rotation: tile.rotation
	//     });

	//     const changed = setFields(sprite, tile, ["anchor", "width", "height", "position", "scale", "angle", "zIndex"]);

	//     console.log('Cloned sprite position:', {
	//       x: sprite.x,
	//       y: sprite.y,
	//       width: sprite.width,
	//       height: sprite.height,
	//       scale: sprite.scale,
	//       anchor: sprite.anchor,
	//       rotation: sprite.rotation
	//     });
	//     if (changed) {
	//         let filter = new PIXI.Filter(null,`
	//             varying vec2 vTextureCoord;
	//             uniform sampler2D uSampler;

	//             void main(void)
	//             {
	//                 mediump vec4 color = texture2D(uSampler, vTextureCoord);
	//                 gl_FragColor = vec4(color.a);
	//             }
	//         `);

	//         sprite.filters = [filter]
	//     }
	//     return changed;
	// }

	//async updateIndex(tile_id) {
	async reindexTile(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.#copyTileToSprite(cloned_tile, tile)) {
			await this.generateAlphaMaskIndex();
		}
	}

	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.#addTileToIndex(tile.id, this.tileToIndexer.size);
			await this.generateAlphaMaskIndex();
		});

		Hooks.on('updateTile', async (tile) => {
			await this.reindexTile(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();
			}
		});
	}

	async handleCanvasRedraw() {
		// Limpa filtros existentes antes do redesenho
		this.unload_filters();
		
		// Reinicializa após um pequeno delay para garantir que o canvas está pronto
		await new Promise(resolve => setTimeout(resolve, 100));
		await this.init();
		
		// Reaplica filtros em todos os tokens
		canvas.tokens.objects.children.forEach(token => {
			this.refreshToken(token);
		});
	}

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

	//handleRefreshToken(_token, skip_visible = false) {
	refreshToken(_token, skip_visible = false) {
		if (!this.renderTexture || !this.renderTexture.valid) return;

		let token = _token?.mesh ? _token : canvas.tokens.get(_token.id);
		if (!token?.mesh) return;
		if (!token.visible && !skip_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 => x && typeof x.getName === "function" && x.getName() === "TextureMaskFilter");
		let lastIndex = token.mesh.filters.length - 1;

		try {
			switch (occ_idx) {
				case -1:
					const textureMask = new TextureMaskFilter(this.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;
			}
		} catch (error) {
			console.warn('Error applying token filter:', error);
			// Remove o filtro problemático
			if (occ_idx >= 0) {
				token.mesh.filters[occ_idx] = null;
			}
		}
	}

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

		this.tileToIndexer.clear();
	}

	destructor() {
		// Remove hooks
		Hooks.off('createTile');
		Hooks.off('updateTile');
		Hooks.off('deleteTile');
		Hooks.off('updateToken');
		Hooks.off('refreshToken');
		Hooks.off('canvasReady');
		Hooks.off('canvasTearDown');
		Hooks.off('drawCanvas');
		
		this.cleanup();
	}

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

	debug() {
		// Adiciona uma borda vermelha ao container da máscara
		const graphics = new PIXI.Graphics();
		graphics.lineStyle(2, 0xFF0000);
		graphics.drawRect(0, 0, this.container.width, this.container.height);
		this.container.addChild(graphics);
  
		// Adiciona uma borda azul em cada tile clonado
		this.container.children.forEach(sprite => {
			if (sprite.isSprite) {
				const bounds = sprite.getBounds();
				const g = new PIXI.Graphics();
				g.lineStyle(2, 0x0000FF);
				g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
				this.container.addChild(g);
			}
		});
	}
}

























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 >= x){
			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(texture, x, y) {
		super(vertex, outline_occlusion_shader, undefined);

		this.maskTexture = texture;
		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;
	}

	calculateSpriteMatrix(outputMatrix, texture, filterManager) {
		const {sourceFrame, destinationFrame} = filterManager.activeState;
		const {orig} = texture;
		const mappedMatrix = outputMatrix.set(
			destinationFrame.width,
			0,
			0,
			destinationFrame.height,
			sourceFrame.x,
			sourceFrame.y
		);
		const worldTransform = canvas.stage.worldTransform.copyTo(PIXI.Matrix.TEMP_MATRIX);

		worldTransform.invert();
		mappedMatrix.prepend(worldTransform);
		mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height);

		return mappedMatrix;
	}

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

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

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

		this.uniforms.mask = tex;
		this.uniforms.otherMatrix = this
			.calculateSpriteMatrix(this.maskMatrix, tex, filterManager)
			.prepend(tex.uvMatrix.mapCoord);

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


// function setFields(sprite,tile,keys){
// 	let changed=false;
// 	for (const key of keys){
// 		let diff = false;
// 		if (sprite[key].equals && tile[key].equals){
// 			diff = (!sprite[key].equals(tile[key]));
// 		}
// 		else{
// 			diff = (sprite[key] !== tile[key]);
// 		}
// 		if (diff){
// 			changed=true
// 			sprite[key] = tile[key]
// 		}
// 	}
// 	return changed;
// }