/**
 * qix_entities.js
 * 新敵 TrackerEnemy (防衛本能) 追加版
 */

class Particle {
    constructor(x, y, color) {
        this.x = x; this.y = y;
        const angle = Math.random() * Math.PI * 2;
        const speed = Math.random() * 5 + 2;
        this.vx = Math.cos(angle) * speed; this.vy = Math.sin(angle) * speed;
        this.life = 1.0; this.decay = Math.random() * 0.03 + 0.01;
        this.color = color; this.size = Math.random() * 3 + 2;
    }
    update() {
        this.x += this.vx; this.y += this.vy;
        this.vx *= 0.95; this.vy *= 0.95; this.life -= this.decay;
    }
}

class Projectile {
    constructor(x, y, vx, vy, size, color, length = 0) {
        this.x = x; this.y = y;
        this.vx = vx; this.vy = vy;
        this.size = size; this.color = color;
        this.length = length; 
        this.active = true;
    }

    update(gridSystem) {
        const speed = Math.hypot(this.vx, this.vy);
        const steps = Math.ceil(speed); 
        const dx = this.vx / steps;
        const dy = this.vy / steps;

        for (let i = 0; i < steps; i++) {
            this.x += dx;
            this.y += dy;
            
            if (this.x < 0 || this.x >= CONFIG.CANVAS_SIZE || this.y < 0 || this.y >= CONFIG.CANVAS_SIZE) {
                this.active = false;
                return null;
            }
            
            const gx = Math.floor(this.x);
            const gy = Math.floor(this.y);

            if (gridSystem.isValid(gx, gy)) {
                const cell = gridSystem.get(gx, gy);
                if (cell === CONFIG.STATE.WALL) {
                    this.active = false;
                    return { hitWall: true };
                }
                if (cell === CONFIG.STATE.TRAIL) {
                    this.active = false;
                    return { hitTrail: true };
                }
            }
        }
        return null;
    }
    
    draw(ctx) {
        ctx.shadowBlur = 10; 
        ctx.shadowColor = this.color;

        if (this.length > 0) {
            const angle = Math.atan2(this.vy, this.vx);
            const tailX = this.x - Math.cos(angle) * this.length;
            const tailY = this.y - Math.sin(angle) * this.length;

            ctx.lineCap = 'round';
            ctx.beginPath();
            ctx.lineWidth = this.size; 
            ctx.strokeStyle = this.color;
            ctx.moveTo(tailX, tailY);
            ctx.lineTo(this.x, this.y);
            ctx.stroke();

            ctx.beginPath();
            ctx.lineWidth = this.size / 2;
            ctx.strokeStyle = '#FFFFFF';
            ctx.moveTo(tailX, tailY);
            ctx.lineTo(this.x, this.y);
            ctx.stroke();
        } else {
            ctx.fillStyle = this.color;
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
            ctx.fill();
        }
        ctx.shadowBlur = 0;
    }
}

class Enemy {
    constructor(config) {
        this.x = config.x; this.y = config.y; this.active = true; this.type = config.type;
    }
    update(gridSystem, trailPoints, isTimeStopped) {}
    draw(ctx, time) {}
}

class QixEnemy extends Enemy {
    constructor(config) {
        super(config);
        this.speedLevels = [3.0, 4.0, 5.0, 6.0, 8.0];
        this.currentSpeed = this.pickSpeed();
        const angle = Math.random() * Math.PI * 2;
        this.vx = Math.cos(angle) * this.currentSpeed;
        this.vy = Math.sin(angle) * this.currentSpeed;
        this.color = config.color;
        this.collisionRadius = 8;
    }
    pickSpeed() {
        const idx = Math.floor(Math.random() * this.speedLevels.length);
        return this.speedLevels[idx];
    }
    update(gridSystem, trailPoints, isTimeStopped, slowMult = 1.0) {
        if (isTimeStopped) return null;
        const steps = 4;
        const subVx = this.vx * slowMult / steps;
        const subVy = this.vy * slowMult / steps;
        let bounced = false;

        for (let i = 0; i < steps; i++) {
            let nextX = this.x + subVx; let hitX = false;
            const checkDist = this.collisionRadius;
            let checkX = nextX + (subVx > 0 ? checkDist : -checkDist);

            if (checkX <= 0 || checkX >= CONFIG.CANVAS_SIZE) hitX = true;
            else {
                let gx = Math.floor(checkX); let gy = Math.floor(this.y);
                if (gridSystem.get(gx, gy) === CONFIG.STATE.WALL) hitX = true;
            }
            if (hitX) {
                this.vx *= -1; this.x += (this.vx > 0 ? 1 : -1) * 2; bounced = true;
            } else { this.x = nextX; }

            let nextY = this.y + subVy; let hitY = false;
            let checkY = nextY + (subVy > 0 ? checkDist : -checkDist);

            if (checkY <= 0 || checkY >= CONFIG.CANVAS_SIZE) hitY = true;
            else {
                let gx = Math.floor(this.x); let gy = Math.floor(checkY);
                if (gridSystem.get(gx, gy) === CONFIG.STATE.WALL) hitY = true;
            }
            if (hitY) {
                this.vy *= -1; this.y += (this.vy > 0 ? 1 : -1) * 2; bounced = true;
            } else { this.y = nextY; }

            if (this.checkCollision(gridSystem)) return { hit: true, type: 'TRAIL' };
            if (bounced) { this.applyBouncePhysics(); break; }
        }
        return null;
    }
    applyBouncePhysics() {
        let angle = Math.atan2(this.vy, this.vx);
        const jitter = (Math.random() - 0.5) * (Math.PI / 9); 
        angle += jitter;
        this.currentSpeed = this.pickSpeed();
        this.vx = Math.cos(angle) * this.currentSpeed;
        this.vy = Math.sin(angle) * this.currentSpeed;
    }
    checkCollision(gridSystem) {
        const r = 6; const cx = Math.floor(this.x); const cy = Math.floor(this.y);
        for (let dy = -r; dy <= r; dy++) {
            for (let dx = -r; dx <= r; dx++) {
                let nx = cx + dx; let ny = cy + dy;
                if (gridSystem.isValid(nx, ny) && gridSystem.get(nx, ny) === CONFIG.STATE.TRAIL) return true;
            }
        }
        return false;
    }
    draw(ctx, time) {
        ctx.fillStyle = "#FFFFFF"; ctx.shadowBlur = 20; ctx.shadowColor = "#FFFFFF";
        ctx.beginPath(); ctx.arc(this.x, this.y, 8, 0, Math.PI * 2); ctx.fill();
        ctx.fillStyle = this.color; ctx.shadowBlur = 10; ctx.shadowColor = this.color;
        ctx.beginPath(); ctx.arc(this.x, this.y, 5, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0;
        ctx.strokeStyle = this.color; ctx.lineWidth = 2;
        ctx.beginPath(); ctx.moveTo(this.x, this.y);
        for (let i = 0; i < 3; i++) {
            const angle = (time / 100) + (i * 2.09);
            ctx.lineTo(this.x + Math.cos(angle) * 18, this.y + Math.sin(angle) * 18);
            ctx.moveTo(this.x, this.y);
        }
        ctx.stroke();
    }
}

class ShooterEnemy extends QixEnemy {
    constructor(config) {
        super(config);
        this.type = 'SHOOTER';
        this.state = 'MOVE'; 
        this.moveDuration = config.interval || 180; 
        this.stateTimer = config.offset || 0; 
        this.chargeDuration = 60; 
        this.pendingProjectiles = [];
    }
    
