import { Container, Graphics, Texture, Sprite } from 'pixi.js';
import { Grid } from './pathfinding';
import { IsoLayout, DEFAULT_ISO, isoToScreen } from '../math/iso';

/**
 * Высокий объект на тайле: рисуется спрайтом (якорь в центре ромба) или,
 * если текстуры нет, колонной высотой height мировых юнитов (·tileW px).
 * ground — id тайла, каким рисовать землю под объектом (по умолчанию — сам объект).
 */
export interface TallSpec {
    height: number;
    ground?: number;
}

/**
 * Изометрическая тайл-карта.
 * Данные карты — числа (id тайлов); отрисовка — по таблице id -> Texture.
 * Проходимость определяется набором блокирующих id; тайлы «высоких» объектов
 * (стены, деревья) рисуются поверх земли по строкам глубины.
 */
export interface TileMapData {
    width: number;
    height: number;
    /** Индексы тайлов, length = width * height. */
    tiles: number[];
    /** Тайлы с этими id непроходимы. */
    blocked: number[];
    /** Тайлы с этими id рисуются как вертикальные объекты. */
    tall?: Record<number, number | TallSpec>;
}

/** Спрайты одной ячейки — для перерисовки тайла без перестроения карты. */
interface CellSprites {
    ground?: Sprite;
    object?: Sprite;
}

export class IsometricTileMap implements Grid {
    readonly data: TileMapData;
    readonly iso: IsoLayout;
    readonly view: Container;

    private textures: Map<number, Texture>;
    private blockedSet: Set<number>;
    private tallSpecs: Map<number, TallSpec>;
    private groundLayer: Container;
    private objectsLayer: Container;
    private cells = new Map<number, CellSprites>();

    get width(): number {
        return this.data.width;
    }

    get height(): number {
        return this.data.height;
    }

    constructor(data: TileMapData, textures: Map<number, Texture>, iso: IsoLayout = DEFAULT_ISO) {
        this.data = data;
        this.iso = iso;
        this.textures = textures;
        this.blockedSet = new Set(data.blocked);
        this.tallSpecs = new Map(
            Object.entries(data.tall ?? {}).map(([id, spec]) => [
                Number(id),
                typeof spec === 'number' ? { height: spec } : spec
            ])
        );
        this.view = new Container();

        const ground = new Container();
        const objects = new Container();
        this.groundLayer = ground;
        this.objectsLayer = objects;
        this.view.addChild(ground, objects);

        // Сортировка глубины по диагонали (tx + ty) — классика изометрии.
        const cells: { tx: number; ty: number }[] = [];
        for (let ty = 0; ty < data.height; ty++) {
            for (let tx = 0; tx < data.width; tx++) {
                cells.push({ tx, ty });
            }
        }
        cells.sort((a, b) => a.tx + a.ty - (b.tx + b.ty));

        for (const cell of cells) {
            this.drawCell(cell.tx, cell.ty);
        }
    }

    /**
     * Изменить тайл: обновляет данные и перерисовывает одну ячейку
     * (сбор предметов, посадка цветов, разрушаемые стены и т.п.).
     */
    setTile(x: number, y: number, id: number): void {
        if (x < 0 || y < 0 || x >= this.data.width || y >= this.data.height) return;
        this.data.tiles[y * this.data.width + x] = id;
        this.drawCell(x, y);
    }

    /** Нарисовать (или перерисовать после setTile) одну ячейку карты. */
    private drawCell(tx: number, ty: number): void {
        const iso = this.iso;
        const id = this.data.tiles[ty * this.data.width + tx];
        const key = ty * this.data.width + tx;
        const old = this.cells.get(key);
        if (old?.ground) old.ground.destroy();
        if (old?.object) old.object.destroy();
        const fresh: CellSprites = {};
        this.cells.set(key, fresh);

        const tall = this.tallSpecs.get(id);
        const p = isoToScreen(tx, ty, iso);

        // Земля: под высоким объектом рисуем его ground-тайл (или плейсхолдер).
        const groundId = tall?.ground ?? id;
        const groundTex = this.textures.get(groundId);
        if (groundTex) {
            const s = new Sprite(groundTex);
            s.anchor.set(0.5, 0);
            s.position.set(p.x, p.y);
            this.groundLayer.addChild(s);
            fresh.ground = s;
        } else {
            // Нет текстуры — плейсхолдер-ромб: зелёный (проходимо) или коричневый (блок).
            const g = new Graphics();
            const hw = iso.tileW / 2;
            const hh = iso.tileH / 2;
            g.poly([p.x, p.y, p.x + hw, p.y + hh, p.x, p.y + iso.tileH, p.x - hw, p.y + hh]);
            g.fill(this.blockedSet.has(id) ? 0x4a3b2a : 0x2d5a27);
            this.groundLayer.addChild(g);
        }

        // Высокий объект: спрайт с якорем в центре ромба, иначе колонна.
        if (tall) {
            const objTex = this.textures.get(id);
            if (objTex) {
                const s = new Sprite(objTex);
                s.anchor.set(0.5, 1);
                s.position.set(p.x, p.y + iso.tileH / 2);
                this.objectsLayer.addChild(s);
                fresh.object = s;
            } else {
                const block = new Graphics();
                const h = tall.height * iso.tileW;
                const hw = iso.tileW / 2;
                const hh = iso.tileH / 2;
                block.poly([p.x, p.y - h, p.x + hw, p.y + hh - h, p.x, p.y + iso.tileH - h, p.x - hw, p.y + hh - h]);
                block.fill(0x6b5a44);
                block.poly([p.x - hw, p.y + hh - h, p.x, p.y + iso.tileH - h, p.x, p.y + iso.tileH, p.x - hw, p.y + hh]);
                block.fill(0x4a3b2a);
                block.poly([p.x + hw, p.y + hh - h, p.x, p.y + iso.tileH - h, p.x, p.y + iso.tileH, p.x + hw, p.y + hh]);
                block.fill(0x352a1e);
                this.objectsLayer.addChild(block);
            }
        }
    }

    isWalkable(x: number, y: number): boolean {
        if (x < 0 || y < 0 || x >= this.data.width || y >= this.data.height) return false;
        const id = this.data.tiles[y * this.data.width + x];
        return !this.blockedSet.has(id);
    }

    /** Границы карты в мировых юнитах (для ограничения камеры). */
    get worldBounds(): { x: number; y: number; width: number; height: number } {
        return { x: 0, y: 0, width: this.data.width, height: this.data.height };
    }
}