import Player from '../entities/Player.js';
import ButtonBarrierSystem from '../systems/ButtonBarrierSystem.js';
import TilePropertyHelper, {
    FLOOR_GIDS,
    SOLID_FLOOR_GID,
    LADDER_TOP_GID,
} from '../systems/TilePropertyHelper.js';

export default class Level1Scene extends Phaser.Scene {
    constructor() {
        super({ key: 'Level1Scene' });
    }

    create() {
        // -------------------------
        // Tilemap — patch external tileset reference
        // -------------------------
        // levels-a.json references platforms.tsx as an external source.
        // Phaser cannot fetch .tsx files, so the tileset object in the JSON cache
        // has no `tiles` array, causing a crash when the parser tries tileset.tiles[n].
        // We replace the external reference with the required metadata before parsing.
        const cachedMap = this.cache.tilemap.get('level1');
        if (cachedMap) {
            // Fix 1: Replace external tileset source reference with embedded metadata.
            // Phaser cannot fetch .tsx files, so the JSON tileset object has no `tiles`
            // array, causing tileset.tiles[n] to throw during parsing.
            cachedMap.data.tilesets = cachedMap.data.tilesets.map(ts => {
                if (!ts.source) return ts;
                const name = ts.source.split('/').pop().replace(/\.\w+$/, '');
                return {
                    firstgid: ts.firstgid,
                    name,
                    columns: 10,
                    tilecount: 100,
                    tileheight: 128,
                    tilewidth: 128,
                    image: '../sprites/platforms.png',
                    imageheight: 1280,
                    imagewidth: 1280,
                    margin: 0,
                    spacing: 0,
                    tiles: [],
                };
            });

            // Fix 2: Normalize startx/starty on every infinite-map layer.
            // Phaser places chunk data into the layer data array at index
            //   [chunk.y - starty][chunk.x - startx]
            // then renders data[row][col] at world pixel (col*tw, row*th).
            // The floor layer has startx=-16, starty=-16 even though all chunks
            // sit at x>=0, y>=0 — this pushes floor tiles to data rows 30+ which
            // maps to world y ~3840px, far off-screen. Setting startx/starty to the
            // actual minimum chunk coordinates fixes the placement.
            for (const layer of cachedMap.data.layers) {
                if (layer.type !== 'tilelayer' || !layer.chunks || !layer.chunks.length) continue;
                let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
                for (const chunk of layer.chunks) {
                    minX = Math.min(minX, chunk.x);
                    minY = Math.min(minY, chunk.y);
                    maxX = Math.max(maxX, chunk.x + chunk.width);
                    maxY = Math.max(maxY, chunk.y + chunk.height);
                }
                layer.startx = minX;
                layer.starty = minY;
                layer.width  = maxX - minX;
                layer.height = maxY - minY;
            }
        }

        // -------------------------
        // Background image
        // -------------------------
        this.add.image(0, 0, 'level1-bg').setOrigin(0, 0).setDisplaySize(30 * 128, 20 * 128).setScrollFactor(0.3);

        const map = this.make.tilemap({ key: 'level1' });

        // 'platforms' = name Phaser derives from the external tileset source 'platforms.tsx'
        // If tiles render blank, verify with: console.log(map.tilesets[0].name)
        const tileset = map.addTilesetImage('platforms', 'platforms', 128, 128, 0, 0);

        this._floorLayer = map.createLayer('floor', tileset, 0, 0);
        this._platformsLayer = map.createLayer('platforms', tileset, 0, 0);
        this._laddersLayer = map.createLayer('ladders', tileset, 0, 0);
        this._barriersLayer = map.createLayer('barriers', tileset, 0, 0);

        // -------------------------
        // Tile collision setup
        // -------------------------

        // Floor layer: one-way — only collide when player falls down
        this._floorLayer.setCollision(FLOOR_GIDS);

        // Platforms layer: floor tiles (one-way) + the one solid wall tile
        this._platformsLayer.setCollision([...FLOOR_GIDS, SOLID_FLOOR_GID]);

        // Ladder layer: only the top rung acts as a one-way surface
        this._laddersLayer.setCollision([LADDER_TOP_GID]);

        // Barrier layer: initial collision set by ButtonBarrierSystem.init()

        // -------------------------
        // Player
        // -------------------------
        // Spawn at tile (3, 11) — center of tile in world pixels
        const spawnX = 3 * 128 + 64; // 448
        const spawnY = 11 * 128 + 64; // 1472
        this._player = new Player(this, spawnX, spawnY, this._laddersLayer);
        const playerSprite = this._player.sprite;

        // -------------------------
        // Button / Barrier system
        // -------------------------
        this._bbSystem = new ButtonBarrierSystem(this, this._barriersLayer);
        this._bbSystem.init();

        // -------------------------
        // Physics colliders
        // -------------------------

        // Floor layer: one-way (pass through from below, land from above)
        this.physics.add.collider(
            playerSprite,
            this._floorLayer,
            null,
            (player, tile) => player.body.velocity.y >= 0,
            this
        );

        // Platforms layer: one-way for floor tiles; full collision for solid wall tile
        this.physics.add.collider(
            playerSprite,
            this._platformsLayer,
            null,
            (player, tile) => {
                if (tile.index === SOLID_FLOOR_GID) return true;
                return player.body.velocity.y >= 0;
            },
            this
        );

        // Ladder layer: top rung as one-way floor; disabled while actively climbing
        this.physics.add.collider(
            playerSprite,
            this._laddersLayer,
            null,
            (player, tile) => !this._player.onLadder && player.body.velocity.y >= 0,
            this
        );

        // Barriers layer: dynamic — processCallback checks current solid state per tile
        this.physics.add.collider(
            playerSprite,
            this._barriersLayer,
            null,
            (player, tile) => {
                const props = TilePropertyHelper.getPropsByGID(tile.index);
                return !!(props && props.solid);
            },
            this
        );

        // -------------------------
        // Camera
        // -------------------------
        const worldW = map.width * map.tileWidth;  // 30 * 128 = 3840
        const worldH = map.height * map.tileHeight; // 20 * 128 = 2560
        this.cameras.main
            .setBounds(0, 0, worldW, worldH)
            .startFollow(playerSprite, true, 0.1, 0.1);
        this.physics.world.setBounds(0, 0, worldW, worldH);

        // -------------------------
        // HUD
        // -------------------------
        this._livesText = this.add
            .text(16, 16, 'Lives: 3', {
                fontFamily: 'monospace',
                fontSize: '28px',
                color: '#ffffff',
                stroke: '#000000',
                strokeThickness: 4,
            })
            .setScrollFactor(0)
            .setDepth(100);

        // Track velocity from the previous frame for button-landing detection
        this._prevVelocityY = 0;
    }