    update(gridSystem, trailPoints, isTimeStopped) {
        if (isTimeStopped) return null;
        
        this.stateTimer++;
        if (this.state === 'MOVE') {
            if (this.stateTimer > this.moveDuration) {
                this.state = 'CHARGE';
                this.stateTimer = 0;
            } else {
                return super.update(gridSystem, trailPoints, isTimeStopped);
            }
        } else if (this.state === 'CHARGE') {
            if (this.checkCollision(gridSystem)) return { hit: true, type: 'TRAIL' };
            
            if (this.stateTimer > this.chargeDuration) {
                this.fire();
                this.state = 'MOVE';
                this.stateTimer = 0;
                this.applyBouncePhysics();
            }
        }
        return null;
    }

    fire() {
        const mode = Math.random() < 0.5 ? 'BURST' : 'SNIPE';
        this.pendingProjectiles.push({
            mode: mode,
            x: this.x,
            y: this.y,
            color: this.color
        });
    }

    draw(ctx, time) {
        if (this.state === 'CHARGE') {
            if (Math.floor(time / 50) % 2 === 0) ctx.fillStyle = "#FFFFFF";
            else ctx.fillStyle = this.color;
        } else {
            ctx.fillStyle = this.color;
        }

        ctx.shadowBlur = 10; ctx.shadowColor = this.color;
        ctx.beginPath();
        const size = 12;
        ctx.moveTo(this.x, this.y - size);
        ctx.lineTo(this.x + size, this.y + size);
        ctx.lineTo(this.x - size, this.y + size);
        ctx.closePath();
        ctx.fill();
        
        ctx.fillStyle = "#FFFFFF";
        ctx.beginPath();
        ctx.arc(this.x, this.y, 4, 0, Math.PI*2);
        ctx.fill();
        ctx.shadowBlur = 0;
    }
}

class SparksEnemy extends Enemy {
    constructor(config, player) {
        super(config);
        this.speed = config.speed;
        this.color = '#FFDD00'; 
        this.player = player;
        this.dirIdx = 2;
        this.spawnTimer = 0;
    }

    update(gridSystem, trailPoints, isTimeStopped, slowMult = 1.0) {
        if (isTimeStopped || slowMult === 0) return null;

        this.spawnTimer++;

        const steps = Math.max(1, Math.ceil(this.speed * slowMult));
        const dirs = [
            {x:0, y:-1}, {x:1, y:-1}, {x:1, y:0}, {x:1, y:1},
            {x:0, y:1}, {x:-1, y:1}, {x:-1, y:0}, {x:-1, y:-1}
        ];

        for (let i = 0; i < steps; i++) {
            const cx = Math.floor(this.x);
            const cy = Math.floor(this.y);

            if (!this.isValidPosition(gridSystem, cx, cy)) {
                this.active = false; return null; 
            }

            const collision = this.checkCollision(gridSystem);
            if (collision) return collision;

            const searchOrder = [-1, 0, 1, 2, 3, 4, 5, 6]; 
            
            let moved = false;
            for (let offset of searchOrder) {
                const idx = (this.dirIdx + offset + 8) % 8;
                const d = dirs[idx];
                const nx = cx + d.x;
                const ny = cy + d.y;

                if (this.isValidPosition(gridSystem, nx, ny)) {
                    this.x = nx; this.y = ny; this.dirIdx = idx;
                    moved = true; break;
                }
            }

            if (!moved) { this.active = false; return null; }
        }
        return null;
    }

    isValidPosition(gridSystem, x, y) {
        if (x < 0 || x >= gridSystem.cols || y < 0 || y >= gridSystem.rows) return false;
        if (gridSystem.get(x, y) !== CONFIG.STATE.WALL) return false;
        for (let dy = -1; dy <= 1; dy++) {
            for (let dx = -1; dx <= 1; dx++) {
                if (dx === 0 && dy === 0) continue;
                const nx = x + dx; const ny = y + dy;
                if (gridSystem.isValid(nx, ny) && gridSystem.get(nx, ny) === CONFIG.STATE.EMPTY) return true;
            }
        }
        return false; 
    }

    checkCollision(gridSystem) {
        if (this.spawnTimer < 180) {
            return null;
        }

        const dx = this.player.x - this.x;
        const dy = this.player.y - this.y;
        if (Math.sqrt(dx*dx + dy*dy) < 8) return { hit: true, type: 'BODY' };
        
        const r = 2;
        for (let dy = -r; dy <= r; dy++) {
            for (let dx = -r; dx <= r; dx++) {
                let nx = Math.floor(this.x) + dx; let ny = Math.floor(this.y) + dy;
                if (gridSystem.isValid(nx, ny) && gridSystem.get(nx, ny) === CONFIG.STATE.TRAIL) return { hit: true, type: 'TRAIL' };
            }
        }
        return null;
    }

    draw(ctx, time) {
        if (this.spawnTimer < 180) {
            if (Math.floor(time / 50) % 2 === 0) {
                ctx.globalAlpha = 0.3;
            } else {
                ctx.globalAlpha = 0.6;
            }
        }

        ctx.save();
        ctx.translate(this.x, this.y);
        
        ctx.fillStyle = "#FFFFFF";
        ctx.shadowBlur = 15;
        ctx.shadowColor = this.color;
        ctx.beginPath();
        ctx.arc(0, 0, 4, 0, Math.PI * 2);
        ctx.fill();

        ctx.strokeStyle = this.color;
        ctx.lineWidth = 2;
        ctx.beginPath();
        for (let i = 0; i < 4; i++) {
            const angle = (time / 100) + (i * Math.PI / 2) + (Math.random() - 0.5);
            const len = 8 + Math.random() * 8;
            const x = Math.cos(angle) * len;
            const y = Math.sin(angle) * len;
            ctx.moveTo(0, 0);
            ctx.lineTo(x, y);
        }
        ctx.stroke();

        ctx.restore();
        
        ctx.globalAlpha = 1.0;
    }
}

/**
 * ★追加: TrackerEnemy (防衛本能)
 * ゆっくりと確実にプレイヤーを追い詰める
 */
class TrackerEnemy extends Enemy {
    constructor(config, player) {
        super(config);
        this.type = 'TRACKER';
        this.player = player;
        this.speed = config.speed || 0.8; // 極めて遅い
        this.color = '#AA0000'; // 暗い赤
        this.wobbleOffset = Math.random() * 100;
    }

    update(gridSystem, trailPoints, isTimeStopped, slowMult = 1.0) {
        if (isTimeStopped || slowMult === 0) return null;

        // プレイヤーへのベクトル
        const dx = this.player.x - this.x;
        const dy = this.player.y - this.y;
        const dist = Math.sqrt(dx*dx + dy*dy);

        if (dist < 4) {
            // 接触
            return { hit: true, type: 'BODY' };
        }

        // 壁や線を無視して直進する
        if (dist > 0) {
            this.x += (dx / dist) * this.speed * slowMult;
            this.y += (dy / dist) * this.speed * slowMult;
        }

        // 当たり判定 (Body Hitのみ)
        // Trackerは線(Trail)には干渉しない（実体がないため）設定にするか、
        // プレイヤー同様に線に触れたらプレイヤーダメージにするか。
        // ここでは「本体への直接攻撃」を主とするため、線との衝突は無視するが、
        // 本体接触距離に入ればダメージ。
        
        return null;
    }

