import { System } from '../ecs/World.js';
import { Camera2D } from './Camera2D.js';

/* ────────────────────────────────────────────────────────────────
   Renderer2D — Canvas 2D rendering system
──────────────────────────────────────────────────────────────── */
/* ── Renderer2D ───────────────────────────────────────────────── */
class Renderer2D {
    constructor(canvas) {
        this._bgColor = { r: 0.1, g: 0.1, b: 0.15, a: 1 };
        this._textures = new Map();
        this._stats = { drawCalls: 0, entities: 0, particles: 0, fps: 0 };
        this._debugDraw = false;
        this._particles = new Map(); // entityId → particles
        this.canvas = canvas;
        const ctx = canvas.getContext('2d');
        if (!ctx)
            throw new Error('Failed to get 2D context');
        this.ctx = ctx;
        this.camera = new Camera2D(canvas.width, canvas.height);
    }
    /* ── Configuration ──────────────────────────────────────────── */
    set backgroundColor(c) { this._bgColor = c; }
    get backgroundColor() { return this._bgColor; }
    set debugDraw(v) { this._debugDraw = v; }
    get debugDraw() { return this._debugDraw; }
    get stats() { return this._stats; }
    /* ── Texture / asset loading ────────────────────────────────── */
    loadTexture(id, src) {
        return new Promise((resolve, reject) => {
            if (this._textures.has(id)) {
                resolve();
                return;
            }
            const img = new Image();
            img.onload = () => { this._textures.set(id, img); resolve(); };
            img.onerror = () => reject(new Error(`Failed to load texture: ${src}`));
            img.src = src;
        });
    }
    setTexture(id, source) {
        this._textures.set(id, source);
    }
    getTexture(id) {
        return this._textures.get(id);
    }
    resize(width, height) {
        this.canvas.width = width;
        this.canvas.height = height;
        this.camera.resize(width, height);
    }
    /* ── Main render ────────────────────────────────────────────── */
    render(world, interpolation) {
        const { ctx, canvas } = this;
        this._stats = { drawCalls: 0, entities: 0, particles: 0, fps: 0 };
        // Clear
        ctx.setTransform(1, 0, 0, 1, 0, 0);
        const bg = this._bgColor;
        ctx.fillStyle = `rgba(${bg.r * 255 | 0},${bg.g * 255 | 0},${bg.b * 255 | 0},${bg.a})`;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        // Apply camera
        this.camera.applyTransform(ctx);
        // Collect renderable items
        const items = [];
        // Sprites
        for (const id of world.query('Transform', 'Sprite')) {
            const t = world.getComponent(id, 'Transform');
            const s = world.getComponent(id, 'Sprite');
            if (s.visible)
                items.push({ entityId: id, layer: s.layer, type: 'sprite', transform: t, component: s });
        }
        // Shapes
        for (const id of world.query('Transform', 'Shape')) {
            const t = world.getComponent(id, 'Transform');
            const s = world.getComponent(id, 'Shape');
            if (s.visible)
                items.push({ entityId: id, layer: s.layer, type: 'shape', transform: t, component: s });
        }
        // Text
        for (const id of world.query('Transform', 'Text')) {
            const t = world.getComponent(id, 'Transform');
            const txt = world.getComponent(id, 'Text');
            if (txt.visible)
                items.push({ entityId: id, layer: txt.layer, type: 'text', transform: t, component: txt });
        }
        // Sort by layer, then by Y (for depth sorting)
        items.sort((a, b) => {
            if (a.layer !== b.layer)
                return a.layer - b.layer;
            return a.transform.position.y - b.transform.position.y;
        });
        // Render each item
        for (const item of items) {
            ctx.save();
            // Interpolated position
            const t = item.transform;
            const x = t.prevPosition.x + (t.position.x - t.prevPosition.x) * interpolation;
            const y = t.prevPosition.y + (t.position.y - t.prevPosition.y) * interpolation;
            const rot = t.prevRotation + (t.rotation - t.prevRotation) * interpolation;
            ctx.translate(x, y);
            ctx.rotate(rot);
            ctx.scale(t.scale.x, t.scale.y);
            switch (item.type) {
                case 'sprite':
                    this.drawSprite(item.component);
                    break;
                case 'shape':
                    this.drawShape(item.component);
                    break;
                case 'text':
                    this.drawText(item.component);
                    break;
            }
            ctx.restore();
            this._stats.drawCalls++;
            this._stats.entities++;
        }
        // Render particles
        this.renderParticles(world, interpolation);
        // Debug: colliders
        if (this._debugDraw) {
            this.renderDebug(world);
        }
    }
    /* ── Drawing primitives ─────────────────────────────────────── */
    drawSprite(s) {
        const { ctx } = this;
        if (s.flipX || s.flipY) {
            ctx.scale(s.flipX ? -1 : 1, s.flipY ? -1 : 1);
        }
        const anchorX = -s.width * s.anchor.x;
        const anchorY = -s.height * s.anchor.y;
        ctx.globalAlpha = s.opacity;
        if (s.assetId) {
            const tex = this._textures.get(s.assetId);
            if (tex) {
                if (s.srcRect) {
                    ctx.drawImage(tex, s.srcRect.x, s.srcRect.y, s.srcRect.w, s.srcRect.h, anchorX, anchorY, s.width, s.height);
                }
                else {
                    ctx.drawImage(tex, anchorX, anchorY, s.width, s.height);
                }
            }
        }
        else {
            // No texture — draw colored rect as placeholder
            ctx.fillStyle = colorToCSS(s.tint);
            ctx.fillRect(anchorX, anchorY, s.width, s.height);
        }
        ctx.globalAlpha = 1;
    }
    drawShape(s) {
        const { ctx } = this;
        if (s.fillColor.a > 0)
            ctx.fillStyle = colorToCSS(s.fillColor);
        if (s.strokeWidth > 0) {
            ctx.strokeStyle = colorToCSS(s.strokeColor);
            ctx.lineWidth = s.strokeWidth;
        }
        switch (s.shape) {
            case 'rect':
                if (s.fillColor.a > 0)
                    ctx.fillRect(-s.width / 2, -s.height / 2, s.width, s.height);
                if (s.strokeWidth > 0)
                    ctx.strokeRect(-s.width / 2, -s.height / 2, s.width, s.height);
                break;
            case 'circle':
                ctx.beginPath();
                ctx.arc(0, 0, s.radius, 0, Math.PI * 2);
                if (s.fillColor.a > 0)
                    ctx.fill();
                if (s.strokeWidth > 0)
                    ctx.stroke();
                break;
            case 'polygon':
                if (s.points.length < 3)
                    break;
                ctx.beginPath();
                ctx.moveTo(s.points[0].x, s.points[0].y);
                for (let i = 1; i < s.points.length; i++) {
                    ctx.lineTo(s.points[i].x, s.points[i].y);
                }
                ctx.closePath();
                if (s.fillColor.a > 0)
                    ctx.fill();
                if (s.strokeWidth > 0)
                    ctx.stroke();
                break;
            case 'line':
                ctx.beginPath();
                ctx.moveTo(s.lineStart.x, s.lineStart.y);
                ctx.lineTo(s.lineEnd.x, s.lineEnd.y);
                if (s.strokeWidth > 0)
                    ctx.stroke();
                break;
        }
    }
    drawText(t) {
        const { ctx } = this;
        ctx.font = `${t.fontSize}px ${t.fontFamily}`;
        ctx.fillStyle = colorToCSS(t.color);
        ctx.textAlign = t.align;
        ctx.textBaseline = t.baseline;
        ctx.fillText(t.text, 0, 0);
    }
    /* ── Particles ──────────────────────────────────────────────── */
    updateParticles(world, dt) {
        for (const entityId of world.query('Transform', 'ParticleEmitter')) {
            const transform = world.getComponent(entityId, 'Transform');
            const emitter = world.getComponent(entityId, 'ParticleEmitter');
            let particles = this._particles.get(entityId);
            if (!particles) {
                particles = [];
                this._particles.set(entityId, particles);
            }
            // Emit new particles
            if (emitter.emitting && particles.length < emitter.maxParticles) {
                const toEmit = Math.floor(emitter.rate * dt);
                for (let i = 0; i < toEmit && particles.length < emitter.maxParticles; i++) {
                    const angle = (randRange(emitter.angle[0], emitter.angle[1]) * Math.PI) / 180;
                    const speed = randRange(emitter.speed[0], emitter.speed[1]);
                    const life = randRange(emitter.lifetime[0], emitter.lifetime[1]);
                    const size = randRange(emitter.size[0], emitter.size[1]);
                    const sizeEnd = randRange(emitter.sizeEnd[0], emitter.sizeEnd[1]);
                    particles.push({
                        x: transform.position.x,
                        y: transform.position.y,
                        vx: Math.cos(angle) * speed,
                        vy: Math.sin(angle) * speed,
                        life,
                        maxLife: life,
                        size,
                        sizeEnd,
                        r: emitter.color.r, g: emitter.color.g, b: emitter.color.b, a: emitter.color.a,
                        rEnd: emitter.colorEnd.r, gEnd: emitter.colorEnd.g, bEnd: emitter.colorEnd.b, aEnd: emitter.colorEnd.a,
                    });
                }
            }
            // Update existing particles
            for (let i = particles.length - 1; i >= 0; i--) {
                const p = particles[i];
                p.life -= dt;
                if (p.life <= 0) {
                    particles.splice(i, 1);
                    continue;
                }
                p.vx += emitter.gravity.x * dt;
                p.vy += emitter.gravity.y * dt;
                p.x += p.vx * dt;
                p.y += p.vy * dt;
            }
        }
    }
    renderParticles(world, _interpolation) {
        const { ctx } = this;
        for (const [entityId, particles] of this._particles) {
            if (!world.getEntity(entityId)) {
                this._particles.delete(entityId);
                continue;
            }
            for (const p of particles) {
                const t = 1 - p.life / p.maxLife;
                const size = p.size + (p.sizeEnd - p.size) * t;
                const r = ((p.r + (p.rEnd - p.r) * t) * 255) | 0;
                const g = ((p.g + (p.gEnd - p.g) * t) * 255) | 0;
                const b = ((p.b + (p.bEnd - p.b) * t) * 255) | 0;
                const a = p.a + (p.aEnd - p.a) * t;
                ctx.globalAlpha = a;
                ctx.fillStyle = `rgb(${r},${g},${b})`;
                ctx.fillRect(p.x - size / 2, p.y - size / 2, size, size);
                this._stats.particles++;
            }
        }
        ctx.globalAlpha = 1;
    }
    /* ── Debug rendering ────────────────────────────────────────── */
    renderDebug(world) {
        const { ctx } = this;
        ctx.strokeStyle = 'rgba(0, 255, 0, 0.5)';
        ctx.lineWidth = 1;
        for (const id of world.query('Transform', 'Collider2D')) {
            const t = world.getComponent(id, 'Transform');
            const c = world.getComponent(id, 'Collider2D');
            ctx.save();
            ctx.translate(t.position.x + c.offset.x, t.position.y + c.offset.y);
            if (c.isTrigger)
                ctx.strokeStyle = 'rgba(255, 255, 0, 0.5)';
            else
                ctx.strokeStyle = 'rgba(0, 255, 0, 0.5)';
            switch (c.shape) {
                case 'aabb':
                    ctx.strokeRect(-c.width / 2, -c.height / 2, c.width, c.height);
                    break;
                case 'circle':
                    ctx.beginPath();
                    ctx.arc(0, 0, c.radius, 0, Math.PI * 2);
                    ctx.stroke();
                    break;
                case 'polygon':
                    if (c.points.length >= 3) {
                        ctx.beginPath();
                        ctx.moveTo(c.points[0].x, c.points[0].y);
                        for (let i = 1; i < c.points.length; i++)
                            ctx.lineTo(c.points[i].x, c.points[i].y);
                        ctx.closePath();
                        ctx.stroke();
                    }
                    break;
            }
            ctx.restore();
        }
    }
}
/* ── Render System (for ECS integration) ──────────────────────── */
class RenderSystem extends System {
    constructor(renderer, priority = 1000) {
        super(priority);
        this.renderer = renderer;
    }
    update(world, dt) {
        this.renderer.camera.update(dt);
        this.renderer.updateParticles(world, dt);
    }
}
/* ── Utilities ────────────────────────────────────────────────── */
function colorToCSS(c) {
    return `rgba(${(c.r * 255) | 0},${(c.g * 255) | 0},${(c.b * 255) | 0},${c.a})`;
}
function randRange(min, max) {
    return min + Math.random() * (max - min);
}

export { RenderSystem, Renderer2D };
//# sourceMappingURL=Renderer2D.js.map
