import { Assets, Spritesheet, Texture } from 'pixi.js';

/**
 * Загрузчик ассетов поверх Assets из PixiJS: группы, прогресс, готовые Texture.
 * URL ассетов передаётся функцией-резолвером (приложение решает, где лежат файлы).
 * Атласы (Spritesheet JSON + PNG) загружаются тем же механизмом: loadAtlas().
 */
export class AssetLoader {
    private textures = new Map<string, Texture>();
    private atlases = new Map<string, Spritesheet>();
    private initPromise: Promise<void> | null = null;

    constructor(private resolveUrl: (key: string) => string) {}

    /** Assets требует однократной инициализации до первой загрузки. */
    private ensureInit(): Promise<void> {
        if (!this.initPromise) {
            this.initPromise = Assets.init({});
        }
        return this.initPromise;
    }

    /** Загрузить группу ключей параллельно; onProgress получает долю 0..1. */
    async load(keys: string[], onProgress?: (p: number) => void): Promise<void> {
        await this.ensureInit();
        const urls = keys.map((key) => this.resolveUrl(key));
        let loaded: Record<string, Texture>;
        try {
            loaded = await Assets.load<Texture>(urls, onProgress);
        } catch (err) {
            throw new Error(`Не удалось загрузить ассеты (${urls.join(', ')}): ${String(err)}`, { cause: err });
        }
        keys.forEach((key, i) => {
            const tex = loaded[urls[i]!];
            if (!tex) throw new Error(`Не удалось загрузить ${urls[i]}: нет результата по URL`);
            // Пиксель-арт мира: nearest — чёткие квадратные пиксели на любом масштабе.
            tex.source.scaleMode = 'nearest';
            this.textures.set(key, tex);
        });
    }

    /**
     * Загрузить атлас (Spritesheet JSON). JSON должен ссылаться на PNG
     * относительным путём — Pixi подгрузит картинку сам.
     * Ключ резолвится как есть: приложение должно дать URL на .json
     * (например, ключ 'chars/hero_sheet.json').
     */
    async loadAtlas(key: string): Promise<Spritesheet> {
        await this.ensureInit();
        const existing = this.atlases.get(key);
        if (existing) return existing;
        const sheet = await Assets.load<Spritesheet>(this.resolveUrl(key));
        if (!(sheet instanceof Spritesheet) || !sheet.textures) {
            throw new Error(`Атлас ${key} не распознан как Spritesheet (проверьте URL и формат JSON)`);
        }
        // Кадры атласа — тоже nearest (общий источник, но пройдёмся по всем на всякий случай).
        for (const tex of Object.values(sheet.textures)) {
            tex.source.scaleMode = 'nearest';
        }
        this.atlases.set(key, sheet);
        return sheet;
    }

    /**
     * Кадры анимации из загруженного атласа: все кадры, чьё имя начинается
     * с prefix, отсортированные по числу в конце имени (hero_walk_1, hero_walk_2, ...).
     */
    frames(key: string, prefix: string): Texture[] {
        const sheet = this.atlases.get(key);
        if (!sheet) throw new Error(`Атлас не загружен: ${key}`);
        const entries = Object.keys(sheet.textures)
            .filter((name) => name.startsWith(prefix))
            .sort((a, b) => {
                const na = Number(a.match(/(\d+)$/)?.[1] ?? -1);
                const nb = Number(b.match(/(\d+)$/)?.[1] ?? -1);
                return na - nb;
            });
        if (entries.length === 0) {
            throw new Error(`В атласе ${key} нет кадров с префиксом ${prefix}`);
        }
        return entries.map((name) => sheet.textures[name]!);
    }

    /** Готовая анимация из sheet.animations (если генератор заполняет animations). */
    animation(key: string, name: string): Texture[] {
        const sheet = this.atlases.get(key);
        if (!sheet) throw new Error(`Атлас не загружен: ${key}`);
        const frames = sheet.animations[name];
        if (!frames) throw new Error(`В атласе ${key} нет анимации ${name}`);
        return frames;
    }

    /**
     * Размер кадра атласа из JSON (sourceSize: номинал ячейки до обрезки).
     * Незнание имени кадра — размер первого кадра атласа.
     */
    frameSize(key: string, frameName?: string): { w: number; h: number } {
        const sheet = this.atlases.get(key);
        if (!sheet) throw new Error(`Атлас не загружен: ${key}`);
        const name = frameName ?? Object.keys(sheet.data.frames)[0];
        const frame = sheet.data.frames[name];
        if (!frame?.sourceSize) throw new Error(`В атласе ${key} нет кадра ${name} (или без sourceSize)`);
        return { w: frame.sourceSize.w, h: frame.sourceSize.h };
    }

    texture(key: string): Texture {
        const t = this.textures.get(key);
        if (!t) throw new Error(`Текстура не загружена: ${key}`);
        return t;
    }

    has(key: string): boolean {
        return this.textures.has(key);
    }
}