    draw(ctx, time) {
        ctx.save();
        ctx.translate(this.x, this.y);

        // 不気味な浮遊感
        const scale = 1.0 + 0.1 * Math.sin((time + this.wobbleOffset) / 300);
        ctx.scale(scale, scale);

        // 闇のオーラ
        ctx.fillStyle = "rgba(50, 0, 0, 0.5)";
        ctx.beginPath();
        ctx.arc(0, 0, 12, 0, Math.PI * 2);
        ctx.fill();

        // 核
        ctx.fillStyle = this.color;
        ctx.shadowBlur = 10;
        ctx.shadowColor = "#FF0000";
        ctx.beginPath();
        ctx.arc(0, 0, 6, 0, Math.PI * 2);
        ctx.fill();

        // 監視する目のような意匠
        ctx.fillStyle = "#000000";
        ctx.beginPath();
        ctx.arc(0, 0, 3, 0, Math.PI * 2);
        ctx.fill();

        ctx.restore();
    }
}

/**
 * BeamBossEnemy: チャージして貫通ビームを撃つボス敵
 * MOVE → CHARGE (照準) → FIRE (ビーム) → COOLDOWN のサイクル
 */
class BeamBossEnemy extends QixEnemy {
    constructor(config, player) {
        super(config);
        this.type = 'BEAM_BOSS';
        this.player = player;
        this.state = 'MOVE';
        this.stateTimer = config.offset || 0;
        this.moveDuration  = config.moveDuration  || 200;
        this.chargeDuration = config.chargeDuration || 90;
        this.fireDuration   = config.fireDuration   || 45;
        this.cooldownDuration = config.cooldownDuration || 40;
        this.beamDir = { x: 1, y: 0 };
        this.activeBeam = null; // { x1,y1,x2,y2,width }
        this.color = config.color || '#FF4400';
        this.beamWidth = config.beamWidth || 14;
        this.pendingProjectiles = []; // 互換性維持
    }

    update(gridSystem, trailPoints, isTimeStopped) {
        if (isTimeStopped) return null;
        this.stateTimer++;

        switch (this.state) {
            case 'MOVE':
                this.activeBeam = null;
                if (this.stateTimer >= this.moveDuration) {
                    this.state = 'CHARGE';
                    this.stateTimer = 0;
                    // プレイヤー方向を4方向スナップで照準
                    if (this.player) {
                        const dx = this.player.x - this.x;
                        const dy = this.player.y - this.y;
                        if (Math.abs(dx) >= Math.abs(dy)) {
                            this.beamDir = { x: Math.sign(dx), y: 0 };
                        } else {
                            this.beamDir = { x: 0, y: Math.sign(dy) };
                        }
                    }
                } else {
                    return super.update(gridSystem, trailPoints, isTimeStopped);
                }
                break;

            case 'CHARGE':
                if (this.checkCollision(gridSystem)) return { hit: true, type: 'TRAIL' };
                if (this.stateTimer >= this.chargeDuration) {
                    this.state = 'FIRE';
                    this.stateTimer = 0;
                    this.activeBeam = this._calcBeam();
                }
                break;

            case 'FIRE':
                this.activeBeam = this._calcBeam();
                if (this.stateTimer >= this.fireDuration) {
                    this.state = 'COOLDOWN';
                    this.stateTimer = 0;
                    this.activeBeam = null;
                    this.applyBouncePhysics();
                }
                break;

            case 'COOLDOWN':
                if (this.stateTimer >= this.cooldownDuration) {
                    this.state = 'MOVE';
                    this.stateTimer = 0;
                }
                break;
        }
        return null;
    }

    _calcBeam() {
        const C = CONFIG.CANVAS_SIZE;
        // ビーム端点を画面外まで延ばし、clampして画面端にする
        const ex = this.x + this.beamDir.x * C * 2;
        const ey = this.y + this.beamDir.y * C * 2;
        return {
            x1: this.x, y1: this.y,
            x2: Math.max(0, Math.min(C, ex)),
            y2: Math.max(0, Math.min(C, ey)),
            width: this.beamWidth
        };
    }

    hasActiveBeam() { return !!this.activeBeam; }

    playerHitByBeam(px, py) {
        if (!this.activeBeam) return false;
        const { x1, y1, x2, y2, width } = this.activeBeam;
        return this._pointSegDist(px, py, x1, y1, x2, y2) < width / 2 + 7;
    }

    enemyHitByBeam(enemy) {
        if (!this.activeBeam) return false;
        const { x1, y1, x2, y2, width } = this.activeBeam;
        return this._pointSegDist(enemy.x, enemy.y, x1, y1, x2, y2) < width / 2 + 8;
    }

    _pointSegDist(px, py, x1, y1, x2, y2) {
        const dx = x2 - x1, dy = y2 - y1;
        const lenSq = dx * dx + dy * dy;
        if (lenSq === 0) return Math.hypot(px - x1, py - y1);
        const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
        return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
    }

