/**
 * MapEditor.js - 2D canvas-based map editor for a Three.js RPG game.
 * Renders all map objects as 2D shapes (top-down view) and provides
 * interactive editing: select, move, add, delete, and modify properties.
 *
 * Coordinate system: X = horizontal (left-right), Z = vertical on canvas
 * (positive Z = up/north in the game world, which maps to negative Y on canvas).
 */

export class MapEditor {
    constructor(canvas, listPanel, propsPanel, statusBar) {
        this.canvas = canvas;
        this.ctx = canvas.getContext('2d');
        this.listPanel = listPanel;
        this.propsPanel = propsPanel;
        this.statusBar = statusBar;

        // Camera state
        this.camera = { x: 0, z: 0, zoom: 2.5 };
        this.minZoom = 0.5;
        this.maxZoom = 10;

        // Object storage
        this.objects = [];
        this.selectedObject = null;
        this.nextId = 1;

        // Interaction state
        this.isDragging = false;
        this.isPanning = false;
        this.dragStartWorld = null;
        this.dragStartObjPos = null;
        this.panStart = null;
        this.panCameraStart = null;
        this.mouseWorld = { x: 0, z: 0 };

        // Settings
        this.snapStep = 1;
        this.showGrid = true;
        this.showLabels = true;
        this.showRoutes = true;

        // Category collapse state
        this.collapsedCategories = {};

        // Structure display names (Russian)
        this.structureLabels = {
            park: 'Парк',
            shop: 'Магазин',
            shelter: 'Приют',
            hospital: 'Больница',
            church: 'Церковь',
            market: 'Рынок',
            construction: 'Стройка',
            busStop: 'Остановка',
            parking: 'Парковка',
            fountain: 'Фонтан',
            phoneBooth: 'Телефон',
            jobBoard: 'Доска вакансий',
            campSpot: 'Лагерь'
        };

        // Category display names (Russian)
        this.categoryLabels = {
            roads: 'Дороги',
            buildings: 'Здания',
            structures: 'Структуры',
            interactables: 'Интерактивные',
            decorations: 'Декорации',
            npcs: 'NPC',
            vehicles: 'Транспорт'
        };

        // Category icons
        this.categoryIcons = {
            roads: '\u2550',
            buildings: '\u2302',
            structures: '\u2736',
            interactables: '\u2699',
            decorations: '\u2600',
            npcs: '\u263A',
            vehicles: '\u2708'
        };

        this._setupEventListeners();
        this._resizeCanvas();
        this._startRenderLoop();
    }

    // =========================================================================
    // COORDINATE CONVERSION
    // =========================================================================

    screenToWorld(sx, sy) {
        const cx = this.canvas.width / 2;
        const cy = this.canvas.height / 2;
        const wx = (sx - cx) / this.camera.zoom + this.camera.x;
        const wz = -(sy - cy) / this.camera.zoom + this.camera.z;
        return { x: wx, z: wz };
    }

    worldToScreen(wx, wz) {
        const cx = this.canvas.width / 2;
        const cy = this.canvas.height / 2;
        const sx = (wx - this.camera.x) * this.camera.zoom + cx;
        const sy = -(wz - this.camera.z) * this.camera.zoom + cy;
        return { x: sx, y: sy };
    }

    // =========================================================================
    // EVENT LISTENERS
    // =========================================================================

    _setupEventListeners() {
        this.canvas.addEventListener('mousedown', (e) => this._onMouseDown(e));
        this.canvas.addEventListener('mousemove', (e) => this._onMouseMove(e));
        this.canvas.addEventListener('mouseup', (e) => this._onMouseUp(e));
        this.canvas.addEventListener('wheel', (e) => this._onWheel(e));
        this.canvas.addEventListener('contextmenu', (e) => e.preventDefault());

        window.addEventListener('keydown', (e) => this._onKeyDown(e));
        window.addEventListener('resize', () => this._resizeCanvas());
    }

    _resizeCanvas() {
        this.canvas.width = this.canvas.clientWidth;
        this.canvas.height = this.canvas.clientHeight;
    }

    _onMouseDown(e) {
        const rect = this.canvas.getBoundingClientRect();
        const sx = e.clientX - rect.left;
        const sy = e.clientY - rect.top;
        const world = this.screenToWorld(sx, sy);

        if (e.button === 2) {
            // Right click: start panning
            this.isPanning = true;
            this.panStart = { x: sx, y: sy };
            this.panCameraStart = { x: this.camera.x, z: this.camera.z };
            return;
        }

        if (e.button === 0) {
            // Left click: select or start drag
            const hit = this.hitTest(world.x, world.z);
            if (hit) {
                this.selectedObject = hit;
                this.isDragging = true;
                this.dragStartWorld = { x: world.x, z: world.z };
                this.dragStartObjPos = { x: hit.x, z: hit.z };
            } else {
                this.selectedObject = null;
            }
            this.updateObjectList();
            this.updatePropsPanel();
        }
    }

    _onMouseMove(e) {
        const rect = this.canvas.getBoundingClientRect();
        const sx = e.clientX - rect.left;
        const sy = e.clientY - rect.top;
        const world = this.screenToWorld(sx, sy);
        this.mouseWorld = world;

        if (this.isPanning && this.panStart) {
            const dx = (sx - this.panStart.x) / this.camera.zoom;
            const dy = (sy - this.panStart.y) / this.camera.zoom;
            this.camera.x = this.panCameraStart.x - dx;
            this.camera.z = this.panCameraStart.z + dy;
        }

        if (this.isDragging && this.selectedObject && this.dragStartWorld) {
            const dx = world.x - this.dragStartWorld.x;
            const dz = world.z - this.dragStartWorld.z;
            let newX = this.dragStartObjPos.x + dx;
            let newZ = this.dragStartObjPos.z + dz;
            if (this.snapStep > 0) {
                newX = Math.round(newX / this.snapStep) * this.snapStep;
                newZ = Math.round(newZ / this.snapStep) * this.snapStep;
            }
            this.selectedObject.x = newX;
            this.selectedObject.z = newZ;
            this.updatePropsPanel();
        }

        this._updateStatusBar();
    }

    _onMouseUp(e) {
        if (e.button === 2) {
            this.isPanning = false;
            this.panStart = null;
            this.panCameraStart = null;
        }
        if (e.button === 0) {
            this.isDragging = false;
            this.dragStartWorld = null;
            this.dragStartObjPos = null;
        }
    }

