import * as THREE from 'three';

export class GameRenderer {
    constructor(containerId) {
        this.container = document.getElementById(containerId) || document.body;

        // 1. Scene
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(0x1a1a1a);

        // 2. Renderer
        this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.shadowMap.enabled = true;
        this.container.appendChild(this.renderer.domElement);

        // 3. Default Lights
        this.setupLights();

        // Debug Properties
        this.scene.add(new THREE.AxesHelper(10)); // Red=X, Green=Y, Blue=Z

        // Grid Helper: Size 100, Divisions 100 (1 unit per cell)
        const gridHelper = new THREE.GridHelper(100, 100, 0x444444, 0x222222);
        this.scene.add(gridHelper);

        // 4. Resize Handler
        window.addEventListener('resize', this.onWindowResize.bind(this));

        // 5. Textures
        this.textureLoader = new THREE.TextureLoader();
        this.textureCache = new Map();
        // 6. Interaction
        this.raycaster = new THREE.Raycaster();
        this.mouse = new THREE.Vector2();
        this.interactionPlane = new THREE.Mesh(
            new THREE.PlaneGeometry(1000, 1000),
            new THREE.MeshBasicMaterial({ visible: false })
        );
        this.interactionPlane.rotation.x = -Math.PI / 2;
        this.scene.add(this.interactionPlane);

        this.selectionMesh = null;
        this.highlightGroup = new THREE.Group();
        this.scene.add(this.highlightGroup);

        this.entities = new Map();
    }

    setupLights() {
        // Ambient Light (Base visibility)
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
        this.scene.add(ambientLight);

        // Directional Light (Sun/Moon - creates shadows)
        const dirLight = new THREE.DirectionalLight(0xffffff, 0.7);
        dirLight.position.set(50, 100, 50);
        dirLight.castShadow = true;
        this.scene.add(dirLight);
    }

    setupInteraction(cameraGetter, onClick, onRightClick) {
        const getMousePos = (event) => {
            const rect = this.renderer.domElement.getBoundingClientRect();
            return {
                x: ((event.clientX - rect.left) / rect.width) * 2 - 1,
                y: -((event.clientY - rect.top) / rect.height) * 2 + 1
            };
        };

        this.renderer.domElement.addEventListener('click', (event) => {
            this.mouse.set(getMousePos(event).x, getMousePos(event).y);
            this.raycaster.setFromCamera(this.mouse, cameraGetter());

            // First, check if we clicked on a door mesh
            if (this.exitGroup) {
                const doorIntersects = this.raycaster.intersectObjects(this.exitGroup.children, false);
                if (doorIntersects.length > 0) {
                    const doorMesh = doorIntersects[0].object;
                    if (doorMesh.userData.isDoor) {
                        // Clicked on a door! Call onClick with a special door object
                        onClick(null, null, doorMesh);
                        return;
                    }
                }
            }

            // If no door clicked, proceed with normal cell click
            const intersects = this.raycaster.intersectObject(this.interactionPlane);

            if (intersects.length > 0) {
                const p = intersects[0].point;
                const x = Math.round(p.x);
                const y = Math.round(-p.z);
                onClick(x, y, null);
            }
        });

        this.renderer.domElement.addEventListener('contextmenu', (event) => {
            event.preventDefault();
            this.mouse.set(getMousePos(event).x, getMousePos(event).y);
            this.raycaster.setFromCamera(this.mouse, cameraGetter());
            const intersects = this.raycaster.intersectObject(this.interactionPlane);

            if (intersects.length > 0) {
                const p = intersects[0].point;
                const x = Math.round(p.x);
                const y = Math.round(-p.z);
                onRightClick(x, y);
            }
        });
    }