    draw(ctx, time) {
        const isCharging = this.state === 'CHARGE';
        const isFiring   = this.state === 'FIRE';
        const C = CONFIG.CANVAS_SIZE;

        // --- ビーム描画 ---
        if (this.activeBeam && isFiring) {
            const b = this.activeBeam;
            const fade = Math.max(0, 1.0 - this.stateTimer / this.fireDuration);
            ctx.save();
            ctx.shadowBlur = 0;
            ctx.lineCap = 'round';
            ctx.beginPath();
            ctx.moveTo(b.x1, b.y1);
            ctx.lineTo(b.x2, b.y2);
            ctx.strokeStyle = this.color;
            ctx.lineWidth = b.width * 4; ctx.globalAlpha = fade * 0.12; ctx.stroke();
            ctx.lineWidth = b.width * 2; ctx.globalAlpha = fade * 0.30; ctx.stroke();
            ctx.lineWidth = b.width;     ctx.globalAlpha = fade * 0.55; ctx.stroke();
            ctx.strokeStyle = '#FFFFFF';
            ctx.lineWidth = b.width * 0.5; ctx.globalAlpha = fade * 0.90; ctx.stroke();
            ctx.restore();
        } else if (isCharging) {
            const t = this.stateTimer / this.chargeDuration;
            const ex = Math.max(0, Math.min(C, this.x + this.beamDir.x * C * 2));
            const ey = Math.max(0, Math.min(C, this.y + this.beamDir.y * C * 2));
            const flashPeriod = Math.max(3, Math.floor(14 - 11 * t));
            const blink = Math.floor(this.stateTimer / flashPeriod) % 2 === 0;
            const base = 0.18 + t * 0.42;
            ctx.save();
            ctx.lineCap = 'round';
            ctx.shadowBlur = 0; // shadowBlur禁止: 長い線は超重い
            // パス1回描いてstroke多段で疑似グロー
            ctx.beginPath();
            ctx.moveTo(this.x, this.y);
            ctx.lineTo(ex, ey);
            ctx.strokeStyle = this.color;
            ctx.lineWidth = 18; ctx.globalAlpha = base * 0.10; ctx.stroke();
            ctx.lineWidth = 10; ctx.globalAlpha = base * 0.22; ctx.stroke();
            ctx.lineWidth = 4;  ctx.globalAlpha = base * 0.60; ctx.stroke();
            // 点滅白線（同じパス流用）
            const wa = blink ? (0.55 + t * 0.45) : (0.06 + t * 0.08);
            ctx.strokeStyle = '#FFFFFF';
            ctx.lineWidth = 2; ctx.globalAlpha = wa;
            ctx.setLineDash([10, 7]); ctx.stroke();
            ctx.setLineDash([]);
            ctx.restore();
        }

        // --- ボス本体 ---
        const flash = isCharging && Math.floor(time / 8) % 2 === 0;
        ctx.save();
        ctx.translate(this.x, this.y);
        const angle = time / 300;

        // 外リング
        ctx.strokeStyle = flash ? '#FFFFFF' : this.color;
        ctx.lineWidth = 3;
        ctx.shadowBlur = 25;
        ctx.shadowColor = this.color;
        ctx.beginPath();
        ctx.arc(0, 0, 18, 0, Math.PI * 2);
        ctx.stroke();

        // 回転ダイヤモンド
        ctx.save();
        ctx.rotate(angle);
        ctx.fillStyle = flash ? '#FFFFFF' : this.color;
        ctx.shadowBlur = 15;
        ctx.shadowColor = this.color;
        ctx.beginPath();
        const s = 11;
        ctx.moveTo(0, -s); ctx.lineTo(s, 0); ctx.lineTo(0, s); ctx.lineTo(-s, 0);
        ctx.closePath();
        ctx.fill();
        ctx.restore();

        // コア
        ctx.fillStyle = '#FFFFFF';
        ctx.shadowBlur = 20;
        ctx.shadowColor = '#FFDD00';
        ctx.beginPath();
        ctx.arc(0, 0, 5, 0, Math.PI * 2);
        ctx.fill();

        ctx.restore();
    }
}

/**
 * OctaBeamEnemy: 8方向同時レーザーを放つ砲台型敵
 * MOVE → CHARGE (8方向予告) → FIRE (8本同時照射) → COOLDOWN
 */
class OctaBeamEnemy extends QixEnemy {
    constructor(config, player) {
        super(config);
        this.type = 'OCTA_BEAM';
        this.player = player;
        this.state = 'MOVE';
        this.stateTimer = config.offset || 0;
        this.moveDuration     = config.moveDuration     || 180;
        this.chargeDuration   = config.chargeDuration   || 80;
        this.fireDuration     = config.fireDuration     || 40;
        this.cooldownDuration = config.cooldownDuration || 70;
        this.beamWidth  = config.beamWidth || 8;
        this.color      = config.color || '#FF8800';
        this.activeBeams = [];
        this.pendingProjectiles = [];
        const s = 1 / Math.SQRT2;
        this._dirs8 = [
            {x:1,y:0},{x:s,y:s},{x:0,y:1},{x:-s,y:s},
            {x:-1,y:0},{x:-s,y:-s},{x:0,y:-1},{x:s,y:-s}
        ];
    }

    update(gridSystem, trailPoints, isTimeStopped) {
        if (isTimeStopped) return null;
        this.stateTimer++;
        switch (this.state) {
            case 'MOVE':
                this.activeBeams = [];
                if (this.stateTimer >= this.moveDuration) {
                    this.state = 'CHARGE'; this.stateTimer = 0;
                } else {
                    return super.update(gridSystem, trailPoints, isTimeStopped);
                }
                break;
            case 'CHARGE':
                if (this.checkCollision(gridSystem)) return { hit: true, type: 'TRAIL' };
                if (this.stateTimer >= this.chargeDuration) {
                    this.state = 'FIRE'; this.stateTimer = 0;
                    this.activeBeams = this._calcBeams();
                }
                break;
            case 'FIRE':
                this.activeBeams = this._calcBeams();
                if (this.stateTimer >= this.fireDuration) {
                    this.state = 'COOLDOWN'; this.stateTimer = 0;
                    this.activeBeams = []; this.applyBouncePhysics();
                }
                break;
            case 'COOLDOWN':
                if (this.stateTimer >= this.cooldownDuration) {
                    this.state = 'MOVE'; this.stateTimer = 0;
                }
                break;
        }
        return null;
    }

    _calcBeams() {
        const C = CONFIG.CANVAS_SIZE;
        return this._dirs8.map(d => ({
            x1: this.x, y1: this.y,
            x2: Math.max(0, Math.min(C, this.x + d.x * C * 2)),
            y2: Math.max(0, Math.min(C, this.y + d.y * C * 2)),
            width: this.beamWidth
        }));
    }

    hasActiveBeam() { return this.activeBeams.length > 0; }

    playerHitByBeam(px, py) {
        return this.activeBeams.some(b =>
            this._pointSegDist(px, py, b.x1, b.y1, b.x2, b.y2) < b.width / 2 + 7);
    }

    enemyHitByBeam(enemy) {
        return this.activeBeams.some(b =>
            this._pointSegDist(enemy.x, enemy.y, b.x1, b.y1, b.x2, b.y2) < b.width / 2 + 8);
    }

    _pointSegDist(px, py, x1, y1, x2, y2) {
        const dx = x2 - x1, dy = y2 - y1;
        const lenSq = dx * dx + dy * dy;
        if (lenSq === 0) return Math.hypot(px - x1, py - y1);
        const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
        return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
    }

