import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

interface AssetEntry {
	path: string;
	type: 'image' | 'audio';
	size: number;
}

@Injectable({ providedIn: 'root' })
export class AssetPreloaderService {
	private totalBytes = 0;
	private loadedBytes = 0;

	private _progress = new BehaviorSubject<number>(0);
	progress$ = this._progress.asObservable();

	private images = new Map<string, HTMLImageElement>();
	private audio = new Map<string, HTMLAudioElement>();

	async preload(): Promise<void> {
		const manifest: AssetEntry[] = await fetch(
			'/assets/manifest.json'
		).then((res) => res.json());
		this.totalBytes = manifest.reduce((sum, asset) => sum + asset.size, 0);

		const loadTasks = manifest.map((entry) =>
			this.loadAsset(entry).then((blob) => {
				const url = URL.createObjectURL(blob);
				if (entry.type === 'image') {
					const img = new Image();
					const loadedPromise = new Promise<void>((resolve, reject) => {
						img.onload = () => {
							URL.revokeObjectURL(url);
							resolve();
						};
						img.onerror = (e) => {
							console.error(`Failed to load image: ${entry.path}`, e);
							URL.revokeObjectURL(url);
							reject(e);
						};
					});
					img.src = url;
					return loadedPromise.then(() => {
						this.images.set(entry.path, img);
					});
				} else if (entry.type === 'audio') {
					const audio = new Audio();
					const loadedPromise = new Promise<void>((resolve, reject) => {
						audio.oncanplaythrough = () => resolve();
						audio.onerror = (e) => {
							console.error(`Failed to load audio: ${entry.path}`, e);
							resolve(); // Don't reject to avoid blocking all
						};
					});
					audio.src = url;
					return loadedPromise.then(() => {
						this.audio.set(entry.path, audio);
					});
				}
				return Promise.resolve();
			})
		);

		await Promise.all(loadTasks);
	}

	private async loadAsset(entry: AssetEntry): Promise<Blob> {
		const response = await fetch(`/assets/${entry.path}`);
		if (!response.ok) {
			throw new Error(`Failed to fetch asset: ${entry.path}`);
		}
		const contentType = response.headers.get('Content-Type') || undefined;
		const reader = response.body?.getReader();
		let loaded = 0;
		const chunks = [];

		while (true) {
			const { done, value } = await reader!.read();
			if (done) break;
			chunks.push(value);
			loaded += value.length;
			this.loadedBytes += value.length;
			this._progress.next(this.loadedBytes / this.totalBytes);
		}

		return new Blob(chunks, contentType ? { type: contentType } : undefined);
	}

	getImage(name: string): HTMLImageElement {
		return this.images.get(name)!;
	}

	getAudio(name: string): HTMLAudioElement {
		return this.audio.get(name)!;
	}
}