    highlightCells(cells) {
        this.highlightGroup.clear();
        if (!cells || cells.length === 0) return;

        cells.forEach((cell, index) => {
            // 1. Create Canvas with Number
            const canvas = document.createElement('canvas');
            canvas.width = 128;
            canvas.height = 128;
            const ctx = canvas.getContext('2d');

            // Background
            ctx.fillStyle = "rgba(255, 255, 0, 0.5)";
            ctx.fillRect(0, 0, 128, 128);

            // Border
            ctx.strokeStyle = "rgba(255, 255, 0, 1)";
            ctx.lineWidth = 4;
            ctx.strokeRect(0, 0, 128, 128);

            // Text (Step Number)
            ctx.font = "bold 60px Arial";
            ctx.fillStyle = "black";
            ctx.textAlign = "center";
            ctx.textBaseline = "middle";
            ctx.fillText((index + 1).toString(), 64, 64);

            const texture = new THREE.CanvasTexture(canvas);

            const geometry = new THREE.PlaneGeometry(0.9, 0.9);
            const material = new THREE.MeshBasicMaterial({
                map: texture,
                transparent: true,
                side: THREE.DoubleSide
            });

            const mesh = new THREE.Mesh(geometry, material);
            mesh.rotation.x = -Math.PI / 2;
            mesh.position.set(cell.x, 0.05, -cell.y);

            this.highlightGroup.add(mesh);
        });
    }

    addEntity(entity) {
        if (this.entities.has(entity.id)) return;


        // Standee: Larger Size (+30%)
        // Old: 0.8 x 1.2 -> New: 1.04 x 1.56
        const w = 1.04;
        const h = 1.56;
        const geometry = new THREE.PlaneGeometry(w, h);

        this.getTexture(entity.texturePath, (texture) => {
            const material = new THREE.MeshBasicMaterial({
                map: texture,
                transparent: true,
                side: THREE.DoubleSide,
                alphaTest: 0.1
            });
            const mesh = new THREE.Mesh(geometry, material);

            // Store target position for animation logic
            mesh.userData = {
                pathQueue: [],
                isMoving: false,
                startPos: null,
                targetPos: null,
                startTime: 0
            };

            mesh.position.set(entity.x, h / 2, -entity.y);

            // Selection Circle
            const ringGeom = new THREE.RingGeometry(0.3, 0.4, 32);
            const ringMat = new THREE.MeshBasicMaterial({ color: 0xffff00, side: THREE.DoubleSide, transparent: true, opacity: 0.35 });
            const ring = new THREE.Mesh(ringGeom, ringMat);
            ring.rotation.x = -Math.PI / 2;
            ring.position.y = -h / 2 + 0.05;
            ring.visible = false;
            ring.name = "SelectionRing";
            mesh.add(ring);

            this.scene.add(mesh);
            this.entities.set(entity.id, mesh);
        });
    }

    toggleEntitySelection(entityId, isSelected) {
        const mesh = this.entities.get(entityId);
        if (mesh) {
            const ring = mesh.getObjectByName("SelectionRing");
            if (ring) ring.visible = isSelected;
        }
    }

    moveEntityAlongPath(entity, path) {
        const mesh = this.entities.get(entity.id);
        if (mesh) {
            mesh.userData.pathQueue = [...path];
            this.highlightGroup.clear();
        }
    }

    updateEntityPosition(entity) {
        const mesh = this.entities.get(entity.id);
        if (mesh) {
            // Prevent snapping if animation is active
            if (mesh.userData.isMoving || mesh.userData.pathQueue.length > 0) return;
            mesh.position.set(entity.x, 1.56 / 2, -entity.y);
        }
    }