    draw(ctx, time) {
        const isCharging = this.state === 'CHARGE';
        const isFiring   = this.state === 'FIRE';
        const C = CONFIG.CANVAS_SIZE;

        if (isFiring && this.activeBeams.length) {
            const fade = Math.max(0, 1.0 - this.stateTimer / this.fireDuration);
            ctx.save();
            ctx.lineCap = 'round';
            ctx.shadowBlur = 0;
            // 全8本を1パスにまとめて多段stroke
            ctx.beginPath();
            this.activeBeams.forEach(b => { ctx.moveTo(b.x1, b.y1); ctx.lineTo(b.x2, b.y2); });
            ctx.strokeStyle = this.color;
            ctx.lineWidth = this.beamWidth * 3;   ctx.globalAlpha = fade * 0.12; ctx.stroke();
            ctx.lineWidth = this.beamWidth * 1.5; ctx.globalAlpha = fade * 0.28; ctx.stroke();
            ctx.lineWidth = this.beamWidth;        ctx.globalAlpha = fade * 0.55; ctx.stroke();
            ctx.strokeStyle = '#FFFFFF';
            ctx.lineWidth = this.beamWidth * 0.4;  ctx.globalAlpha = fade * 0.90; ctx.stroke();
            ctx.restore();
        } else if (isCharging) {
            const t = this.stateTimer / this.chargeDuration;
            const flashPeriod = Math.max(3, Math.floor(14 - 11 * t));
            const blink = Math.floor(this.stateTimer / flashPeriod) % 2 === 0;
            const base = 0.15 + t * 0.35;
            ctx.save();
            ctx.lineCap = 'round';
            ctx.shadowBlur = 0; // shadowBlur禁止: 8本×長い線 = 超重い
            // 8方向を1パスにまとめてstroke多段
            ctx.beginPath();
            this._dirs8.forEach(d => {
                const ex = Math.max(0, Math.min(C, this.x + d.x * C * 2));
                const ey = Math.max(0, Math.min(C, this.y + d.y * C * 2));
                ctx.moveTo(this.x, this.y); ctx.lineTo(ex, ey);
            });
            ctx.strokeStyle = this.color;
            ctx.lineWidth = 10; ctx.globalAlpha = base * 0.10; ctx.stroke();
            ctx.lineWidth = 4;  ctx.globalAlpha = base * 0.22; ctx.stroke();
            ctx.lineWidth = 1.5; ctx.globalAlpha = base * 0.55; ctx.stroke();
            const wa = blink ? (0.35 + t * 0.45) : (0.05 + t * 0.08);
            ctx.strokeStyle = '#FFFFFF';
            ctx.lineWidth = 1.5; ctx.globalAlpha = wa;
            ctx.setLineDash([8, 6]); ctx.stroke();
            ctx.setLineDash([]);
            ctx.restore();
        }

        // 本体: 回転する八角形
        const t_c = isCharging ? this.stateTimer / this.chargeDuration : 0;
        const fp = isCharging ? Math.max(3, Math.floor(14 - 11 * t_c)) : 10;
        const flash = isCharging && Math.floor(this.stateTimer / fp) % 2 === 0;
        ctx.save();
        ctx.translate(this.x, this.y);
        const rot = time / 450;
        ctx.strokeStyle = flash ? '#FFFFFF' : this.color;
        ctx.lineWidth = 2.5; ctx.shadowBlur = 20; ctx.shadowColor = this.color;
        ctx.beginPath(); ctx.arc(0, 0, 17, 0, Math.PI * 2); ctx.stroke();
        for (let r = 0; r < 2; r++) {
            ctx.save(); ctx.rotate(rot + r * Math.PI / 4);
            ctx.fillStyle = flash ? '#FFFFFF' : this.color;
            ctx.shadowBlur = 12;
            ctx.beginPath();
            const s = 9;
            ctx.moveTo(0,-s); ctx.lineTo(s,0); ctx.lineTo(0,s); ctx.lineTo(-s,0);
            ctx.closePath(); ctx.fill();
            ctx.restore();
        }
        ctx.fillStyle = '#FFFFFF'; ctx.shadowBlur = 20; ctx.shadowColor = '#FFAA00';
        ctx.beginPath(); ctx.arc(0, 0, 4, 0, Math.PI * 2); ctx.fill();
        ctx.restore();
    }
}

/**
 * SniperBeamEnemy: チャージ中に自機を追尾し正確に狙い撃ちするビーム敵
 * MOVE → CHARGE (自機追尾予告線) → FIRE (精密ビーム) → COOLDOWN
 */
class SniperBeamEnemy extends QixEnemy {
    constructor(config, player) {
        super(config);
        this.type = 'SNIPER_BEAM';
        this.player = player;
        this.state = 'MOVE';
        this.stateTimer = config.offset || 0;
        this.moveDuration     = config.moveDuration     || 200;
        this.chargeDuration   = config.chargeDuration   || 80;
        this.fireDuration     = config.fireDuration     || 45;
        this.cooldownDuration = config.cooldownDuration || 50;
        this.beamDir   = { x: 1, y: 0 };
        this.activeBeam = null;
        this.color      = config.color || '#FF0088';
        this.beamWidth  = config.beamWidth || 12;
        this.pendingProjectiles = [];
    }

    update(gridSystem, trailPoints, isTimeStopped) {
        if (isTimeStopped) return null;
        this.stateTimer++;
        switch (this.state) {
            case 'MOVE':
                this.activeBeam = null;
                if (this.stateTimer >= this.moveDuration) {
                    this.state = 'CHARGE'; this.stateTimer = 0;
                } else {
                    return super.update(gridSystem, trailPoints, isTimeStopped);
                }
                break;
            case 'CHARGE':
                if (this.checkCollision(gridSystem)) return { hit: true, type: 'TRAIL' };
                // チャージ中は常にプレイヤーを追尾
                if (this.player) {
                    const dx = this.player.x - this.x;
                    const dy = this.player.y - this.y;
                    const dist = Math.hypot(dx, dy);
                    if (dist > 0) this.beamDir = { x: dx / dist, y: dy / dist };
                }
                if (this.stateTimer >= this.chargeDuration) {
                    this.state = 'FIRE'; this.stateTimer = 0;
                    this.activeBeam = this._calcBeam();
                }
                break;
            case 'FIRE':
                this.activeBeam = this._calcBeam();
                if (this.stateTimer >= this.fireDuration) {
                    this.state = 'COOLDOWN'; this.stateTimer = 0;
                    this.activeBeam = null; this.applyBouncePhysics();
                }
                break;
            case 'COOLDOWN':
                if (this.stateTimer >= this.cooldownDuration) {
                    this.state = 'MOVE'; this.stateTimer = 0;
                }
                break;
        }
        return null;
    }

    _calcBeam() {
        const C = CONFIG.CANVAS_SIZE;
        return {
            x1: this.x, y1: this.y,
            x2: Math.max(0, Math.min(C, this.x + this.beamDir.x * C * 2)),
            y2: Math.max(0, Math.min(C, this.y + this.beamDir.y * C * 2)),
            width: this.beamWidth
        };
    }

    hasActiveBeam() { return !!this.activeBeam; }

    playerHitByBeam(px, py) {
        if (!this.activeBeam) return false;
        const { x1, y1, x2, y2, width } = this.activeBeam;
        return this._pointSegDist(px, py, x1, y1, x2, y2) < width / 2 + 7;
    }

    enemyHitByBeam(enemy) {
        if (!this.activeBeam) return false;
        const { x1, y1, x2, y2, width } = this.activeBeam;
        return this._pointSegDist(enemy.x, enemy.y, x1, y1, x2, y2) < width / 2 + 8;
    }

    _pointSegDist(px, py, x1, y1, x2, y2) {
        const dx = x2 - x1, dy = y2 - y1;
        const lenSq = dx * dx + dy * dy;
        if (lenSq === 0) return Math.hypot(px - x1, py - y1);
        const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
        return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
    }