    update(time, delta) {
        this._player.update(time, delta);
        this._checkButtonPress();

        this._livesText.setText('Lives: ' + this._player.lives);

        // Store velocity at END of frame so next frame's _checkButtonPress sees the
        // pre-landing velocity. Physics zeroes velocity.y before update() is called,
        // so capturing it here (post-physics, post-input) gives the correct falling speed.
        this._prevVelocityY = this._player.body.velocity.y;
    }

    /**
     * Detect when the player lands on top of an unpressed button tile and press it.
     * Uses prevVelocityY to distinguish a real landing from walking across the top.
     */
    _checkButtonPress() {
        const body = this._player.body;

        // Player must be touching the ground this frame
        if (!body.blocked.down) return;
        // Must have been falling (not just standing; threshold filters walking-on-top)
        if (this._prevVelocityY < 50) return;

        const sprite = this._player.sprite;
        // Sample a point just below the player's feet center
        const feetX = sprite.x;
        const feetY = sprite.y + 120; // approx bottom of physics body

        const tile = this._barriersLayer.getTileAtWorldXY(feetX, feetY + 4);
        if (!tile || tile.index <= 0) return;

        if (this._bbSystem.isButtonTile(tile.index)) {
            this._bbSystem.pressButton(tile.x, tile.y);
        }
    }
}