    updateAnimations(time) {
        this.entities.forEach((mesh, id) => {
            const data = mesh.userData;

            if (!data.isMoving && data.pathQueue.length > 0) {
                const nextStep = data.pathQueue.shift();

                data.isMoving = true;
                data.startTime = time;
                data.startPos = mesh.position.clone();
                // Target: x, y (grid) -> x, z (world)
                data.targetPos = new THREE.Vector3(nextStep.x, mesh.position.y, -nextStep.y);
            }

            if (data.isMoving) {
                const duration = 400; // ms per tile
                const elapsed = time - data.startTime;
                const t = Math.min(elapsed / duration, 1);

                // Lerp X/Z
                mesh.position.x = THREE.MathUtils.lerp(data.startPos.x, data.targetPos.x, t);
                mesh.position.z = THREE.MathUtils.lerp(data.startPos.z, data.targetPos.z, t);

                // Jump Arc
                const baseHeight = 1.56 / 2;
                mesh.position.y = baseHeight + (0.5 * Math.sin(t * Math.PI));

                if (t >= 1) {
                    mesh.position.set(data.targetPos.x, baseHeight, data.targetPos.z);
                    data.isMoving = false;

                    // IF Finished Sequence (Queue empty)
                    if (data.pathQueue.length === 0) {
                        // Check if it's the player (id 'p1')
                        if (id === 'p1' && this.onHeroFinishedMove) {
                            // Grid Coords from World Coords (X, -Z)
                            this.onHeroFinishedMove(mesh.position.x, -mesh.position.z);
                        }
                    }
                }
            }
        });
    }
    renderExits(exits) {
        // Cancel any pending render
        if (this._pendingExitRender) {
            this._pendingExitRender = false;
        }

        // Create exitGroup if it doesn't exist
        if (!this.exitGroup) {
            this.exitGroup = new THREE.Group();
            this.scene.add(this.exitGroup);
        }

        if (!exits || exits.length === 0) return;

        // Get existing door cells to avoid duplicates
        const existingDoorCells = new Set();
        this.exitGroup.children.forEach(child => {
            if (child.userData.isDoor) {
                child.userData.cells.forEach(cell => {
                    existingDoorCells.add(`${cell.x},${cell.y}`);
                });
            }
        });

        // Filter out exits that already have doors
        const newExits = exits.filter(ex => !existingDoorCells.has(`${ex.x},${ex.y}`));

        if (newExits.length === 0) {

            return;
        }



        // Set flag for this render
        this._pendingExitRender = true;
        const thisRender = this._pendingExitRender;

        // LOAD TEXTURE
        this.getTexture('/assets/images/dungeon1/doors/door1_closed.png', (texture) => {
            // Check if this render was cancelled
            if (!thisRender || this._pendingExitRender !== thisRender) {
                return;
            }

            const mat = new THREE.MeshBasicMaterial({
                map: texture,
                color: 0xffffff,
                transparent: true,
                side: THREE.DoubleSide
            });

            // Grouping Logic
            const processed = new Set();
            const doors = [];

            // Helper to normalize direction to number
            const normalizeDir = (dir) => {
                if (typeof dir === 'number') return dir;
                const map = { 'N': 0, 'E': 1, 'S': 2, 'W': 3 };
                return map[dir] ?? dir;
            };

            newExits.forEach((ex, i) => {
                const key = `${ex.x},${ex.y}`;
                const exDir = normalizeDir(ex.direction);

                if (processed.has(key)) {
                    return;
                }

                let partner = null;
                for (let j = i + 1; j < newExits.length; j++) {
                    const other = newExits[j];
                    const otherKey = `${other.x},${other.y}`;
                    const otherDir = normalizeDir(other.direction);

                    if (processed.has(otherKey)) continue;

                    if (exDir !== otherDir) {
                        continue;
                    }

                    let isAdj = false;
                    if (exDir === 0 || exDir === 2) {
                        // North/South: check if same Y and adjacent X
                        isAdj = (ex.y === other.y && Math.abs(ex.x - other.x) === 1);
                    } else {
                        // East/West: check if same X and adjacent Y
                        isAdj = (ex.x === other.x && Math.abs(ex.y - other.y) === 1);
                    }

                    if (isAdj) {
                        partner = other;
                        break;
                    }
                }

                if (partner) {
                    doors.push([ex, partner]);
                    processed.add(key);
                    processed.add(`${partner.x},${partner.y}`);
                } else {
                    doors.push([ex]);
                    processed.add(key);
                }
            });

            // Render Doors
            doors.forEach((door, idx) => {
                const d1 = door[0];
                const d2 = door.length > 1 ? door[1] : d1;

                const centerX = (d1.x + d2.x) / 2;
                const centerY = (d1.y + d2.y) / 2;
                const dir = normalizeDir(d1.direction);

                let angle = 0;
                let worldX = centerX;
                let worldZ = -centerY;

                if (dir === 0) {
                    angle = 0;
                    worldZ -= 0.5;
                } else if (dir === 2) {
                    angle = 0;
                    worldZ += 0.5;
                } else if (dir === 1) {
                    angle = Math.PI / 2;
                    worldX += 0.5;
                } else if (dir === 3) {
                    angle = Math.PI / 2;
                    worldX -= 0.5;
                }

                const geom = new THREE.PlaneGeometry(2, 2);
                // Clone material for each door so they can have independent textures
                const doorMat = mat.clone();
                const mesh = new THREE.Mesh(geom, doorMat);

                mesh.position.set(worldX, 1, worldZ);
                mesh.rotation.y = angle;

                // Store door data for interaction (new doors always start closed)
                // Convert numeric direction to string for generator compatibility
                const dirMap = { 0: 'N', 1: 'E', 2: 'S', 3: 'W' };
                mesh.userData = {
                    isDoor: true,
                    isOpen: false,
                    cells: [d1, d2],
                    direction: dir,
                    exitData: {
                        x: d1.x,
                        y: d1.y,
                        direction: dirMap[dir] || 'N'
                    }
                };
                mesh.name = `door_${idx}`;

                this.exitGroup.add(mesh);
            });
        });
    }