    draw(ctx, time) {
        const isCharging = this.state === 'CHARGE';
        const isFiring   = this.state === 'FIRE';
        const C = CONFIG.CANVAS_SIZE;

        if (isFiring && this.activeBeam) {
            const b = this.activeBeam;
            const fade = Math.max(0, 1.0 - this.stateTimer / this.fireDuration);
            ctx.save();
            ctx.lineCap = 'round';
            ctx.shadowBlur = 0;
            ctx.beginPath();
            ctx.moveTo(b.x1, b.y1); ctx.lineTo(b.x2, b.y2);
            ctx.strokeStyle = this.color;
            ctx.lineWidth = b.width * 4;   ctx.globalAlpha = fade * 0.12; ctx.stroke();
            ctx.lineWidth = b.width * 2;   ctx.globalAlpha = fade * 0.30; ctx.stroke();
            ctx.lineWidth = b.width;       ctx.globalAlpha = fade * 0.55; ctx.stroke();
            ctx.strokeStyle = '#FFFFFF';
            ctx.lineWidth = b.width * 0.4; ctx.globalAlpha = fade * 0.90; ctx.stroke();
            ctx.restore();
        } else if (isCharging) {
            const t = this.stateTimer / this.chargeDuration;
            const ex = Math.max(0, Math.min(C, this.x + this.beamDir.x * C * 2));
            const ey = Math.max(0, Math.min(C, this.y + this.beamDir.y * C * 2));
            const flashPeriod = Math.max(3, Math.floor(14 - 11 * t));
            const blink = Math.floor(this.stateTimer / flashPeriod) % 2 === 0;
            const base = 0.18 + t * 0.42;
            ctx.save();
            ctx.lineCap = 'round';
            ctx.shadowBlur = 0; // shadowBlur禁止
            ctx.beginPath();
            ctx.moveTo(this.x, this.y); ctx.lineTo(ex, ey);
            ctx.strokeStyle = this.color;
            ctx.lineWidth = 20; ctx.globalAlpha = base * 0.10; ctx.stroke();
            ctx.lineWidth = 11; ctx.globalAlpha = base * 0.22; ctx.stroke();
            ctx.lineWidth = 4;  ctx.globalAlpha = base * 0.60; ctx.stroke();
            const wa = blink ? (0.60 + t * 0.40) : (0.06 + t * 0.08);
            ctx.strokeStyle = '#FFFFFF';
            ctx.lineWidth = 2; ctx.globalAlpha = wa;
            ctx.setLineDash([10, 6]); ctx.stroke();
            ctx.setLineDash([]);
            ctx.restore();
        }

        // 本体: スコープ型
        const t_c = isCharging ? this.stateTimer / this.chargeDuration : 0;
        const fp = isCharging ? Math.max(3, Math.floor(14 - 11 * t_c)) : 10;
        const flash = isCharging && Math.floor(this.stateTimer / fp) % 2 === 0;
        ctx.save();
        ctx.translate(this.x, this.y);
        const rot = time / 700;
        ctx.strokeStyle = flash ? '#FFFFFF' : this.color;
        ctx.lineWidth = 2.5; ctx.shadowBlur = 22; ctx.shadowColor = this.color;
        ctx.beginPath(); ctx.arc(0, 0, 17, 0, Math.PI * 2); ctx.stroke();
        // 照準クロス
        ctx.save(); ctx.rotate(rot);
        ctx.strokeStyle = flash ? '#FFFFFF' : this.color;
        ctx.lineWidth = 2;
        for (let i = 0; i < 4; i++) {
            const a = i * Math.PI / 2;
            ctx.beginPath();
            ctx.moveTo(Math.cos(a) * 8, Math.sin(a) * 8);
            ctx.lineTo(Math.cos(a) * 17, Math.sin(a) * 17);
            ctx.stroke();
        }
        ctx.restore();
        ctx.fillStyle = '#FFFFFF'; ctx.shadowBlur = 20; ctx.shadowColor = '#FF44AA';
        ctx.beginPath(); ctx.arc(0, 0, 5, 0, Math.PI * 2); ctx.fill();
        ctx.restore();
    }
}

/**
 * SnakeEnemy: 確保済み領域を侵食しながら動く蛇敵
 * - 角度ベースの連続運動（斜め移動可）で蛇行する
 * - WALL内部にいるとき前方がEMPTYなら反射→孤立したポケットを作る（再確保可能）
 * - EMPTY内では境界とTRAILのみ反射（自由に泳ぐ）
 * - 侵食半径3px（直径7px幅）
 */
class SnakeEnemy extends Enemy {
    constructor(config, player) {
        super(config);
        this.type = 'SNAKE';
        this.player = player;
        this.speed = config.speed || 1.4;
        this.color = config.color || '#00FF44';
        this.bodyLength = config.bodyLength || 50;
        this.eraseRadius = config.eraseRadius || 3;

        // 角度ベース移動（蛇行）
        this.angle = Math.random() * Math.PI * 2;
        this.wobble = Math.random() * Math.PI * 2;     // 蛇行位相
        this.wobbleAmp = 0.30 + Math.random() * 0.20; // 蛇行振幅
        this.wobbleSpeed = 0.04 + Math.random() * 0.03;
        this.stuckTimer = 0;

        this.segments = [{ x: config.x, y: config.y }];
        this.erasedThisUpdate = [];
        this.beamWarnings = [];
    }

    update(gridSystem, trailPoints, isTimeStopped, slowMult = 1.0) {
        if (isTimeStopped || slowMult === 0) return null;
        this.erasedThisUpdate = [];

        // ビーム回避: 現在位置がビーム外のときのみ前方チェック→既にビーム上なら脱出優先
        if (this.beamWarnings.length > 0 && !this._currentlyInBeam() && this._angleCrossesBeam(this.angle)) {
            this.angle += Math.PI * 0.5 + (Math.random() - 0.5) * 0.3;
        }

        // 蛇行: 位相を進めてサイン波で角度を揺らす
        this.wobble += this.wobbleSpeed;
        const effectiveAngle = this.angle + Math.sin(this.wobble) * this.wobbleAmp;

        const mvx = Math.cos(effectiveAngle) * this.speed * slowMult;
        const mvy = Math.sin(effectiveAngle) * this.speed * slowMult;
        const nx = this.x + mvx;
        const ny = this.y + mvy;

        const gxCur = Math.floor(this.x);
        const gyCur = Math.floor(this.y);
        const gnx   = Math.floor(nx);
        const gny   = Math.floor(ny);

        const curState  = gridSystem.isValid(gxCur, gyCur) ? gridSystem.get(gxCur, gyCur) : CONFIG.STATE.EMPTY;
        const nextState = gridSystem.isValid(gnx,   gny)   ? gridSystem.get(gnx,   gny)   : null;

        const hitBorder = nx <= 2 || nx >= CONFIG.CANVAS_SIZE - 2 ||
                          ny <= 2 || ny >= CONFIG.CANVAS_SIZE - 2;
        const hitTrail  = nextState === CONFIG.STATE.TRAIL;
        // WALL内にいるとき前方EMPTYへ進もうとしたら反射→孤立ポケット形成
        const hitVoid   = (curState === CONFIG.STATE.WALL) && (nextState === CONFIG.STATE.EMPTY);

        if (hitBorder || hitTrail || hitVoid) {
            // 反射: 当たった面によって反転
            if (hitBorder) {
                if (nx <= 2 || nx >= CONFIG.CANVAS_SIZE - 2) this.angle = Math.PI - this.angle;
                else this.angle = -this.angle;
            } else {
                // EMPTY壁またはTRAIL壁: 法線方向で反射 + ランダム揺れ
                const normalAngle = Math.atan2(mvy, mvx) + Math.PI;
                this.angle = normalAngle + (Math.random() - 0.5) * 0.5;
            }
            this.wobble = 0;
            this.stuckTimer++;
            if (this.stuckTimer > 40) {
                // 長時間スタック → ランダム方向リセット
                this.angle = Math.random() * Math.PI * 2;
                this.stuckTimer = 0;
            }
        } else {
            this.x = nx;
            this.y = ny;
            this.stuckTimer = 0;

            const gx = Math.floor(this.x);
            const gy = Math.floor(this.y);

            // 侵食: 頭位置から半径eraseRadiusの円内のWALLをEMPTYに
            const r = this.eraseRadius;
            for (let ey = gy - r; ey <= gy + r; ey++) {
                for (let ex = gx - r; ex <= gx + r; ex++) {
                    if (Math.hypot(ex - gx, ey - gy) > r) continue;
                    if (ex <= 0 || ex >= gridSystem.cols - 1) continue;
                    if (ey <= 0 || ey >= gridSystem.rows - 1) continue;
                    if (gridSystem.get(ex, ey) === CONFIG.STATE.WALL) {
                        gridSystem.set(ex, ey, CONFIG.STATE.EMPTY);
                        this.erasedThisUpdate.push(ey * gridSystem.cols + ex);
                    }
                }
            }

            // TRAILとの衝突判定
            if (this._checkTrailCollision(gridSystem, gx, gy)) {
                return { hit: true, type: 'TRAIL', erased: this.erasedThisUpdate };
            }
        }

        // セグメント追加・制限
        this.segments.unshift({ x: this.x, y: this.y });
        while (this.segments.length > this.bodyLength) this.segments.pop();

        // プレイヤー近接判定
        const ddx = this.player.x - this.x;
        const ddy = this.player.y - this.y;
        if (Math.sqrt(ddx * ddx + ddy * ddy) < 9) {
            return { hit: true, type: 'BODY', erased: this.erasedThisUpdate };
        }

        return this.erasedThisUpdate.length > 0 ? { erased: this.erasedThisUpdate } : null;
    }

