import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { OptionsComponent } from '../options/options.component';
import { GameState } from './gameState';
import { SimulationEngine } from './simulationEngine';
import { GameRules } from './gameRules';
import { Command } from './command';

@Component({
	selector: 'app-game',
	templateUrl: './game.component.html',
	styleUrls: ['./game.component.css'],
	imports: [OptionsComponent],
})
export class GameComponent implements OnInit {
	engine!: SimulationEngine;
	private optionsOpen = false;
	private isPaused = false;
	private lastFrameTime = 0;

	@ViewChild('gameCanvas') canvasRef!: ElementRef<HTMLCanvasElement>;
	@ViewChild('canvasWrapper') wrapperRef!: ElementRef<HTMLDivElement>;
	private logicalWidth = 800;
	private logicalHeight = 600;
	scaleX: number = 1;
	scaleY: number = 1;
	pixelScale: number = 1;

	async ngOnInit() {
		const initialState: GameState = {
			tick: 0,
			units: [{ id: 'u1', x: 0, y: 0 }],
			commandHistory: [],
		};

		const json = await fetch('/assets/gameData.json').then((r) => r.json());
		this.engine = new SimulationEngine(initialState, GameRules, json);
		requestAnimationFrame(this.gameLoop.bind(this));
	}

	start() {
		this.engine.start();
	}

	stop() {
		this.engine.stop();
	}

	step() {
		this.engine.step();
	}

	rewind() {
		this.engine.rewindToZero();
	}

	fastForward() {
		this.engine.fastForwardToEnd();
	}

	async reloadGameData() {
		const json = await fetch('/assets/gameData.json').then((r) => r.json());
		this.engine.reinitializeWithNewData(json);
	}

	issueMove() {
		const cmd: Command = {
			step: this.engine.currentStep + 1,
			type: 'move',
			payload: { id: 'u1', dx: 1, dy: 0 },
		};
		this.engine.issueCommand(cmd);
	}

	ngAfterViewInit(): void {
		this.resizeCanvas();
		requestAnimationFrame(this.render);
		window.addEventListener('resize', this.resizeCanvas.bind(this));
	}

	render = (now: number) => {
		if (!this.engine) return;

		const [prevState, currState] = this.engine.getRenderStates();

		// Compute interpolation factor (0..1)
		const lastStepTime = this.engine.isRunning ? this.engine.lastStepTime : now;
		const t = this.engine.isRunning
			? Math.max(Math.min((now - lastStepTime) / this.engine.interval, 1), 0)
			: 1; // fully render the last known state

		this.renderFrame(prevState, currState, t);

		requestAnimationFrame(this.render);
	};

	renderFrame(prev: GameState, curr: GameState, t: number) {
		const ctx = this.canvasRef.nativeElement.getContext('2d');
		if (!ctx) return;

		const canvas = this.canvasRef.nativeElement;
		ctx.clearRect(0, 0, canvas.width, canvas.height);

		for (let i = 0; i < curr.units.length; i++) {
			const currUnit = curr.units[i];
			const prevUnit =
				prev.units.find((u) => u.id === currUnit.id) ?? currUnit;

			const x = prevUnit.x + (currUnit.x - prevUnit.x) * t;
			const y = prevUnit.y + (currUnit.y - prevUnit.y) * t;

			ctx.fillStyle = 'cyan';
			ctx.beginPath();
			ctx.arc(x * 10, y * 10, 5, 0, Math.PI * 2);
			ctx.fill();
		}
	}

	resizeCanvas(): void {
		const wrapper = this.wrapperRef.nativeElement;
		const canvas = this.canvasRef.nativeElement;

		const targetAspect = this.logicalWidth / this.logicalHeight;
		const maxW = wrapper.clientWidth;
		const maxH = wrapper.clientHeight;

		let width = maxW;
		let height = width / targetAspect;

		if (height > maxH) {
			height = maxH;
			width = height * targetAspect;
		}

		canvas.width = width;
		canvas.height = height;

		this.scaleX = canvas.width / this.logicalWidth; // e.g. 800
		this.scaleY = canvas.height / this.logicalHeight; // e.g. 600

		// Optionally use the smaller of the two for uniform scaling
		this.pixelScale = Math.min(this.scaleX, this.scaleY);
	}

	toScreen(x: number, y: number): [number, number] {
		return [x * this.pixelScale, y * this.pixelScale];
	}

	fromScreen(px: number, py: number): [number, number] {
		return [px / this.pixelScale, py / this.pixelScale];
	}

	private gameLoop(currentTime: number): void {
		if (this.lastFrameTime === 0) {
			this.lastFrameTime = currentTime;
		}
		const deltaTime = (currentTime - this.lastFrameTime) / 1000;
		this.lastFrameTime = currentTime;

		// Only update the game state if not paused
		if (!this.isPaused) {
			this.updateGame(deltaTime);
			requestAnimationFrame(this.gameLoop.bind(this));
		}
	}

	private updateGame(deltaTime: number): void {
		// Implement your game update logic here using deltaTime
	}

	// Called when opening options: pause the game
	public openOptions(): void {
		this.optionsOpen = true;
		this.pauseGame();
	}

	// Called when closing options: resume the game
	public closeOptions(): void {
		this.optionsOpen = false;
		this.resumeGame();
	}

	private pauseGame(): void {
		this.isPaused = true;
	}

	private resumeGame(): void {
		this.isPaused = false;
		// Reset the lastFrameTime to avoid a huge delta on resume
		this.lastFrameTime = performance.now();
		requestAnimationFrame(this.gameLoop.bind(this));
	}
}