    onWindowResize() {
        if (this.camera) {
            this.renderer.setSize(window.innerWidth, window.innerHeight);
        }
    }

    render(camera) {
        if (camera) {
            this.renderer.render(this.scene, camera);
        }
    }

    getTexture(path, onLoad) {
        if (!this.textureCache.has(path)) {

            const tex = this.textureLoader.load(
                path,
                (texture) => {

                    texture.needsUpdate = true;
                    if (onLoad) onLoad(texture);
                },
                undefined,
                (err) => {
                    console.error(`[TextureLoader] ✗ Failed to load: ${path}`, err);
                }
            );
            tex.magFilter = THREE.NearestFilter;
            tex.minFilter = THREE.NearestFilter;
            tex.colorSpace = THREE.SRGBColorSpace;
            this.textureCache.set(path, tex);
        } else {
            // Already cached, call onLoad immediately if texture is ready
            const cachedTex = this.textureCache.get(path);
            if (onLoad && cachedTex.image) {
                onLoad(cachedTex);
            }
        }
        return this.textureCache.get(path);
    }

    addTile(cells, type, tileDef, tileInstance) {
        // cells: Array of {x, y} global coordinates
        // tileDef: The definition object (has textures, dimensions)
        // tileInstance: The instance object (has x, y, rotation, id)



        // Draw Texture Plane (The Image) - WAIT FOR TEXTURE TO LOAD
        if (tileDef && tileInstance && tileDef.textures && tileDef.textures.length > 0) {

            const texturePath = tileDef.textures[0];

            // Load texture with callback
            this.getTexture(texturePath, (texture) => {

                // --- NEW LOGIC: Calculate center based on DIMENSIONS, not CELLS ---

                // 1. Get the specific variant for this rotation to know the VISUAL bounds
                //    (The shape the grid sees: e.g. 4x2 for East)
                const currentVariant = tileDef.variants[tileInstance.rotation];

                if (!currentVariant) {
                    console.error(`[GameRenderer] Missing variant for rotation ${tileInstance.rotation}`);
                    return;
                }

                const rotWidth = currentVariant.width;
                const rotHeight = currentVariant.height;

                // 2. Calculate the Geometric Center of the tile relative to the anchor
                //    Formula: anchor + (dimension - 1) / 2
                //    (Subtract 1 because width 1 is just offset 0)
                const cx = tileInstance.x + (rotWidth - 1) / 2;
                const cy = tileInstance.y + (rotHeight - 1) / 2;



                // 3. Use BASE dimensions from NORTH variant for the Plane 
                //    (Since we are rotating the plane itself, we start with the un-rotated image size)
                const baseWidth = tileDef.variants.N.width;
                const baseHeight = tileDef.variants.N.height;

                // Create Plane with BASE dimensions
                const geometry = new THREE.PlaneGeometry(baseWidth, baseHeight);

                const material = new THREE.MeshBasicMaterial({
                    map: texture,
                    transparent: true,
                    side: THREE.FrontSide, // Only visible from top
                    alphaTest: 0.1
                });
                const plane = new THREE.Mesh(geometry, material);

                // Initial Rotation: Plane X-Y to X-Z (Flat on ground)
                plane.rotation.x = -Math.PI / 2;

                // Handle Rotation
                const rotMap = { 'N': 0, 'E': 1, 'S': 2, 'W': 3 };
                const r = rotMap[tileInstance.rotation] !== undefined ? rotMap[tileInstance.rotation] : 0;

                // Apply Tile Rotation (Z-axis is Up in this local frame before X-rotation? No, after X-rot)
                // Actually, standard hierarchy: Rotate Z first? 
                // ThreeJS rotation order XYZ. 
                // We want to rotate around the Y axis of the world (which is Z of the plane before x-rotation?)
                // Simplest: Rotate Z of the plane, which corresponds to world Y.
                // Note: We use negative rotation because ThreeJS is CCW, but our grid might be different, 
                // but usually -r * PI/2 works for this setup.
                plane.rotation.z = -r * (Math.PI / 2);

                // Position at the calculated center
                // Notice: World Z is -Grid Y
                plane.position.set(cx, 0.01, -cy);
                plane.receiveShadow = true;

                this.scene.add(plane);

            });
        } else {
            console.warn(`[GameRenderer] details missing for texture render. def: ${!!tileDef}, inst: ${!!tileInstance}`);
        }
    }