    _currentlyInBeam() {
        for (const seg of this.beamWarnings) {
            if (this._ptSegDist(this.x, this.y, seg.x1, seg.y1, seg.x2, seg.y2) < 24) return true;
        }
        return false;
    }

    _angleCrossesBeam(angle) {
        for (let i = 16; i <= 80; i += 16) {
            const px = this.x + Math.cos(angle) * i;
            const py = this.y + Math.sin(angle) * i;
            for (const seg of this.beamWarnings) {
                if (this._ptSegDist(px, py, seg.x1, seg.y1, seg.x2, seg.y2) < 24) return true;
            }
        }
        return false;
    }

    _ptSegDist(px, py, x1, y1, x2, y2) {
        const dx = x2 - x1, dy = y2 - y1;
        const lenSq = dx * dx + dy * dy;
        if (lenSq === 0) return Math.hypot(px - x1, py - y1);
        const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
        return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
    }

    _checkTrailCollision(gridSystem, gx, gy) {
        const r = 3;
        for (let dy = -r; dy <= r; dy++) {
            for (let dx = -r; dx <= r; dx++) {
                const nx = gx + dx, ny = gy + dy;
                if (gridSystem.isValid(nx, ny) &&
                    gridSystem.get(nx, ny) === CONFIG.STATE.TRAIL) return true;
            }
        }
        return false;
    }