    _onWheel(e) {
        e.preventDefault();
        const rect = this.canvas.getBoundingClientRect();
        const sx = e.clientX - rect.left;
        const sy = e.clientY - rect.top;

        const worldBefore = this.screenToWorld(sx, sy);

        const zoomFactor = e.deltaY < 0 ? 1.1 : 0.9;
        this.camera.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.camera.zoom * zoomFactor));

        const worldAfter = this.screenToWorld(sx, sy);
        this.camera.x += worldBefore.x - worldAfter.x;
        this.camera.z += worldBefore.z - worldAfter.z;
    }

    _onKeyDown(e) {
        if (e.key === 'Delete' && this.selectedObject) {
            this.deleteObject(this.selectedObject);
        }
    }

    // =========================================================================
    // HIT TESTING
    // =========================================================================

    hitTest(worldX, worldZ) {
        // Check in reverse order so top-rendered objects are hit first
        for (let i = this.objects.length - 1; i >= 0; i--) {
            const obj = this.objects[i];
            if (this._hitTestObject(obj, worldX, worldZ)) {
                return obj;
            }
        }
        return null;
    }

    _hitTestObject(obj, wx, wz) {
        switch (obj.type) {
            case 'road': {
                let rw, rh;
                if (obj.rotation && Math.abs(obj.rotation) > 0.1) {
                    rw = obj.height;
                    rh = obj.width;
                } else {
                    rw = obj.width;
                    rh = obj.height;
                }
                return Math.abs(wx - obj.x) <= rw / 2 && Math.abs(wz - obj.z) <= rh / 2;
            }
            case 'building': {
                const bw = obj.w || 10;
                const bd = obj.d || 8;
                return Math.abs(wx - obj.x) <= bw / 2 && Math.abs(wz - obj.z) <= bd / 2;
            }
            case 'structure':
                return this._hitTestStructure(obj, wx, wz);
            case 'dumpster':
                return Math.abs(wx - obj.x) <= 1.5 && Math.abs(wz - obj.z) <= 1;
            case 'bench':
                return Math.abs(wx - obj.x) <= 2 && Math.abs(wz - obj.z) <= 1;
            case 'trashPile':
                return Math.hypot(wx - obj.x, wz - obj.z) <= 1.5;
            case 'lamp':
                return Math.hypot(wx - obj.x, wz - obj.z) <= 1.5;
            case 'hydrant':
                return Math.hypot(wx - obj.x, wz - obj.z) <= 1;
            case 'bin':
                return Math.hypot(wx - obj.x, wz - obj.z) <= 1;
            case 'npc':
                return Math.hypot(wx - obj.x, wz - obj.z) <= 2;
            case 'vehicleRoute': {
                const route = obj;
                if (route.axis === 'x') {
                    const minX = Math.min(route.start, route.end);
                    const maxX = Math.max(route.start, route.end);
                    return wx >= minX && wx <= maxX && Math.abs(wz - route.lane) <= 3;
                } else {
                    const minZ = Math.min(route.start, route.end);
                    const maxZ = Math.max(route.start, route.end);
                    return wz >= minZ && wz <= maxZ && Math.abs(wx - route.lane) <= 3;
                }
            }
            case 'passerbyRoute': {
                if (!obj.waypoints || obj.waypoints.length < 2) return false;
                for (let j = 0; j < obj.waypoints.length - 1; j++) {
                    const ax = obj.waypoints[j][0], az = obj.waypoints[j][1];
                    const bx = obj.waypoints[j + 1][0], bz = obj.waypoints[j + 1][1];
                    const dist = this._pointToSegmentDist(wx, wz, ax, az, bx, bz);
                    if (dist < 3) return true;
                }
                return false;
            }
            default:
                return Math.abs(wx - obj.x) <= 3 && Math.abs(wz - obj.z) <= 3;
        }
    }

    _hitTestStructure(obj, wx, wz) {
        const st = obj.structureType;
        // For structures with radius (park, construction, fountain)
        if (obj.radius) {
            return Math.hypot(wx - obj.x, wz - obj.z) <= obj.radius;
        }
        // For structures with w/d dimensions
        if (obj.w && obj.d) {
            return Math.abs(wx - obj.x) <= obj.w / 2 && Math.abs(wz - obj.z) <= obj.d / 2;
        }
        // Fallback defaults per type
        switch (st) {
            case 'park':
                return Math.hypot(wx - obj.x, wz - obj.z) <= 13;
            case 'construction':
                return Math.hypot(wx - obj.x, wz - obj.z) <= 12;
            case 'fountain':
                return Math.hypot(wx - obj.x, wz - obj.z) <= 2;
            case 'campSpot':
                return Math.abs(wx - obj.x) + Math.abs(wz - obj.z) <= 3;
            default:
                return Math.abs(wx - obj.x) <= 3 && Math.abs(wz - obj.z) <= 3;
        }
    }

    _pointToSegmentDist(px, pz, ax, az, bx, bz) {
        const dx = bx - ax;
        const dz = bz - az;
        const lenSq = dx * dx + dz * dz;
        if (lenSq === 0) return Math.hypot(px - ax, pz - az);
        let t = ((px - ax) * dx + (pz - az) * dz) / lenSq;
        t = Math.max(0, Math.min(1, t));
        const cx = ax + t * dx;
        const cz = az + t * dz;
        return Math.hypot(px - cx, pz - cz);
    }

    // =========================================================================
    // RENDERING
    // =========================================================================

    _startRenderLoop() {
        const loop = () => {
            this.render();
            requestAnimationFrame(loop);
        };
        requestAnimationFrame(loop);
    }

    render() {
        const ctx = this.ctx;
        const w = this.canvas.width;
        const h = this.canvas.height;

        ctx.clearRect(0, 0, w, h);

        // Background
        ctx.fillStyle = '#1a1a2e';
        ctx.fillRect(0, 0, w, h);

        // Draw ground
        this._drawGround();

        // Draw grid
        if (this.showGrid) this._drawGrid();

        // Draw coordinate axes
        this._drawAxes();

        // Draw objects in layers
        this._drawRoads();
        this._drawBuildings();
        this._drawStructures();
        this._drawInteractables();
        this._drawDecorations();
        if (this.showRoutes) {
            this._drawVehicleRoutes();
            this._drawPasserbyRoutes();
        }
        this._drawNPCs();

        // Draw selection highlight
        if (this.selectedObject) {
            this._drawSelectionHighlight(this.selectedObject);
        }
    }

    _drawGround() {
        const ctx = this.ctx;
        const topLeft = this.worldToScreen(-100, 100);
        const botRight = this.worldToScreen(100, -100);
        ctx.fillStyle = '#2d5a2d';
        ctx.fillRect(topLeft.x, topLeft.y, botRight.x - topLeft.x, botRight.y - topLeft.y);
    }

    _drawGrid() {
        const ctx = this.ctx;
        const w = this.canvas.width;
        const h = this.canvas.height;

        const topLeftWorld = this.screenToWorld(0, 0);
        const botRightWorld = this.screenToWorld(w, h);

        const minX = Math.floor(topLeftWorld.x / 10) * 10 - 10;
        const maxX = Math.ceil(botRightWorld.x / 10) * 10 + 10;
        const minZ = Math.floor(botRightWorld.z / 10) * 10 - 10;
        const maxZ = Math.ceil(topLeftWorld.z / 10) * 10 + 10;

        // Light grid every 10 units
        ctx.lineWidth = 0.5;
        for (let x = minX; x <= maxX; x += 10) {
            const isMajor = x % 50 === 0;
            ctx.strokeStyle = isMajor ? 'rgba(255,255,255,0.15)' : 'rgba(255,255,255,0.05)';
            ctx.lineWidth = isMajor ? 1 : 0.5;
            const s1 = this.worldToScreen(x, minZ);
            const s2 = this.worldToScreen(x, maxZ);
            ctx.beginPath();
            ctx.moveTo(s1.x, s1.y);
            ctx.lineTo(s2.x, s2.y);
            ctx.stroke();
        }
        for (let z = minZ; z <= maxZ; z += 10) {
            const isMajor = z % 50 === 0;
            ctx.strokeStyle = isMajor ? 'rgba(255,255,255,0.15)' : 'rgba(255,255,255,0.05)';
            ctx.lineWidth = isMajor ? 1 : 0.5;
            const s1 = this.worldToScreen(minX, z);
            const s2 = this.worldToScreen(maxX, z);
            ctx.beginPath();
            ctx.moveTo(s1.x, s1.y);
            ctx.lineTo(s2.x, s2.y);
            ctx.stroke();
        }
    }

    _drawAxes() {
        const ctx = this.ctx;
        const w = this.canvas.width;
        const h = this.canvas.height;

        // X axis (red)
        const xStart = this.worldToScreen(-200, 0);
        const xEnd = this.worldToScreen(200, 0);
        ctx.strokeStyle = 'rgba(255, 80, 80, 0.6)';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(xStart.x, xStart.y);
        ctx.lineTo(xEnd.x, xEnd.y);
        ctx.stroke();

        // Z axis (blue)
        const zStart = this.worldToScreen(0, -200);
        const zEnd = this.worldToScreen(0, 200);
        ctx.strokeStyle = 'rgba(80, 80, 255, 0.6)';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(zStart.x, zStart.y);
        ctx.lineTo(zEnd.x, zEnd.y);
        ctx.stroke();

        // Axis labels
        ctx.font = '12px monospace';
        ctx.fillStyle = '#ff5050';
        const xLabel = this.worldToScreen(195, 2);
        ctx.fillText('X', xLabel.x, xLabel.y);
        ctx.fillStyle = '#5050ff';
        const zLabel = this.worldToScreen(2, 195);
        ctx.fillText('Z', zLabel.x, zLabel.y);
    }

    _drawRoads() {
        const ctx = this.ctx;
        const roads = this.objects.filter(o => o.type === 'road');
        for (const road of roads) {
            const isNS = road.rotation && Math.abs(road.rotation) > 0.1;
            let rw, rh;
            if (isNS) {
                rw = road.height;
                rh = road.width;
            } else {
                rw = road.width;
                rh = road.height;
            }

            // Sidewalks
            const sw = road.sidewalkWidth ?? 3;
            if (sw > 0) {
                ctx.fillStyle = 'rgba(120, 120, 120, 0.4)';
                if (isNS) {
                    // N-S road: sidewalks east/west
                    const halfW = road.width / 2;
                    const halfLen = road.height / 2;
                    // East sidewalk
                    const eTl = this.worldToScreen(road.x + halfW, road.z + halfLen);
                    const eBr = this.worldToScreen(road.x + halfW + sw, road.z - halfLen);
                    ctx.fillRect(eTl.x, eTl.y, eBr.x - eTl.x, eBr.y - eTl.y);
                    // West sidewalk
                    const wTl = this.worldToScreen(road.x - halfW - sw, road.z + halfLen);
                    const wBr = this.worldToScreen(road.x - halfW, road.z - halfLen);
                    ctx.fillRect(wTl.x, wTl.y, wBr.x - wTl.x, wBr.y - wTl.y);
                } else {
                    // E-W road: sidewalks north/south
                    const halfH = road.height / 2;
                    const halfLen = road.width / 2;
                    // North sidewalk
                    const nTl = this.worldToScreen(road.x - halfLen, road.z + halfH + sw);
                    const nBr = this.worldToScreen(road.x + halfLen, road.z + halfH);
                    ctx.fillRect(nTl.x, nTl.y, nBr.x - nTl.x, nBr.y - nTl.y);
                    // South sidewalk
                    const sTl = this.worldToScreen(road.x - halfLen, road.z - halfH);
                    const sBr = this.worldToScreen(road.x + halfLen, road.z - halfH - sw);
                    ctx.fillRect(sTl.x, sTl.y, sBr.x - sTl.x, sBr.y - sTl.y);
                }
            }

            // Road surface
            const tl = this.worldToScreen(road.x - rw / 2, road.z + rh / 2);
            const br = this.worldToScreen(road.x + rw / 2, road.z - rh / 2);
            ctx.fillStyle = 'rgba(80, 80, 80, 0.8)';
            ctx.fillRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);

            // Road center line
            ctx.strokeStyle = 'rgba(200, 200, 50, 0.3)';
            ctx.lineWidth = 1;
            ctx.setLineDash([4, 6]);
            if (isNS) {
                const cv1 = this.worldToScreen(road.x, road.z - rh / 2);
                const cv2 = this.worldToScreen(road.x, road.z + rh / 2);
                ctx.beginPath();
                ctx.moveTo(cv1.x, cv1.y);
                ctx.lineTo(cv2.x, cv2.y);
                ctx.stroke();
            } else {
                const c1 = this.worldToScreen(road.x - rw / 2, road.z);
                const c2 = this.worldToScreen(road.x + rw / 2, road.z);
                ctx.beginPath();
                ctx.moveTo(c1.x, c1.y);
                ctx.lineTo(c2.x, c2.y);
                ctx.stroke();
            }
            ctx.setLineDash([]);

            // Label
            if (this.camera.zoom > 1.5 && road.name) {
                const center = this.worldToScreen(road.x, road.z);
                ctx.font = `${Math.max(9, 11 * this.camera.zoom / 2.5)}px sans-serif`;
                ctx.fillStyle = 'rgba(255, 255, 200, 0.7)';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText(road.name, center.x, center.y);
            }
        }
    }

    _drawBuildings() {
        const ctx = this.ctx;
        const buildings = this.objects.filter(o => o.type === 'building');
        for (const bld of buildings) {
            const bw = bld.w || 10;
            const bd = bld.d || 8;
            const tl = this.worldToScreen(bld.x - bw / 2, bld.z + bd / 2);
            const br = this.worldToScreen(bld.x + bw / 2, bld.z - bd / 2);
            ctx.fillStyle = bld.color || '#888888';
            ctx.fillRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
            ctx.strokeStyle = 'rgba(0,0,0,0.5)';
            ctx.lineWidth = 1;
            ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
        }
    }

    _drawStructures() {
        const ctx = this.ctx;
        const structures = this.objects.filter(o => o.type === 'structure');
        for (const st of structures) {
            this._drawStructure(st);
        }
    }

    _drawStructure(obj) {
        const ctx = this.ctx;
        const st = obj.structureType;
        const sc = this.worldToScreen(obj.x, obj.z);
        const zoom = this.camera.zoom;
        const label = this.structureLabels[st] || st;

        switch (st) {
            case 'park': {
                const r = (obj.radius || 18) * zoom;
                ctx.fillStyle = 'rgba(50, 150, 50, 0.4)';
                ctx.beginPath();
                ctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
                ctx.fill();
                ctx.strokeStyle = 'rgba(30, 120, 30, 0.6)';
                ctx.lineWidth = 2;
                ctx.stroke();
                break;
            }
            case 'shop': {
                const hw = (obj.w || 10) / 2, hd = (obj.d || 8) / 2;
                const tl = this.worldToScreen(obj.x - hw, obj.z + hd);
                const br = this.worldToScreen(obj.x + hw, obj.z - hd);
                ctx.fillStyle = 'rgba(60, 100, 200, 0.7)';
                ctx.fillRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                ctx.strokeStyle = '#3355aa';
                ctx.lineWidth = 1.5;
                ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                break;
            }
            case 'shelter': {
                const hw = (obj.w || 8) / 2, hd = (obj.d || 6) / 2;
                const tl = this.worldToScreen(obj.x - hw, obj.z + hd);
                const br = this.worldToScreen(obj.x + hw, obj.z - hd);
                ctx.fillStyle = 'rgba(90, 60, 30, 0.7)';
                ctx.fillRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                ctx.strokeStyle = '#5a3a1a';
                ctx.lineWidth = 1.5;
                ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                break;
            }
            case 'hospital': {
                const hw = (obj.w || 12) / 2, hd = (obj.d || 10) / 2;
                const tl = this.worldToScreen(obj.x - hw, obj.z + hd);
                const br = this.worldToScreen(obj.x + hw, obj.z - hd);
                ctx.fillStyle = 'rgba(240, 240, 240, 0.8)';
                ctx.fillRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                ctx.strokeStyle = '#cccccc';
                ctx.lineWidth = 1.5;
                ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                // Red cross
                const center = this.worldToScreen(obj.x, obj.z);
                const crossSize = 3 * zoom;
                ctx.strokeStyle = '#cc0000';
                ctx.lineWidth = Math.max(2, zoom);
                ctx.beginPath();
                ctx.moveTo(center.x - crossSize, center.y);
                ctx.lineTo(center.x + crossSize, center.y);
                ctx.moveTo(center.x, center.y - crossSize);
                ctx.lineTo(center.x, center.y + crossSize);
                ctx.stroke();
                break;
            }
            case 'church': {
                const hw = (obj.w || 10) / 2, hd = (obj.d || 14) / 2;
                const tl = this.worldToScreen(obj.x - hw, obj.z + hd);
                const br = this.worldToScreen(obj.x + hw, obj.z - hd);
                ctx.fillStyle = 'rgba(120, 60, 160, 0.7)';
                ctx.fillRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                ctx.strokeStyle = '#6a2a9a';
                ctx.lineWidth = 1.5;
                ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                break;
            }
            case 'market': {
                const hw = (obj.w || 14) / 2, hd = (obj.d || 10) / 2;
                const tl = this.worldToScreen(obj.x - hw, obj.z + hd);
                const br = this.worldToScreen(obj.x + hw, obj.z - hd);
                ctx.fillStyle = 'rgba(220, 140, 40, 0.7)';
                ctx.fillRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                ctx.strokeStyle = '#cc8820';
                ctx.lineWidth = 1.5;
                ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                break;
            }
            case 'construction': {
                const r = (obj.radius || 12) * zoom;
                ctx.strokeStyle = 'rgba(220, 200, 40, 0.7)';
                ctx.lineWidth = 2;
                ctx.setLineDash([6, 4]);
                ctx.beginPath();
                ctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
                ctx.stroke();
                ctx.setLineDash([]);
                break;
            }
            case 'busStop': {
                const hw = (obj.w || 5) / 2, hd = (obj.d || 2) / 2;
                const tl = this.worldToScreen(obj.x - hw, obj.z + hd);
                const br = this.worldToScreen(obj.x + hw, obj.z - hd);
                ctx.fillStyle = 'rgba(0, 200, 220, 0.7)';
                ctx.fillRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                ctx.strokeStyle = '#00aabb';
                ctx.lineWidth = 1;
                ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                break;
            }
            case 'parking': {
                const hw = (obj.w || 20) / 2, hd = (obj.d || 15) / 2;
                const tl = this.worldToScreen(obj.x - hw, obj.z + hd);
                const br = this.worldToScreen(obj.x + hw, obj.z - hd);
                ctx.fillStyle = 'rgba(100, 100, 100, 0.5)';
                ctx.fillRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                ctx.strokeStyle = 'rgba(150, 150, 150, 0.7)';
                ctx.lineWidth = 1;
                ctx.setLineDash([4, 3]);
                ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                ctx.setLineDash([]);
                break;
            }
            case 'fountain': {
                const r = (obj.radius || 2) * zoom;
                ctx.fillStyle = 'rgba(80, 140, 255, 0.7)';
                ctx.beginPath();
                ctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
                ctx.fill();
                ctx.strokeStyle = '#4488dd';
                ctx.lineWidth = 1.5;
                ctx.stroke();
                break;
            }
            case 'phoneBooth': {
                const hw = ((obj.w || 1.2) / 2) * zoom;
                const hd = ((obj.d || 1.2) / 2) * zoom;
                ctx.fillStyle = 'rgba(0, 160, 140, 0.8)';
                ctx.fillRect(sc.x - hw, sc.y - hd, hw * 2, hd * 2);
                ctx.strokeStyle = '#009988';
                ctx.lineWidth = 1;
                ctx.strokeRect(sc.x - hw, sc.y - hd, hw * 2, hd * 2);
                break;
            }
            case 'jobBoard': {
                const hw = ((obj.w || 1.2) / 2) * zoom;
                const hd = ((obj.d || 0.8) / 2) * zoom;
                ctx.fillStyle = 'rgba(60, 160, 60, 0.8)';
                ctx.fillRect(sc.x - hw, sc.y - hd, hw * 2, hd * 2);
                ctx.strokeStyle = '#338833';
                ctx.lineWidth = 1;
                ctx.strokeRect(sc.x - hw, sc.y - hd, hw * 2, hd * 2);
                break;
            }
            case 'campSpot': {
                const size = 3 * zoom;
                ctx.fillStyle = 'rgba(50, 180, 50, 0.6)';
                ctx.beginPath();
                ctx.moveTo(sc.x, sc.y - size);
                ctx.lineTo(sc.x + size, sc.y);
                ctx.lineTo(sc.x, sc.y + size);
                ctx.lineTo(sc.x - size, sc.y);
                ctx.closePath();
                ctx.fill();
                ctx.strokeStyle = '#228822';
                ctx.lineWidth = 1.5;
                ctx.stroke();
                break;
            }
        }

        // Label
        if (this.camera.zoom > 1.5) {
            const fontSize = Math.max(9, 11 * zoom / 2.5);
            ctx.font = `bold ${fontSize}px sans-serif`;
            ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'bottom';
            const labelPos = this.worldToScreen(obj.x, obj.z);
            let offsetY = -6;
            if (st === 'park') offsetY = -(obj.radius || 18) * zoom - 4;
            if (st === 'construction') offsetY = -(obj.radius || 12) * zoom - 4;
            ctx.fillText(label, labelPos.x, labelPos.y + offsetY);
        }
    }

    _drawInteractables() {
        const ctx = this.ctx;

        // Dumpsters
        const dumpsters = this.objects.filter(o => o.type === 'dumpster');
        for (const d of dumpsters) {
            const tl = this.worldToScreen(d.x - 0.75, d.z + 0.5);
            const br = this.worldToScreen(d.x + 0.75, d.z - 0.5);
            ctx.fillStyle = 'rgba(40, 100, 40, 0.8)';
            ctx.fillRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
            ctx.strokeStyle = '#1a5a1a';
            ctx.lineWidth = 1;
            ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
        }

        // Benches
        const benches = this.objects.filter(o => o.type === 'bench');
        for (const b of benches) {
            const isRotated = b.rot && Math.abs(b.rot) > 0.5;
            const bw = isRotated ? 0.5 : 2;
            const bh = isRotated ? 2 : 0.5;
            const tl = this.worldToScreen(b.x - bw / 2, b.z + bh / 2);
            const br = this.worldToScreen(b.x + bw / 2, b.z - bh / 2);
            ctx.fillStyle = 'rgba(140, 90, 40, 0.8)';
            ctx.fillRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
            ctx.strokeStyle = '#6a4020';
            ctx.lineWidth = 1;
            ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
        }

        // Trash piles
        const trashPiles = this.objects.filter(o => o.type === 'trashPile');
        for (const t of trashPiles) {
            const sc = this.worldToScreen(t.x, t.z);
            const r = 1.5 * this.camera.zoom;
            ctx.fillStyle = 'rgba(100, 110, 50, 0.7)';
            ctx.beginPath();
            ctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
            ctx.fill();
            ctx.strokeStyle = '#5a5a30';
            ctx.lineWidth = 1;
            ctx.stroke();
        }
    }

    _drawDecorations() {
        const ctx = this.ctx;

        // Lamps
        const lamps = this.objects.filter(o => o.type === 'lamp');
        for (const l of lamps) {
            const sc = this.worldToScreen(l.x, l.z);
            const r = 0.8 * this.camera.zoom;
            ctx.fillStyle = 'rgba(255, 230, 80, 0.8)';
            ctx.beginPath();
            ctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
            ctx.fill();
            // Glow effect
            ctx.fillStyle = 'rgba(255, 230, 80, 0.15)';
            ctx.beginPath();
            ctx.arc(sc.x, sc.y, r * 3, 0, Math.PI * 2);
            ctx.fill();
        }

        // Hydrants
        const hydrants = this.objects.filter(o => o.type === 'hydrant');
        for (const h of hydrants) {
            const sc = this.worldToScreen(h.x, h.z);
            const r = 0.5 * this.camera.zoom;
            ctx.fillStyle = 'rgba(220, 50, 50, 0.8)';
            ctx.beginPath();
            ctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
            ctx.fill();
        }

        // Bins
        const bins = this.objects.filter(o => o.type === 'bin');
        for (const b of bins) {
            const sc = this.worldToScreen(b.x, b.z);
            const r = 0.4 * this.camera.zoom;
            ctx.fillStyle = 'rgba(150, 150, 150, 0.8)';
            ctx.beginPath();
            ctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
            ctx.fill();
        }
    }

    _drawNPCs() {
        const ctx = this.ctx;
        const npcs = this.objects.filter(o => o.type === 'npc');
        for (const npc of npcs) {
            // Draw patrol route
            if (npc.patrol && npc.patrol.length > 1) {
                ctx.strokeStyle = 'rgba(80, 120, 255, 0.4)';
                ctx.lineWidth = 1.5;
                ctx.setLineDash([4, 4]);
                ctx.beginPath();
                for (let i = 0; i < npc.patrol.length; i++) {
                    const ps = this.worldToScreen(npc.patrol[i][0], npc.patrol[i][1]);
                    if (i === 0) ctx.moveTo(ps.x, ps.y);
                    else ctx.lineTo(ps.x, ps.y);
                }
                // Close loop
                const first = this.worldToScreen(npc.patrol[0][0], npc.patrol[0][1]);
                ctx.lineTo(first.x, first.y);
                ctx.stroke();
                ctx.setLineDash([]);

                // Patrol waypoints
                for (const wp of npc.patrol) {
                    const wps = this.worldToScreen(wp[0], wp[1]);
                    ctx.fillStyle = 'rgba(80, 120, 255, 0.5)';
                    ctx.beginPath();
                    ctx.arc(wps.x, wps.y, 2, 0, Math.PI * 2);
                    ctx.fill();
                }
            }

            // NPC circle
            const sc = this.worldToScreen(npc.x, npc.z);
            const r = 1.5 * this.camera.zoom;
            ctx.fillStyle = npc.color || '#3355aa';
            ctx.beginPath();
            ctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
            ctx.fill();
            ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)';
            ctx.lineWidth = 1.5;
            ctx.stroke();

            // Name label
            if (this.camera.zoom > 1.0) {
                const fontSize = Math.max(9, 11 * this.camera.zoom / 2.5);
                ctx.font = `bold ${fontSize}px sans-serif`;
                ctx.fillStyle = '#ffffff';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'bottom';
                ctx.fillText(npc.name || 'NPC', sc.x, sc.y - r - 3);
            }
        }
    }

    _drawVehicleRoutes() {
        const ctx = this.ctx;
        const routes = this.objects.filter(o => o.type === 'vehicleRoute');
        for (const route of routes) {
            ctx.strokeStyle = 'rgba(220, 140, 40, 0.5)';
            ctx.lineWidth = 2;

            let sx1, sy1, sx2, sy2;
            if (route.axis === 'x') {
                const s1 = this.worldToScreen(route.start, route.lane);
                const s2 = this.worldToScreen(route.end, route.lane);
                sx1 = s1.x; sy1 = s1.y;
                sx2 = s2.x; sy2 = s2.y;
            } else {
                const s1 = this.worldToScreen(route.lane, route.start);
                const s2 = this.worldToScreen(route.lane, route.end);
                sx1 = s1.x; sy1 = s1.y;
                sx2 = s2.x; sy2 = s2.y;
            }

            ctx.beginPath();
            ctx.moveTo(sx1, sy1);
            ctx.lineTo(sx2, sy2);
            ctx.stroke();

            // Arrow at end
            const angle = Math.atan2(sy2 - sy1, sx2 - sx1);
            const arrowSize = 8;
            ctx.fillStyle = 'rgba(220, 140, 40, 0.7)';
            ctx.beginPath();
            ctx.moveTo(sx2, sy2);
            ctx.lineTo(sx2 - arrowSize * Math.cos(angle - 0.4), sy2 - arrowSize * Math.sin(angle - 0.4));
            ctx.lineTo(sx2 - arrowSize * Math.cos(angle + 0.4), sy2 - arrowSize * Math.sin(angle + 0.4));
            ctx.closePath();
            ctx.fill();
        }
    }

    _drawPasserbyRoutes() {
        const ctx = this.ctx;
        const routes = this.objects.filter(o => o.type === 'passerbyRoute');
        for (const route of routes) {
            if (!route.waypoints || route.waypoints.length < 2) continue;

            ctx.strokeStyle = 'rgba(100, 200, 100, 0.3)';
            ctx.lineWidth = 1.5;
            ctx.setLineDash([3, 5]);
            ctx.beginPath();
            for (let i = 0; i < route.waypoints.length; i++) {
                const wp = route.waypoints[i];
                const sc = this.worldToScreen(wp[0], wp[1]);
                if (i === 0) ctx.moveTo(sc.x, sc.y);
                else ctx.lineTo(sc.x, sc.y);
            }
            ctx.stroke();
            ctx.setLineDash([]);

            // Arrow at end
            const last = route.waypoints[route.waypoints.length - 1];
            const prev = route.waypoints[route.waypoints.length - 2];
            const sLast = this.worldToScreen(last[0], last[1]);
            const sPrev = this.worldToScreen(prev[0], prev[1]);
            const angle = Math.atan2(sLast.y - sPrev.y, sLast.x - sPrev.x);
            const arrowSize = 6;
            ctx.fillStyle = 'rgba(100, 200, 100, 0.5)';
            ctx.beginPath();
            ctx.moveTo(sLast.x, sLast.y);
            ctx.lineTo(sLast.x - arrowSize * Math.cos(angle - 0.4), sLast.y - arrowSize * Math.sin(angle - 0.4));
            ctx.lineTo(sLast.x - arrowSize * Math.cos(angle + 0.4), sLast.y - arrowSize * Math.sin(angle + 0.4));
            ctx.closePath();
            ctx.fill();
        }
    }

    _drawSelectionHighlight(obj) {
        const ctx = this.ctx;
        ctx.strokeStyle = '#ffff00';
        ctx.lineWidth = 2.5;
        ctx.setLineDash([5, 3]);

        switch (obj.type) {
            case 'road': {
                let rw, rh;
                if (obj.rotation && Math.abs(obj.rotation) > 0.1) {
                    rw = obj.height;
                    rh = obj.width;
                } else {
                    rw = obj.width;
                    rh = obj.height;
                }
                const pad = 1;
                const tl = this.worldToScreen(obj.x - rw / 2 - pad, obj.z + rh / 2 + pad);
                const br = this.worldToScreen(obj.x + rw / 2 + pad, obj.z - rh / 2 - pad);
                ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                break;
            }
            case 'building': {
                const bw = obj.w || 10;
                const bd = obj.d || 8;
                const pad = 1;
                const tl = this.worldToScreen(obj.x - bw / 2 - pad, obj.z + bd / 2 + pad);
                const br = this.worldToScreen(obj.x + bw / 2 + pad, obj.z - bd / 2 - pad);
                ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                break;
            }
            case 'structure':
                this._drawStructureSelectionHighlight(obj);
                break;
            case 'npc': {
                const sc = this.worldToScreen(obj.x, obj.z);
                const r = (1.5 + 0.5) * this.camera.zoom;
                ctx.beginPath();
                ctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
                ctx.stroke();
                break;
            }
            case 'vehicleRoute': {
                if (obj.axis === 'x') {
                    const pad = 2;
                    const tl = this.worldToScreen(Math.min(obj.start, obj.end) - pad, obj.lane + pad);
                    const br = this.worldToScreen(Math.max(obj.start, obj.end) + pad, obj.lane - pad);
                    ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                } else {
                    const pad = 2;
                    const tl = this.worldToScreen(obj.lane - pad, Math.max(obj.start, obj.end) + pad);
                    const br = this.worldToScreen(obj.lane + pad, Math.min(obj.start, obj.end) - pad);
                    ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
                }
                break;
            }
            case 'passerbyRoute': {
                if (obj.waypoints && obj.waypoints.length > 1) {
                    ctx.strokeStyle = 'rgba(255, 255, 0, 0.8)';
                    ctx.lineWidth = 3;
                    ctx.beginPath();
                    for (let i = 0; i < obj.waypoints.length; i++) {
                        const sc = this.worldToScreen(obj.waypoints[i][0], obj.waypoints[i][1]);
                        if (i === 0) ctx.moveTo(sc.x, sc.y);
                        else ctx.lineTo(sc.x, sc.y);
                    }
                    ctx.stroke();
                }
                break;
            }
            default: {
                const sc = this.worldToScreen(obj.x, obj.z);
                const r = 3 * this.camera.zoom;
                ctx.beginPath();
                ctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
                ctx.stroke();
                break;
            }
        }
        ctx.setLineDash([]);
    }

    _drawStructureSelectionHighlight(obj) {
        const ctx = this.ctx;
        const pad = 1;

        // Круглые структуры (park, construction, fountain)
        if (obj.radius) {
            const sc = this.worldToScreen(obj.x, obj.z);
            const r = (obj.radius + pad) * this.camera.zoom;
            ctx.beginPath();
            ctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
            ctx.stroke();
            return;
        }

        // Прямоугольные структуры с размерами из конфига
        if (obj.w && obj.d) {
            const hw = obj.w / 2, hd = obj.d / 2;
            const tl = this.worldToScreen(obj.x - hw - pad, obj.z + hd + pad);
            const br = this.worldToScreen(obj.x + hw + pad, obj.z - hd - pad);
            ctx.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
            return;
        }

        // campSpot — ромб
        if (obj.structureType === 'campSpot') {
            const sc = this.worldToScreen(obj.x, obj.z);
            const size = (3 + pad) * this.camera.zoom;
            ctx.beginPath();
            ctx.moveTo(sc.x, sc.y - size);
            ctx.lineTo(sc.x + size, sc.y);
            ctx.lineTo(sc.x, sc.y + size);
            ctx.lineTo(sc.x - size, sc.y);
            ctx.closePath();
            ctx.stroke();
            return;
        }

        // Fallback — круг
        const sc = this.worldToScreen(obj.x, obj.z);
        const r = 4 * this.camera.zoom;
        ctx.beginPath();
        ctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
        ctx.stroke();
    }

    // =========================================================================
    // CONFIG LOADING / EXPORTING
    // =========================================================================

    async loadConfigFromFile(path) {
        try {
            const response = await fetch(path);
            const json = await response.json();
            this.loadConfig(json);
        } catch (err) {
            console.error('Failed to load map config:', err);
        }
    }

    loadConfig(json) {
        this.objects = [];
        this.nextId = 1;
        this.selectedObject = null;

        // Roads
        if (json.roads) {
            for (const road of json.roads) {
                this.objects.push({
                    type: 'road',
                    category: 'roads',
                    id: this.nextId++,
                    roadId: road.id || '',
                    name: road.name || '',
                    x: road.x,
                    z: road.z,
                    width: road.width,
                    height: road.height,
                    rotation: road.rotation || 0,
                    sidewalkWidth: road.sidewalkWidth ?? 3
                });
            }
        }

        // Buildings
        if (json.buildings) {
            for (let i = 0; i < json.buildings.length; i++) {
                const bld = json.buildings[i];
                this.objects.push({
                    type: 'building',
                    category: 'buildings',
                    id: this.nextId++,
                    x: bld.x,
                    z: bld.z,
                    w: bld.w,
                    h: bld.h,
                    d: bld.d,
                    color: bld.color || '#888888'
                });
            }
        }

        // Structures
        if (json.structures) {
            for (const [key, data] of Object.entries(json.structures)) {
                this.objects.push({
                    type: 'structure',
                    category: 'structures',
                    id: this.nextId++,
                    structureType: key,
                    x: data.x,
                    z: data.z,
                    radius: data.radius,
                    w: data.w,
                    h: data.h,
                    d: data.d,
                    rotation: data.rotation
                });
            }
        }

        // Interactables - dumpsters
        if (json.interactables && json.interactables.dumpsters) {
            for (const d of json.interactables.dumpsters) {
                this.objects.push({
                    type: 'dumpster',
                    category: 'interactables',
                    id: this.nextId++,
                    x: d.x,
                    z: d.z,
                    rot: d.rot || 0
                });
            }
        }

        // Interactables - benches
        if (json.interactables && json.interactables.benches) {
            for (const b of json.interactables.benches) {
                this.objects.push({
                    type: 'bench',
                    category: 'interactables',
                    id: this.nextId++,
                    x: b.x,
                    z: b.z,
                    rot: b.rot || 0
                });
            }
        }

        // Interactables - trash piles
        if (json.interactables && json.interactables.trashPiles) {
            for (const t of json.interactables.trashPiles) {
                this.objects.push({
                    type: 'trashPile',
                    category: 'interactables',
                    id: this.nextId++,
                    x: t.x,
                    z: t.z
                });
            }
        }

        // Decorations
        if (json.decorations) {
            if (json.decorations.lamps) {
                for (const l of json.decorations.lamps) {
                    this.objects.push({
                        type: 'lamp',
                        category: 'decorations',
                        id: this.nextId++,
                        x: l[0],
                        z: l[1]
                    });
                }
            }
            if (json.decorations.hydrants) {
                for (const h of json.decorations.hydrants) {
                    this.objects.push({
                        type: 'hydrant',
                        category: 'decorations',
                        id: this.nextId++,
                        x: h[0],
                        z: h[1]
                    });
                }
            }
            if (json.decorations.bins) {
                for (const b of json.decorations.bins) {
                    this.objects.push({
                        type: 'bin',
                        category: 'decorations',
                        id: this.nextId++,
                        x: b[0],
                        z: b[1]
                    });
                }
            }
        }

        // NPCs
        if (json.npcs) {
            for (const npc of json.npcs) {
                this.objects.push({
                    type: 'npc',
                    category: 'npcs',
                    id: this.nextId++,
                    name: npc.name,
                    x: npc.x,
                    z: npc.z,
                    npcType: npc.type,
                    color: npc.color || '#3355aa',
                    patrol: npc.patrol ? npc.patrol.map(p => [...p]) : null
                });
            }
        }

        // Vehicle routes
        if (json.vehicles && json.vehicles.routes) {
            for (const route of json.vehicles.routes) {
                // For vehicle routes, x/z represent center for dragging purposes
                let cx, cz;
                if (route.axis === 'x') {
                    cx = (route.start + route.end) / 2;
                    cz = route.lane;
                } else {
                    cx = route.lane;
                    cz = (route.start + route.end) / 2;
                }
                this.objects.push({
                    type: 'vehicleRoute',
                    category: 'vehicles',
                    id: this.nextId++,
                    x: cx,
                    z: cz,
                    axis: route.axis,
                    lane: route.lane,
                    start: route.start,
                    end: route.end,
                    dir: route.dir
                });
            }
        }

        // Passerby routes
        if (json.passerbyRoutes) {
            for (const route of json.passerbyRoutes) {
                const wps = route.waypoints || [];
                let cx = 0, cz = 0;
                if (wps.length > 0) {
                    for (const wp of wps) { cx += wp[0]; cz += wp[1]; }
                    cx /= wps.length;
                    cz /= wps.length;
                }
                this.objects.push({
                    type: 'passerbyRoute',
                    category: 'vehicles',
                    id: this.nextId++,
                    x: cx,
                    z: cz,
                    waypoints: wps.map(w => [...w])
                });
            }
        }

        this.updateObjectList();
        this.updatePropsPanel();
    }

    exportConfig() {
        const config = {
            roads: [],
            buildings: [],
            structures: {},
            interactables: { dumpsters: [], benches: [], trashPiles: [] },
            decorations: { lamps: [], hydrants: [], bins: [] },
            npcs: [],
            vehicles: { routes: [] },
            passerbyRoutes: []
        };

        for (const obj of this.objects) {
            switch (obj.type) {
                case 'road':
                    config.roads.push({
                        id: obj.roadId || obj.id.toString(),
                        name: obj.name || '',
                        x: obj.x,
                        z: obj.z,
                        width: obj.width,
                        height: obj.height,
                        rotation: obj.rotation || 0,
                        sidewalkWidth: obj.sidewalkWidth ?? 3
                    });
                    break;
                case 'building':
                    config.buildings.push({
                        x: obj.x,
                        z: obj.z,
                        w: obj.w,
                        h: obj.h,
                        d: obj.d,
                        color: obj.color
                    });
                    break;
                case 'structure': {
                    const data = { x: obj.x, z: obj.z };
                    if (obj.radius !== undefined && obj.radius !== null) data.radius = obj.radius;
                    if (obj.w !== undefined && obj.w !== null) data.w = obj.w;
                    if (obj.h !== undefined && obj.h !== null) data.h = obj.h;
                    if (obj.d !== undefined && obj.d !== null) data.d = obj.d;
                    if (obj.rotation !== undefined && obj.rotation !== null) data.rotation = obj.rotation;
                    config.structures[obj.structureType] = data;
                    break;
                }
                case 'dumpster':
                    config.interactables.dumpsters.push({
                        x: obj.x,
                        z: obj.z,
                        rot: obj.rot || 0
                    });
                    break;
                case 'bench':
                    config.interactables.benches.push({
                        x: obj.x,
                        z: obj.z,
                        rot: obj.rot || 0
                    });
                    break;
                case 'trashPile':
                    config.interactables.trashPiles.push({
                        x: obj.x,
                        z: obj.z
                    });
                    break;
                case 'lamp':
                    config.decorations.lamps.push([obj.x, obj.z]);
                    break;
                case 'hydrant':
                    config.decorations.hydrants.push([obj.x, obj.z]);
                    break;
                case 'bin':
                    config.decorations.bins.push([obj.x, obj.z]);
                    break;
                case 'npc': {
                    const npcData = {
                        name: obj.name,
                        x: obj.x,
                        z: obj.z,
                        type: obj.npcType || 'citizen',
                        color: obj.color
                    };
                    if (obj.patrol) npcData.patrol = obj.patrol.map(p => [...p]);
                    config.npcs.push(npcData);
                    break;
                }
                case 'vehicleRoute':
                    config.vehicles.routes.push({
                        axis: obj.axis,
                        lane: obj.lane,
                        start: obj.start,
                        end: obj.end,
                        dir: obj.dir
                    });
                    break;
                case 'passerbyRoute':
                    config.passerbyRoutes.push({
                        waypoints: obj.waypoints ? obj.waypoints.map(w => [...w]) : []
                    });
                    break;
            }
        }

        return config;
    }

    saveToFile() {
        const config = this.exportConfig();
        const json = JSON.stringify(config, null, 2);
        const blob = new Blob([json], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'map-config.json';
        a.click();
        URL.revokeObjectURL(url);
    }

    loadFromFile() {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.json';
        input.onchange = (e) => {
            const file = e.target.files[0];
            if (!file) return;
            const reader = new FileReader();
            reader.onload = (ev) => {
                try {
                    const json = JSON.parse(ev.target.result);
                    this.loadConfig(json);
                } catch (err) {
                    console.error('Failed to parse JSON:', err);
                    alert('Ошибка при загрузке файла: ' + err.message);
                }
            };
            reader.readAsText(file);
        };
        input.click();
    }

    // =========================================================================
    // OBJECT MANAGEMENT
    // =========================================================================

    addObject(type, props = {}) {
        const centerWorld = this.screenToWorld(this.canvas.width / 2, this.canvas.height / 2);
        const baseX = props.x !== undefined ? props.x : Math.round(centerWorld.x);
        const baseZ = props.z !== undefined ? props.z : Math.round(centerWorld.z);

        let obj = null;

        switch (type) {
            case 'road':
                obj = {
                    type: 'road',
                    category: 'roads',
                    id: this.nextId++,
                    roadId: props.roadId || 'road_' + this.nextId,
                    name: props.name || 'Новая дорога',
                    x: baseX,
                    z: baseZ,
                    width: props.width || 100,
                    height: props.height || 8,
                    rotation: props.rotation || 0,
                    sidewalkWidth: props.sidewalkWidth ?? 3
                };
                break;
            case 'building':
                obj = {
                    type: 'building',
                    category: 'buildings',
                    id: this.nextId++,
                    x: baseX,
                    z: baseZ,
                    w: props.w || 10,
                    h: props.h || 15,
                    d: props.d || 8,
                    color: props.color || '#888888'
                };
                break;
            case 'structure':
                obj = {
                    type: 'structure',
                    category: 'structures',
                    id: this.nextId++,
                    structureType: props.structureType || 'shop',
                    x: baseX,
                    z: baseZ,
                    radius: props.radius,
                    w: props.w,
                    h: props.h,
                    d: props.d,
                    rotation: props.rotation
                };
                break;
            case 'dumpster':
                obj = {
                    type: 'dumpster',
                    category: 'interactables',
                    id: this.nextId++,
                    x: baseX,
                    z: baseZ,
                    rot: props.rot || 0
                };
                break;
            case 'bench':
                obj = {
                    type: 'bench',
                    category: 'interactables',
                    id: this.nextId++,
                    x: baseX,
                    z: baseZ,
                    rot: props.rot || 0
                };
                break;
            case 'trashPile':
                obj = {
                    type: 'trashPile',
                    category: 'interactables',
                    id: this.nextId++,
                    x: baseX,
                    z: baseZ
                };
                break;
            case 'lamp':
                obj = {
                    type: 'lamp',
                    category: 'decorations',
                    id: this.nextId++,
                    x: baseX,
                    z: baseZ
                };
                break;
            case 'hydrant':
                obj = {
                    type: 'hydrant',
                    category: 'decorations',
                    id: this.nextId++,
                    x: baseX,
                    z: baseZ
                };
                break;
            case 'bin':
                obj = {
                    type: 'bin',
                    category: 'decorations',
                    id: this.nextId++,
                    x: baseX,
                    z: baseZ
                };
                break;
            case 'npc':
                obj = {
                    type: 'npc',
                    category: 'npcs',
                    id: this.nextId++,
                    name: props.name || 'Новый NPC',
                    x: baseX,
                    z: baseZ,
                    npcType: props.npcType || 'citizen',
                    color: props.color || '#3355aa',
                    patrol: props.patrol || null
                };
                break;
            case 'vehicleRoute':
                obj = {
                    type: 'vehicleRoute',
                    category: 'vehicles',
                    id: this.nextId++,
                    x: baseX,
                    z: baseZ,
                    axis: props.axis || 'x',
                    lane: props.lane || baseZ,
                    start: props.start || (baseX - 50),
                    end: props.end || (baseX + 50),
                    dir: props.dir || 1
                };
                break;
            case 'passerbyRoute':
                obj = {
                    type: 'passerbyRoute',
                    category: 'vehicles',
                    id: this.nextId++,
                    x: baseX,
                    z: baseZ,
                    waypoints: props.waypoints || [
                        [baseX - 30, baseZ],
                        [baseX, baseZ],
                        [baseX + 30, baseZ]
                    ]
                };
                break;
            default:
                console.warn('Unknown object type:', type);
                return null;
        }

        if (obj) {
            this.objects.push(obj);
            this.selectedObject = obj;
            this.updateObjectList();
            this.updatePropsPanel();
        }

        return obj;
    }

    deleteObject(obj) {
        const idx = this.objects.indexOf(obj);
        if (idx !== -1) {
            this.objects.splice(idx, 1);
            if (this.selectedObject === obj) {
                this.selectedObject = null;
            }
            this.updateObjectList();
            this.updatePropsPanel();
        }
    }

    // =========================================================================
    // OBJECT LIST PANEL (left sidebar)
    // =========================================================================

    updateObjectList() {
        if (!this.listPanel) return;

        const categories = [
            { key: 'roads', types: ['road'] },
            { key: 'buildings', types: ['building'] },
            { key: 'structures', types: ['structure'] },
            { key: 'interactables', types: ['dumpster', 'bench', 'trashPile'] },
            { key: 'decorations', types: ['lamp', 'hydrant', 'bin'] },
            { key: 'npcs', types: ['npc'] },
            { key: 'vehicles', types: ['vehicleRoute', 'passerbyRoute'] }
        ];

        let html = '';

        for (const cat of categories) {
            const items = this.objects.filter(o => cat.types.includes(o.type));
            const isCollapsed = this.collapsedCategories[cat.key] || false;
            const icon = this.categoryIcons[cat.key] || '';
            const label = this.categoryLabels[cat.key] || cat.key;

            html += `<div class="editor-category">`;
            html += `<div class="editor-category-header" data-category="${cat.key}">`;
            html += `<span class="editor-collapse-icon">${isCollapsed ? '\u25B6' : '\u25BC'}</span>`;
            html += `<span class="editor-category-icon">${icon}</span>`;
            html += `<span class="editor-category-label">${label}</span>`;
            html += `<span class="editor-category-count">(${items.length})</span>`;
            html += `<button class="editor-add-btn" data-add-type="${cat.types[0]}" title="Добавить">+</button>`;
            html += `</div>`;

            if (!isCollapsed) {
                html += `<div class="editor-category-items">`;
                for (const item of items) {
                    const isSelected = this.selectedObject && this.selectedObject.id === item.id;
                    const itemName = this._getObjectDisplayName(item);
                    const coords = `(${Math.round(item.x)}, ${Math.round(item.z)})`;
                    html += `<div class="editor-item${isSelected ? ' selected' : ''}" data-id="${item.id}">`;
                    html += `<span class="editor-item-type">${this._getTypeIcon(item.type)}</span>`;
                    html += `<span class="editor-item-name">${itemName}</span>`;
                    html += `<span class="editor-item-coords">${coords}</span>`;
                    html += `</div>`;
                }
                html += `</div>`;
            }

            html += `</div>`;
        }

        this.listPanel.innerHTML = html;

        // Attach event listeners
        const headers = this.listPanel.querySelectorAll('.editor-category-header');
        for (const header of headers) {
            header.addEventListener('click', (e) => {
                // Don't toggle on add button click
                if (e.target.classList.contains('editor-add-btn')) return;
                const catKey = header.getAttribute('data-category');
                this.collapsedCategories[catKey] = !this.collapsedCategories[catKey];
                this.updateObjectList();
            });
        }

        const addButtons = this.listPanel.querySelectorAll('.editor-add-btn');
        for (const btn of addButtons) {
            btn.addEventListener('click', (e) => {
                e.stopPropagation();
                const addType = btn.getAttribute('data-add-type');
                this.addObject(addType);
            });
        }

        const itemElements = this.listPanel.querySelectorAll('.editor-item');
        for (const el of itemElements) {
            el.addEventListener('click', () => {
                const id = parseInt(el.getAttribute('data-id'));
                const obj = this.objects.find(o => o.id === id);
                if (obj) {
                    this.selectedObject = obj;
                    this.camera.x = obj.x;
                    this.camera.z = obj.z;
                    this.updateObjectList();
                    this.updatePropsPanel();
                }
            });
        }
    }

    _getObjectDisplayName(obj) {
        switch (obj.type) {
            case 'road': return obj.name || obj.roadId || 'Дорога';
            case 'building': return `Здание ${obj.color || ''}`;
            case 'structure': return this.structureLabels[obj.structureType] || obj.structureType;
            case 'dumpster': return 'Мусорный бак';
            case 'bench': return 'Скамейка';
            case 'trashPile': return 'Куча мусора';
            case 'lamp': return 'Фонарь';
            case 'hydrant': return 'Гидрант';
            case 'bin': return 'Урна';
            case 'npc': return obj.name || 'NPC';
            case 'vehicleRoute': return `Маршрут ${obj.axis}:${obj.lane}`;
            case 'passerbyRoute': return 'Маршрут прохожих';
            default: return obj.type;
        }
    }

    _getTypeIcon(type) {
        switch (type) {
            case 'road': return '\u2550';
            case 'building': return '\u2302';
            case 'structure': return '\u2736';
            case 'dumpster': return '\u2612';
            case 'bench': return '\u2587';
            case 'trashPile': return '\u2622';
            case 'lamp': return '\u2600';
            case 'hydrant': return '\u2666';
            case 'bin': return '\u25CF';
            case 'npc': return '\u263A';
            case 'vehicleRoute': return '\u2192';
            case 'passerbyRoute': return '\u2026';
            default: return '\u25A0';
        }
    }

    // =========================================================================
    // PROPERTIES PANEL (right sidebar)
    // =========================================================================

    updatePropsPanel() {
        if (!this.propsPanel) return;

        if (!this.selectedObject) {
            this.propsPanel.innerHTML = '<div class="editor-props-empty">Объект не выбран</div>';
            return;
        }

        const obj = this.selectedObject;
        let html = '';

        html += `<div class="editor-props-header">${this._getObjectDisplayName(obj)}</div>`;
        html += `<div class="editor-props-type">Тип: ${obj.type}</div>`;

        html += `<div class="editor-props-section">`;
        html += `<label class="editor-prop-label">Позиция</label>`;
        html += this._propNumberField('x', 'X', obj.x);
        html += this._propNumberField('z', 'Z', obj.z);
        html += `</div>`;

        switch (obj.type) {
            case 'road':
                html += `<div class="editor-props-section">`;
                html += `<label class="editor-prop-label">Параметры дороги</label>`;
                html += this._propTextField('name', 'Название', obj.name || '');
                html += this._propTextField('roadId', 'ID', obj.roadId || '');
                html += this._propNumberField('width', 'Ширина', obj.width);
                html += this._propNumberField('height', 'Толщина', obj.height);
                html += this._propNumberField('rotation', 'Поворот', obj.rotation || 0, 0.01);
                html += this._propNumberField('sidewalkWidth', 'Тротуар', obj.sidewalkWidth ?? 3, 0.5);
                html += `</div>`;
                break;

            case 'building':
                html += `<div class="editor-props-section">`;
                html += `<label class="editor-prop-label">Параметры здания</label>`;
                html += this._propNumberField('w', 'Ширина (W)', obj.w);
                html += this._propNumberField('h', 'Высота (H)', obj.h);
                html += this._propNumberField('d', 'Глубина (D)', obj.d);
                html += this._propColorField('color', 'Цвет', obj.color || '#888888');
                html += `</div>`;
                break;

            case 'structure':
                html += `<div class="editor-props-section">`;
                html += `<label class="editor-prop-label">Тип структуры: ${this.structureLabels[obj.structureType] || obj.structureType}</label>`;
                if (obj.radius !== undefined && obj.radius !== null) {
                    html += this._propNumberField('radius', 'Радиус', obj.radius);
                }
                if (obj.w !== undefined && obj.w !== null) {
                    html += this._propNumberField('w', 'Ширина (W)', obj.w);
                }
                if (obj.h !== undefined && obj.h !== null) {
                    html += this._propNumberField('h', 'Высота (H)', obj.h);
                }
                if (obj.d !== undefined && obj.d !== null) {
                    html += this._propNumberField('d', 'Глубина (D)', obj.d);
                }
                if (obj.rotation !== undefined && obj.rotation !== null) {
                    html += this._propNumberField('rotation', 'Поворот', obj.rotation, 0.01);
                }
                html += `</div>`;
                break;

            case 'dumpster':
            case 'bench':
                html += `<div class="editor-props-section">`;
                html += `<label class="editor-prop-label">Параметры</label>`;
                html += this._propNumberField('rot', 'Поворот', obj.rot || 0, 0.1);
                html += `</div>`;
                break;

            case 'npc':
                html += `<div class="editor-props-section">`;
                html += `<label class="editor-prop-label">Параметры NPC</label>`;
                html += this._propTextField('name', 'Имя', obj.name || '');
                html += this._propTextField('npcType', 'Тип', obj.npcType || 'citizen');
                html += this._propColorField('color', 'Цвет', obj.color || '#3355aa');
                html += `</div>`;

                // Patrol points editor
                html += `<div class="editor-props-section">`;
                html += `<label class="editor-prop-label">Маршрут патрулирования</label>`;
                if (obj.patrol && obj.patrol.length > 0) {
                    for (let i = 0; i < obj.patrol.length; i++) {
                        html += `<div class="editor-patrol-point">`;
                        html += `<span class="editor-patrol-idx">${i + 1}.</span>`;
                        html += `<input type="number" class="editor-patrol-input" data-patrol-idx="${i}" data-patrol-axis="x" value="${obj.patrol[i][0]}" step="1">`;
                        html += `<input type="number" class="editor-patrol-input" data-patrol-idx="${i}" data-patrol-axis="z" value="${obj.patrol[i][1]}" step="1">`;
                        html += `<button class="editor-patrol-remove" data-patrol-idx="${i}" title="Удалить точку">x</button>`;
                        html += `</div>`;
                    }
                } else {
                    html += `<div class="editor-props-note">Нет маршрута</div>`;
                }
                html += `<button class="editor-patrol-add">+ Добавить точку</button>`;
                html += `</div>`;
                break;

            case 'vehicleRoute':
                html += `<div class="editor-props-section">`;
                html += `<label class="editor-prop-label">Параметры маршрута</label>`;
                html += this._propTextField('axis', 'Ось', obj.axis);
                html += this._propNumberField('lane', 'Полоса', obj.lane);
                html += this._propNumberField('start', 'Начало', obj.start);
                html += this._propNumberField('end', 'Конец', obj.end);
                html += this._propNumberField('dir', 'Направление', obj.dir, 1);
                html += `</div>`;
                break;

            case 'passerbyRoute':
                html += `<div class="editor-props-section">`;
                html += `<label class="editor-prop-label">Точки маршрута</label>`;
                if (obj.waypoints && obj.waypoints.length > 0) {
                    for (let i = 0; i < obj.waypoints.length; i++) {
                        html += `<div class="editor-patrol-point">`;
                        html += `<span class="editor-patrol-idx">${i + 1}.</span>`;
                        html += `<input type="number" class="editor-waypoint-input" data-wp-idx="${i}" data-wp-axis="x" value="${obj.waypoints[i][0]}" step="1">`;
                        html += `<input type="number" class="editor-waypoint-input" data-wp-idx="${i}" data-wp-axis="z" value="${obj.waypoints[i][1]}" step="1">`;
                        html += `<button class="editor-waypoint-remove" data-wp-idx="${i}" title="Удалить точку">x</button>`;
                        html += `</div>`;
                    }
                }
                html += `<button class="editor-waypoint-add">+ Добавить точку</button>`;
                html += `</div>`;
                break;
        }

        // Delete button
        html += `<div class="editor-props-actions">`;
        html += `<button class="editor-delete-btn">Удалить</button>`;
        html += `</div>`;

        this.propsPanel.innerHTML = html;

        // Attach event listeners
        this._attachPropsListeners();
    }

    _propNumberField(propName, label, value, step = 1) {
        return `<div class="editor-prop-row">
            <span class="editor-prop-name">${label}:</span>
            <input type="number" class="editor-prop-input" data-prop="${propName}" value="${value}" step="${step}">
        </div>`;
    }

    _propTextField(propName, label, value) {
        return `<div class="editor-prop-row">
            <span class="editor-prop-name">${label}:</span>
            <input type="text" class="editor-prop-input" data-prop="${propName}" value="${value}">
        </div>`;
    }

    _propColorField(propName, label, value) {
        return `<div class="editor-prop-row">
            <span class="editor-prop-name">${label}:</span>
            <input type="color" class="editor-prop-color" data-prop="${propName}" value="${value}">
        </div>`;
    }

    _attachPropsListeners() {
        if (!this.propsPanel || !this.selectedObject) return;

        // Number and text inputs
        const inputs = this.propsPanel.querySelectorAll('.editor-prop-input');
        for (const input of inputs) {
            input.addEventListener('change', () => {
                const prop = input.getAttribute('data-prop');
                if (input.type === 'number') {
                    this.selectedObject[prop] = parseFloat(input.value);
                } else {
                    this.selectedObject[prop] = input.value;
                }
                this.updateObjectList();
            });
        }

        // Color inputs
        const colorInputs = this.propsPanel.querySelectorAll('.editor-prop-color');
        for (const input of colorInputs) {
            input.addEventListener('input', () => {
                const prop = input.getAttribute('data-prop');
                this.selectedObject[prop] = input.value;
            });
        }

        // Delete button
        const deleteBtn = this.propsPanel.querySelector('.editor-delete-btn');
        if (deleteBtn) {
            deleteBtn.addEventListener('click', () => {
                if (this.selectedObject) {
                    this.deleteObject(this.selectedObject);
                }
            });
        }

        // Patrol point inputs (NPC)
        const patrolInputs = this.propsPanel.querySelectorAll('.editor-patrol-input');
        for (const input of patrolInputs) {
            input.addEventListener('change', () => {
                const idx = parseInt(input.getAttribute('data-patrol-idx'));
                const axis = input.getAttribute('data-patrol-axis');
                if (this.selectedObject.patrol && this.selectedObject.patrol[idx]) {
                    if (axis === 'x') {
                        this.selectedObject.patrol[idx][0] = parseFloat(input.value);
                    } else {
                        this.selectedObject.patrol[idx][1] = parseFloat(input.value);
                    }
                }
            });
        }

        // Remove patrol point buttons
        const patrolRemoveBtns = this.propsPanel.querySelectorAll('.editor-patrol-remove');
        for (const btn of patrolRemoveBtns) {
            btn.addEventListener('click', () => {
                const idx = parseInt(btn.getAttribute('data-patrol-idx'));
                if (this.selectedObject.patrol) {
                    this.selectedObject.patrol.splice(idx, 1);
                    if (this.selectedObject.patrol.length === 0) {
                        this.selectedObject.patrol = null;
                    }
                    this.updatePropsPanel();
                }
            });
        }

        // Add patrol point button
        const patrolAddBtn = this.propsPanel.querySelector('.editor-patrol-add');
        if (patrolAddBtn) {
            patrolAddBtn.addEventListener('click', () => {
                if (!this.selectedObject.patrol) {
                    this.selectedObject.patrol = [];
                }
                this.selectedObject.patrol.push([
                    Math.round(this.selectedObject.x),
                    Math.round(this.selectedObject.z)
                ]);
                this.updatePropsPanel();
            });
        }

        // Waypoint inputs (passerby route)
        const wpInputs = this.propsPanel.querySelectorAll('.editor-waypoint-input');
        for (const input of wpInputs) {
            input.addEventListener('change', () => {
                const idx = parseInt(input.getAttribute('data-wp-idx'));
                const axis = input.getAttribute('data-wp-axis');
                if (this.selectedObject.waypoints && this.selectedObject.waypoints[idx]) {
                    if (axis === 'x') {
                        this.selectedObject.waypoints[idx][0] = parseFloat(input.value);
                    } else {
                        this.selectedObject.waypoints[idx][1] = parseFloat(input.value);
                    }
                }
            });
        }

        // Remove waypoint buttons
        const wpRemoveBtns = this.propsPanel.querySelectorAll('.editor-waypoint-remove');
        for (const btn of wpRemoveBtns) {
            btn.addEventListener('click', () => {
                const idx = parseInt(btn.getAttribute('data-wp-idx'));
                if (this.selectedObject.waypoints) {
                    this.selectedObject.waypoints.splice(idx, 1);
                    this.updatePropsPanel();
                }
            });
        }

        // Add waypoint button
        const wpAddBtn = this.propsPanel.querySelector('.editor-waypoint-add');
        if (wpAddBtn) {
            wpAddBtn.addEventListener('click', () => {
                if (!this.selectedObject.waypoints) {
                    this.selectedObject.waypoints = [];
                }
                this.selectedObject.waypoints.push([
                    Math.round(this.selectedObject.x),
                    Math.round(this.selectedObject.z)
                ]);
                this.updatePropsPanel();
            });
        }
    }

    // =========================================================================
    // STATUS BAR
    // =========================================================================

    _updateStatusBar() {
        if (!this.statusBar) return;
        const mx = this.mouseWorld.x.toFixed(1);
        const mz = this.mouseWorld.z.toFixed(1);
        const count = this.objects.length;
        const zoom = this.camera.zoom.toFixed(1);
        this.statusBar.textContent = `Координаты: X=${mx}  Z=${mz}  |  Масштаб: ${zoom}x  |  Объектов: ${count}`;
    }
}