    openDoor(doorMesh) {
        if (!doorMesh || !doorMesh.userData.isDoor) return;
        if (doorMesh.userData.isOpen) return; // Already open

        // Load open door texture
        this.getTexture('/assets/images/dungeon1/doors/door1_open.png', (texture) => {
            doorMesh.material.map = texture;
            doorMesh.material.needsUpdate = true;
            doorMesh.userData.isOpen = true;

        });
    }

    getDoorAtPosition(x, y) {
        if (!this.exitGroup) return null;

        // Check all doors in exitGroup
        for (const child of this.exitGroup.children) {
            if (child.userData.isDoor) {
                // Check if any of the door's cells match the position
                for (const cell of child.userData.cells) {
                    if (cell.x === x && cell.y === y) {
                        return child;
                    }
                }
            }
        }
        return null;
    }

    isPlayerAdjacentToDoor(playerX, playerY, doorMesh) {
        if (!doorMesh || !doorMesh.userData.isDoor) return false;

        // Check if player is adjacent to any of the door's cells
        for (const cell of doorMesh.userData.cells) {
            const dx = Math.abs(playerX - cell.x);
            const dy = Math.abs(playerY - cell.y);

            // Adjacent means distance of 1 in one direction and 0 in the other
            if ((dx === 1 && dy === 0) || (dx === 0 && dy === 1)) {
                return true;
            }
        }
        return false;
    }

    blockDoor(exitData) {
        if (!this.exitGroup || !exitData) return;

        // Find the door mesh
        let targetDoor = null;

        for (const child of this.exitGroup.children) {
            if (child.userData.isDoor) {
                // Check if this door corresponds to the exitData
                // exitData has x,y of one of the cells
                for (const cell of child.userData.cells) {
                    if (cell.x === exitData.x && cell.y === exitData.y) {
                        targetDoor = child;
                        break;
                    }
                }
            }
            if (targetDoor) break;
        }

        if (targetDoor) {
            this.getTexture('/assets/images/dungeon1/doors/door1_blocked.png', (texture) => {
                targetDoor.material.map = texture;
                targetDoor.material.needsUpdate = true;
                targetDoor.userData.isBlocked = true;
                targetDoor.userData.isOpen = false; // Ensure strictly not open
            });
        }
    }

    // ========== MANUAL PLACEMENT SYSTEM ==========

    enableDoorSelection(enabled) {
        this.doorSelectionEnabled = enabled;

        if (enabled) {
            // Highlight available exits
            this.highlightAvailableExits();
        } else {
            // Remove highlights
            if (this.exitHighlightGroup) {
                this.exitHighlightGroup.clear();
            }
        }
    }

    highlightAvailableExits() {
        if (!this.exitHighlightGroup) {
            this.exitHighlightGroup = new THREE.Group();
            this.scene.add(this.exitHighlightGroup);
        }

        this.exitHighlightGroup.clear();

        // Highlight each exit door with a pulsing glow
        if (this.exitGroup) {
            this.exitGroup.children.forEach(doorMesh => {
                if (doorMesh.userData.isDoor && !doorMesh.userData.isOpen) {
                    // Create highlight ring
                    const ringGeom = new THREE.RingGeometry(1.2, 1.4, 32);
                    const ringMat = new THREE.MeshBasicMaterial({
                        color: 0x00ff00,
                        side: THREE.DoubleSide,
                        transparent: true,
                        opacity: 0.6
                    });
                    const ring = new THREE.Mesh(ringGeom, ringMat);
                    ring.rotation.x = -Math.PI / 2;
                    ring.position.copy(doorMesh.position);
                    ring.position.y = 0.05;


                    // Store reference to door for click handling
                    doorMesh.userData.isExit = true;
                    // Create proper exit data with all required fields
                    const firstCell = doorMesh.userData.cells[0];
                    // Convert numeric direction (0,1,2,3) to string ('N','E','S','W')
                    const dirMap = { 0: 'N', 1: 'E', 2: 'S', 3: 'W' };
                    doorMesh.userData.exitData = {
                        x: firstCell.x,
                        y: firstCell.y,
                        direction: dirMap[doorMesh.userData.direction] || 'N'
                    };

                    this.exitHighlightGroup.add(ring);
                }
            });
        }
    }