    draw(ctx, time) {
        if (this.segments.length === 0) return;
        ctx.save();

        // ボディ: 連続した曲線で描画（尻尾から頭へ）
        const n = this.segments.length;
        if (n >= 2) {
            ctx.beginPath();
            ctx.moveTo(this.segments[n - 1].x, this.segments[n - 1].y);
            for (let i = n - 2; i >= 0; i--) {
                ctx.lineTo(this.segments[i].x, this.segments[i].y);
            }
            ctx.strokeStyle = this.color;
            ctx.lineWidth = 7;
            ctx.lineCap = 'round';
            ctx.lineJoin = 'round';
            ctx.globalAlpha = 0.35;
            ctx.shadowBlur = 0;
            ctx.stroke();

            // 明るい芯線
            ctx.beginPath();
            ctx.moveTo(this.segments[n - 1].x, this.segments[n - 1].y);
            for (let i = n - 2; i >= 0; i--) {
                ctx.lineTo(this.segments[i].x, this.segments[i].y);
            }
            ctx.strokeStyle = this.color;
            ctx.lineWidth = 3;
            ctx.globalAlpha = 0.7;
            ctx.stroke();
        }

        // 頭
        ctx.globalAlpha = 1;
        ctx.shadowBlur = 16;
        ctx.shadowColor = this.color;
        ctx.fillStyle = '#FFFFFF';
        ctx.beginPath();
        ctx.arc(this.x, this.y, 8, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = this.color;
        ctx.beginPath();
        ctx.arc(this.x, this.y, 5.5, 0, Math.PI * 2);
        ctx.fill();

        // 目
        ctx.shadowBlur = 0;
        const eyeAngle = this.angle + Math.sin(this.wobble) * this.wobbleAmp;
        ctx.fillStyle = '#000000';
        for (const side of [0.55, -0.55]) {
            ctx.beginPath();
            ctx.arc(
                this.x + Math.cos(eyeAngle + side) * 3.5,
                this.y + Math.sin(eyeAngle + side) * 3.5,
                1.8, 0, Math.PI * 2
            );
            ctx.fill();
        }

        ctx.restore();
    }
}

/**
 * MineEnemy: 近接感知地雷。
 * IDLE → WARNING（予告）→ EXPLODING（爆発）のステートマシン。
 * triggerRadius: 起爆トリガー距離
 * blastRadius:   実際のダメージ範囲（triggerより大きいと脱出可能）
 * warningFrames: 爆発までのフレーム数
 */
class MineEnemy extends Enemy {
    constructor(config) {
        super(config);
        this.type = 'MINE';
        this.triggerRadius = config.triggerRadius || 55;
        this.blastRadius   = config.blastRadius   || 75;
        this.warningFrames = config.warningFrames  || 90;
        this.color = config.color || '#FF6600';
        this.pulseTimer = 0;
        this.state = 'IDLE'; // 'IDLE' | 'WARNING' | 'EXPLODING'
        this.warningTimer = 0;
    }
    trigger() {
        if (this.state === 'IDLE') {
            this.state = 'WARNING';
            this.warningTimer = this.warningFrames;
        }
    }
    update(gridSystem, trailPoints, isTimeStopped) {
        if (isTimeStopped) return null;
        this.pulseTimer++;
        if (this.state === 'WARNING') {
            this.warningTimer--;
            if (this.warningTimer <= 0) this.state = 'EXPLODING';
        }
        return null;
    }
    playerInRange(px, py) {
        return Math.hypot(px - this.x, py - this.y) < this.triggerRadius;
    }
    playerInBlastRange(px, py) {
        return Math.hypot(px - this.x, py - this.y) < this.blastRadius;
    }
    draw(ctx, time) {
        const pulse = 0.5 + 0.5 * Math.sin(time / 300);
        const r = 10;
        ctx.save();
        ctx.translate(this.x, this.y);

        // WARNING: blast radius 予告円
        if (this.state === 'WARNING') {
            const blink = Math.sin(this.warningTimer * (0.15 + (1 - this.warningTimer / this.warningFrames) * 0.3));
            const fillA  = Math.max(0, 0.08 + 0.07 * blink);
            const lineA  = Math.max(0, 0.45 + 0.35 * blink);
            ctx.fillStyle = `rgba(255,30,0,${fillA})`;
            ctx.beginPath();
            ctx.arc(0, 0, this.blastRadius, 0, Math.PI * 2);
            ctx.fill();
            ctx.strokeStyle = `rgba(255,60,0,${lineA})`;
            ctx.lineWidth = 2;
            ctx.setLineDash([8, 5]);
            ctx.beginPath();
            ctx.arc(0, 0, this.blastRadius, 0, Math.PI * 2);
            ctx.stroke();
            ctx.setLineDash([]);
        }

        const isWarning = this.state === 'WARNING';
        const warnBlink = isWarning ? 0.5 + 0.5 * Math.sin(this.warningTimer * 0.35) : 1;
        const bodyColor = isWarning
            ? `hsl(${15 + 10 * Math.sin(this.warningTimer * 0.4)},100%,${50 + 15 * pulse}%)`
            : this.color;
        const glowColor = isWarning ? '#FF2200' : this.color;
        const glowSize  = isWarning ? 10 + 16 * warnBlink : 8 + pulse * 14;

        // スパイク
        ctx.strokeStyle = bodyColor;
        ctx.lineWidth = 2.5;
        ctx.shadowBlur = glowSize;
        ctx.shadowColor = glowColor;
        for (let i = 0; i < 8; i++) {
            const a = (i / 8) * Math.PI * 2;
            ctx.beginPath();
            ctx.moveTo(Math.cos(a) * (r - 2), Math.sin(a) * (r - 2));
            ctx.lineTo(Math.cos(a) * (r + 7), Math.sin(a) * (r + 7));
            ctx.stroke();
        }
        // 本体
        ctx.fillStyle = bodyColor;
        ctx.beginPath();
        ctx.arc(0, 0, r, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#FFF';
        ctx.shadowBlur = 0;
        ctx.beginPath();
        ctx.arc(0, 0, 4, 0, Math.PI * 2);
        ctx.fill();

        // IDLE: トリガー範囲（点線）
        if (this.state === 'IDLE') {
            ctx.strokeStyle = `rgba(255,100,0,${0.15 + pulse * 0.2})`;
            ctx.lineWidth = 1;
            ctx.setLineDash([4, 4]);
            ctx.beginPath();
            ctx.arc(0, 0, this.triggerRadius, 0, Math.PI * 2);
            ctx.stroke();
            ctx.setLineDash([]);
        }

        ctx.restore();
    }
}

/**
 * GhostEnemy: 壁をすり抜けてプレイヤーを追う幽霊。
 */
class GhostEnemy extends Enemy {
    constructor(config, player) {
        super(config);
        this.type = 'GHOST';
        this.player = player;
        this.speed = config.speed || 1.2;
        this.color = config.color || '#8888FF';
        this.wobble = Math.random() * 200;
    }
    update(gridSystem, trailPoints, isTimeStopped, slowMult = 1.0) {
        if (isTimeStopped || slowMult === 0) return null;
        const dx = this.player.x - this.x;
        const dy = this.player.y - this.y;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist < 7) return { hit: true, type: 'BODY' };
        // 壁を無視して直進 + 微妙な揺れ
        const wobX = Math.sin((Date.now() + this.wobble) / 700) * 0.6 * slowMult;
        const wobY = Math.cos((Date.now() + this.wobble) / 500) * 0.6 * slowMult;
        this.x += (dx / dist) * this.speed * slowMult + wobX;
        this.y += (dy / dist) * this.speed * slowMult + wobY;
        this.x = Math.max(2, Math.min(CONFIG.CANVAS_SIZE - 2, this.x));
        this.y = Math.max(2, Math.min(CONFIG.CANVAS_SIZE - 2, this.y));
        // トレイル接触判定
        const gx = Math.floor(this.x), gy = Math.floor(this.y);
        for (let ddy = -4; ddy <= 4; ddy++) for (let ddx = -4; ddx <= 4; ddx++) {
            if (gridSystem.isValid(gx + ddx, gy + ddy) &&
                gridSystem.get(gx + ddx, gy + ddy) === CONFIG.STATE.TRAIL)
                return { hit: true, type: 'TRAIL' };
        }
        return null;
    }
    draw(ctx, time) {
        ctx.save();
        ctx.translate(this.x, this.y);
        const pulse = 0.5 + 0.5 * Math.sin(time / 400 + this.wobble / 50);
        ctx.globalAlpha = 0.45 + pulse * 0.45;
        ctx.shadowBlur = 20;
        ctx.shadowColor = this.color;
        ctx.fillStyle = this.color;
        // ゴースト体
        ctx.beginPath();
        ctx.arc(0, -4, 10, Math.PI, 0);
        ctx.lineTo(10, 9);
        ctx.bezierCurveTo(6, 15, 2, 10, 0, 15);
        ctx.bezierCurveTo(-2, 10, -6, 15, -10, 9);
        ctx.closePath();
        ctx.fill();
        // 目
        ctx.globalAlpha = 1;
        ctx.shadowBlur = 0;
        ctx.fillStyle = '#FFF';
        ctx.beginPath(); ctx.arc(-3.5, -5, 3, 0, Math.PI * 2); ctx.fill();
        ctx.beginPath(); ctx.arc(3.5, -5, 3, 0, Math.PI * 2); ctx.fill();
        ctx.fillStyle = '#111';
        ctx.beginPath(); ctx.arc(-3.5, -4.5, 1.5, 0, Math.PI * 2); ctx.fill();
        ctx.beginPath(); ctx.arc(3.5, -4.5, 1.5, 0, Math.PI * 2); ctx.fill();
        ctx.restore();
    }
}

class ClaimEffect {
    constructor(indices, cols) {
        this.whiteCanvas = this.createCache(indices, 255, 255, 255);
        this.cyanCanvas = this.createCache(indices, 200, 0, 255); // マゼンタ系に変更
        this.life = 1.0;
        this.decay = 0.005;
    }

    createCache(indices, r, g, b) {
        const c = document.createElement('canvas');
        c.width = CONFIG.CANVAS_SIZE;
        c.height = CONFIG.CANVAS_SIZE;
        const ctx = c.getContext('2d');
        const imgData = ctx.createImageData(CONFIG.CANVAS_SIZE, CONFIG.CANVAS_SIZE);
        const data = imgData.data;

        for (let i = 0; i < indices.length; i++) {
            const idx = indices[i] * 4;
            data[idx] = r;
            data[idx + 1] = g;
            data[idx + 2] = b;
            data[idx + 3] = 255; 
        }
        ctx.putImageData(imgData, 0, 0);
        return c;
    }

    update() {
        this.life -= this.decay;
    }

    draw(ctx) {
        if (this.life <= 0) return;

        ctx.save();
        ctx.globalCompositeOperation = 'lighter';
        
        const alpha = Math.pow(this.life, 0.8);
        ctx.globalAlpha = alpha;

        if (this.life > 0.8) {
            ctx.drawImage(this.whiteCanvas, 0, 0);
        } else {
            ctx.drawImage(this.cyanCanvas, 0, 0);
        }
        
        ctx.restore();
    }
}