    showPlacementPreview(preview) {
        if (!preview) {
            this.hidePlacementPreview();
            return;
        }

        // Create preview groups if they don't exist
        if (!this.previewGroup) {
            this.previewGroup = new THREE.Group();
            this.scene.add(this.previewGroup);
        }

        if (!this.projectionGroup) {
            this.projectionGroup = new THREE.Group();
            this.scene.add(this.projectionGroup);
        }

        // Clear previous preview
        this.previewGroup.clear();
        this.projectionGroup.clear();

        const { card, cells, isValid, x, y, rotation } = preview;

        // Calculate bounds for tile - OLD LOGIC (Removed)
        // Note: We ignore 'cells' for positioning the texture, but keep them for the Ground Projection (Green/Red squares)

        // 1. FLOATING TILE (Y = 3)
        if (card.textures && card.textures.length > 0) {
            this.getTexture(card.textures[0], (texture) => {

                // Get Current Rotation Variant for Dimensions
                const currentVariant = card.variants[rotation];
                const rotWidth = currentVariant.width;
                const rotHeight = currentVariant.height;

                // Calculate Center based on Anchor (x, y) and Dimensions
                const cx = x + (rotWidth - 1) / 2;
                const cy = y + (rotHeight - 1) / 2;

                // Use BASE dimensions from NORTH variant
                const baseWidth = card.variants.N.width;
                const baseHeight = card.variants.N.height;

                const geometry = new THREE.PlaneGeometry(baseWidth, baseHeight);
                const material = new THREE.MeshBasicMaterial({
                    map: texture,
                    transparent: true,
                    opacity: 0.8,
                    side: THREE.DoubleSide
                });
                const floatingTile = new THREE.Mesh(geometry, material);

                floatingTile.rotation.x = -Math.PI / 2;

                // Apply Z rotation
                const rotMap = { 'N': 0, 'E': 1, 'S': 2, 'W': 3 };
                const r = rotMap[rotation] !== undefined ? rotMap[rotation] : 0;
                floatingTile.rotation.z = -r * (Math.PI / 2);



                floatingTile.position.set(cx, 3, -cy);
                this.previewGroup.add(floatingTile);
            });
        }

        // 2. GROUND PROJECTION (Green/Red/Blue)
        const baseColor = isValid ? 0x00ff00 : 0xff0000;

        // Calculate global exit positions
        const exitKeys = new Set();
        if (preview.variant && preview.variant.exits) {
            preview.variant.exits.forEach(ex => {
                const gx = x + ex.x;
                const gy = y + ex.y;
                exitKeys.add(`${gx},${gy}`);
            });
        }

        cells.forEach(cell => {
            const key = `${cell.x},${cell.y}`;
            let color = baseColor;

            // If this cell is an exit, color it Blue
            if (exitKeys.has(key)) {
                color = 0x0000ff; // Blue
            }

            const geometry = new THREE.PlaneGeometry(0.95, 0.95);
            const material = new THREE.MeshBasicMaterial({
                color: color,
                transparent: true,
                opacity: 0.5,
                side: THREE.DoubleSide
            });
            const projection = new THREE.Mesh(geometry, material);
            projection.rotation.x = -Math.PI / 2;
            projection.position.set(cell.x, 0.02, -cell.y);
            this.projectionGroup.add(projection);
        });
    }

    hidePlacementPreview() {
        if (this.previewGroup) {
            this.previewGroup.clear();
        }
        if (this.projectionGroup) {
            this.projectionGroup.clear();
        }
    }

}
