{% extends 'base.html' %}

{% block content %}
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
    <h1 class="h2"><i class="bi bi-grid-3x3 me-2 text-primary"></i>{{ _('Plater / Build Plate') }}</h1>
</div>

<div class="row" style="height: 70vh;">
    
    
    <!-- 3D Area -->
    <div class="col-md-9 h-100 position-relative">
        <div id="plater-container" class="w-100 h-100 rounded shadow-sm border border-secondary" style="overflow: hidden; background: #f8f9fa;"></div>
        
        <div class="position-absolute top-50 start-0 translate-middle-y ms-3 p-2 bg-white rounded shadow-sm d-flex flex-column gap-2 opacity-75" style="z-index: 10;">
            <button class="btn btn-primary btn-sm rounded" id="btn-translate" title="{{ _('Translate (W)') }}" onclick="setTransformMode('translate')"><i class="bi bi-arrows-move"></i></button>
            <button class="btn btn-outline-secondary btn-sm rounded" id="btn-rotate" title="{{ _('Rotate (E)') }}" onclick="setTransformMode('rotate')"><i class="bi bi-arrow-clockwise"></i></button>
            <button class="btn btn-outline-secondary btn-sm rounded" id="btn-scale" title="{{ _('Scale (R)') }}" onclick="setTransformMode('scale')"><i class="bi bi-arrows-angle-expand"></i></button>
            <hr class="m-0 border-secondary">
            <button class="btn btn-outline-info btn-sm rounded" id="btn-layflat" title="{{ _('Lay Flat') }}" onclick="setTransformMode('layflat')"><i class="bi bi-symmetry-horizontal"></i></button>
            <button class="btn btn-outline-danger btn-sm rounded mt-2" id="btn-remove" title="{{ _('Remove Selected (Del)') }}" onclick="removeActiveModel()"><i class="bi bi-trash3"></i></button>
        </div>
    </div>

    <!-- Sidebar -->
    <div class="col-md-3 h-100 d-flex flex-column pb-3" style="overflow-y: auto; overflow-x: hidden;">
        <div class="card shadow-sm mb-3">
            <div class="card-header bg-light fw-bold text-secondary d-flex justify-content-between align-items-center" style="cursor: pointer; z-index: 10;" data-bs-toggle="collapse" data-bs-target="#collapseModels" aria-expanded="true">
                <span><i class="bi bi-layers-fill me-2"></i>{{ _('Available Models') }}</span>
                <i class="bi bi-chevron-bar-contract"></i>
            </div>
            <div id="collapseModels" class="collapse show">
                <div class="list-group list-group-flush" id="model-list" style="max-height: 250px; overflow-y: auto;">
                    {% for model in models %}
                    <button id="add-model-btn-{{ model.id }}" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" data-matrix="{{ model.transform_matrix or '' }}" onclick="addModelToPlate(this, {{ model.id }}, '{{ model.url }}', '{{ model.name }}', '{{ model.status }}')">
                        <span class="text-truncate">{{ model.name }}</span>
                        <i class="bi bi-plus-circle text-success"></i>
                    </button>
                    {% else %}
                    <div class="p-3 text-center text-muted">{{ _("No STL models uploaded yet. Go upload some first.") }}</div>
                    {% endfor %}
                </div>
            </div>
        </div>
        
        <div class="card shadow-sm mb-3 flex-shrink-0">
            <div class="card-header bg-light fw-bold text-secondary d-flex justify-content-between align-items-center" style="cursor: pointer;" data-bs-toggle="collapse" data-bs-target="#collapseSettings" aria-expanded="true">
                <span><i class="bi bi-sliders me-2"></i>{{ _('Other Settings') }}</span>
                <i class="bi bi-chevron-bar-contract"></i>
            </div>
            <div id="collapseSettings" class="collapse show">
                <div class="card-body py-2">
                    <div class="mb-2">
                        <label for="infill-density" class="form-label text-secondary small mb-1">{{ _('Infill Density') }} (%)</label>
                        <input type="number" class="form-control form-control-sm" id="infill-density" value="20" min="0" max="100">
                    </div>
                    <div class="mb-2">
                        <label for="support-type" class="form-label text-secondary small mb-1">{{ _('Support') }}</label>
                        <select class="form-select form-select-sm" id="support-type">
                            <option value="false">{{ _('None') }}</option>
                            <option value="buildplate">{{ _('Touching Buildplate') }}</option>
                            <option value="true">{{ _('Everywhere') }}</option>
                        </select>
                    </div>
                    <div class="mb-2">
                        <label for="support-pattern" class="form-label text-secondary small mb-1">{{ _('Support Type') }}</label>
                        <select class="form-select form-select-sm" id="support-pattern" disabled>
                            <option value="lines">{{ _('Lines') }} ({{ _('默认线状') }})</option>
                            <option value="grid">{{ _('Grid') }} ({{ _('网格状') }})</option>
                            <option value="triangles">{{ _('Triangles') }} ({{ _('三角网') }})</option>
                            <option value="zigzag">{{ _('ZigZag') }} ({{ _('之字形') }})</option>
                            <option value="tree">{{ _('Tree') }} ({{ _('树状') }})</option>
                        </select>
                    </div>
                </div>
            </div>
        </div>
        
        <div class="card shadow-sm flex-shrink-0">
            <div class="card-header bg-light fw-bold text-secondary d-flex justify-content-between align-items-center" style="cursor: pointer;" data-bs-toggle="collapse" data-bs-target="#collapseQuality" aria-expanded="true">
                <span><i class="bi bi-gear-wide-connected me-2"></i>{{ _('Quality Profile') }}</span>
                <i class="bi bi-chevron-bar-contract"></i>
            </div>
            <div id="collapseQuality" class="collapse show">
                <div class="card-body">
                    <div class="mb-3">
                        <select class="form-select bg-light" id="quality">
                            {% for key, name in presets %}
                            <option value="{{ key }}" {% if key == last_quality %}selected{% endif %}>{{ _(name) }}</option>
                            {% endfor %}
                        </select>
                    </div>
                    
                    <hr>
                    
                    <div class="d-flex justify-content-between">
                        <button class="btn btn-outline-danger btn-sm" onclick="clearPlate()"><i class="bi bi-trash me-1"></i>{{ _('Clear Board') }}</button>
                        <button class="btn btn-primary" onclick="mergeAndSlice()" id="btn-merge"><i class="bi bi-gear-fill me-2" id="merge-icon"></i><span id="merge-text">{{ _('Merge & Slice') }}</span></button>
                    </div>
                </div>
            </div>
        </div>
    </div>

</div>

<script src="{{ url_for('static', filename='js/three.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/OrbitControls.js') }}"></script>
<script src="{{ url_for('static', filename='js/TransformControls.js') }}"></script>
<script src="{{ url_for('static', filename='js/STLLoader.js') }}"></script>

<script>
// Toggle icons on collapse
document.addEventListener('DOMContentLoaded', function() {
    const cards = document.querySelectorAll('.collapse');
    cards.forEach(card => {
        card.addEventListener('show.bs.collapse', function () {
            const icon = this.previousElementSibling.querySelector('i.bi-chevron-bar-expand');
            if(icon) {
                icon.classList.remove('bi-chevron-bar-expand');
                icon.classList.add('bi-chevron-bar-contract');
            }
        });
        card.addEventListener('hide.bs.collapse', function () {
            const icon = this.previousElementSibling.querySelector('i.bi-chevron-bar-contract');
            if(icon) {
                icon.classList.remove('bi-chevron-bar-contract');
                icon.classList.add('bi-chevron-bar-expand');
            }
        });
    });
});

let scene, camera, renderer, orbit, transformControl, transformProxy, gridHelper, bedBoxOutline;
let boundPlanes = {};
let bedWidth = {{ w }};
let bedDepth = {{ h }};
let bedHeight = {{ hd }};
let offsetX = {{ offset_x|default(0) }};
let offsetY = {{ offset_y|default(0) }};
let loadedModels = [];
let activeModel = null;

initPlater();
animate();

function initPlater() {
    const container = document.getElementById('plater-container');
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xe9ecef);
    
    camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 1, 3000);
    camera.position.set(0, -bedDepth * 1.2, bedHeight);
    
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(container.clientWidth, container.clientHeight);
    container.appendChild(renderer.domElement);
    
    // Lights
    scene.add(new THREE.AmbientLight(0x888888));
    const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
    dirLight.position.set(100, 100, 200);
    scene.add(dirLight);

    // Bed Grid
    const gridSizeX = bedWidth;
    const gridSizeY = bedDepth;
    const maxGridSize = Math.max(gridSizeX, gridSizeY);
    // Divisions needed to make each square exactly 10mm wide
    const gridDivisions = Math.ceil(maxGridSize / 10);
    gridHelper = new THREE.GridHelper(gridDivisions * 10, gridDivisions, 0xbbbbbb, 0xdddddd);
    gridHelper.rotation.x = Math.PI / 2;
    scene.add(gridHelper);

    // Bed Origin Axes (Bottom-Left Corner)
    const axesHelper = new THREE.AxesHelper(maxGridSize / 4);
    axesHelper.position.set(-bedWidth / 2, -bedDepth / 2, 0.2);
    scene.add(axesHelper);

    // Show Bed Box outline
    const boxGeo = new THREE.BoxGeometry(bedWidth, bedDepth, bedHeight);
    const edges = new THREE.EdgesGeometry(boxGeo);
    bedBoxOutline = new THREE.LineSegments(edges, new THREE.LineBasicMaterial( { color: 0xcccccc } ));
    bedBoxOutline.position.z = bedHeight / 2;
    scene.add(bedBoxOutline);
    
    // Warning planes
    const planeMat = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.4, side: THREE.DoubleSide, depthWrite: false });
    boundPlanes = {
        minX: new THREE.Mesh(new THREE.PlaneGeometry(bedDepth, bedHeight), planeMat),
        maxX: new THREE.Mesh(new THREE.PlaneGeometry(bedDepth, bedHeight), planeMat),
        minY: new THREE.Mesh(new THREE.PlaneGeometry(bedWidth, bedHeight), planeMat),
        maxY: new THREE.Mesh(new THREE.PlaneGeometry(bedWidth, bedHeight), planeMat),
        minZ: new THREE.Mesh(new THREE.PlaneGeometry(bedWidth, bedDepth), planeMat),
        maxZ: new THREE.Mesh(new THREE.PlaneGeometry(bedWidth, bedDepth), planeMat)
    };
    
    boundPlanes.minX.rotation.y = Math.PI / 2;
    boundPlanes.minX.rotation.z = Math.PI / 2;
    boundPlanes.minX.position.set(-bedWidth/2, 0, bedHeight/2);
    
    boundPlanes.maxX.rotation.y = Math.PI / 2;
    boundPlanes.maxX.rotation.z = Math.PI / 2;
    boundPlanes.maxX.position.set(bedWidth/2, 0, bedHeight/2);
    
    boundPlanes.minY.rotation.x = Math.PI / 2;
    boundPlanes.minY.position.set(0, -bedDepth/2, bedHeight/2);
    
    boundPlanes.maxY.rotation.x = Math.PI / 2;
    boundPlanes.maxY.position.set(0, bedDepth/2, bedHeight/2);
    
    boundPlanes.minZ.position.set(0, 0, 0); // bottom
    
    boundPlanes.maxZ.position.set(0, 0, bedHeight); // top
    
    for (let key in boundPlanes) {
        boundPlanes[key].visible = false;
        scene.add(boundPlanes[key]);
    }
    
    // Controls
    orbit = new THREE.OrbitControls(camera, renderer.domElement);
    orbit.enableDamping = true;
    orbit.target.set(0, 0, 0);
    
    transformProxy = new THREE.Object3D();
    scene.add(transformProxy);

    transformControl = new THREE.TransformControls(camera, renderer.domElement);
    transformControl.setSpace('world');
    transformControl.addEventListener('dragging-changed', function (event) {
        orbit.enabled = !event.value;
        if (!event.value && activeModel) {
            scene.attach(activeModel);
            transformProxy.position.copy(activeModel.getWorldPosition(new THREE.Vector3()));
            transformProxy.rotation.set(0, 0, 0);
            transformProxy.scale.set(1, 1, 1);
            transformProxy.attach(activeModel);
        }
    });
    scene.add(transformControl);

    window.addEventListener('resize', onWindowResize);
    window.addEventListener('keydown', onKeyDown);
    renderer.domElement.addEventListener('pointerdown', onPointerDown);
}

function onWindowResize() {
    const container = document.getElementById('plater-container');
    camera.aspect = container.clientWidth / container.clientHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(container.clientWidth, container.clientHeight);
}

let layFlatMode = false;

function setTransformMode(mode) {
    if (mode !== 'layflat') {
        transformControl.setMode(mode);
        transformControl.setSpace(mode === 'scale' ? 'local' : 'world');
        layFlatMode = false;
        
        document.getElementById('btn-translate').className = mode === 'translate' ? 'btn btn-primary btn-sm rounded' : 'btn btn-outline-secondary btn-sm rounded';
        document.getElementById('btn-rotate').className = mode === 'rotate' ? 'btn btn-primary btn-sm rounded' : 'btn btn-outline-secondary btn-sm rounded';
        document.getElementById('btn-scale').className = mode === 'scale' ? 'btn btn-primary btn-sm rounded' : 'btn btn-outline-secondary btn-sm rounded';
        document.getElementById('btn-layflat').className = 'btn btn-outline-info btn-sm rounded';
    } else {
        layFlatMode = true;
        document.getElementById('btn-translate').className = 'btn btn-outline-secondary btn-sm rounded';
        document.getElementById('btn-rotate').className = 'btn btn-outline-secondary btn-sm rounded';
        document.getElementById('btn-scale').className = 'btn btn-outline-secondary btn-sm rounded';
        document.getElementById('btn-layflat').className = 'btn btn-info btn-sm rounded text-white';
        transformControl.detach();
    }
}

function removeActiveModel() {
    if (activeModel) {
        removeModel(activeModel);
    }
}

function onKeyDown(event) {
    switch (event.key.toLowerCase()) {
        case 'w': setTransformMode('translate'); break;
        case 'e': setTransformMode('rotate'); break;
        case 'r': setTransformMode('scale'); break;
        case 'backspace':
        case 'delete':
            removeActiveModel();
            break;
    }
}

function onPointerDown(event) {
    if(transformControl.dragging) return;
    const container = document.getElementById('plater-container');
    const rect = renderer.domElement.getBoundingClientRect();
    const pointer = new THREE.Vector2();
    pointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
    pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
    
    const raycaster = new THREE.Raycaster();
    raycaster.setFromCamera(pointer, camera);
    const intersects = raycaster.intersectObjects(loadedModels, true);
    
    if (layFlatMode) {
        if (intersects.length > 0) {
            const hit = intersects[0];
            const obj = hit.object;
            const face = hit.face;
            if (face) {
                // Ensure model is in world space before applying transformations
                scene.attach(obj);
                
                // The target normal to align with (pointing downward to the bed, Z = -1)
                const targetNormal = new THREE.Vector3(0, 0, -1);
                
                // Get the face's normal in world space
                let localNormal = face.normal.clone();
                let currentWorldNormal = localNormal.transformDirection(obj.matrixWorld).normalize();

                // Compute quaternion to rotate current normal to point straight down
                let quaternion = new THREE.Quaternion();
                quaternion.setFromUnitVectors(currentWorldNormal, targetNormal);
                
                // Apply global rotation
                obj.quaternion.premultiply(quaternion);
                obj.updateMatrixWorld(true);
                
                // Snap to bed (Z=0)
                obj.geometry.computeBoundingBox();
                const box = obj.geometry.boundingBox.clone();
                box.applyMatrix4(obj.matrixWorld);
                const minZ = box.min.z;
                obj.position.z -= minZ;
                obj.updateMatrixWorld(true);
                
                // Exit lay flat mode and reset to translate
                setTransformMode('translate');
                selectModel(obj);
            }
        }
        return;
    }
    
    if (intersects.length > 0) {
        selectModel(intersects[0].object);
    } else {
        selectModel(null);
    }
}

function selectModel(model) {
    if (activeModel && activeModel !== model) {
        scene.attach(activeModel);
    }
    activeModel = model;
    if (model) {
        scene.attach(model);
        transformProxy.position.copy(model.getWorldPosition(new THREE.Vector3()));
        transformProxy.rotation.set(0, 0, 0);
        transformProxy.scale.set(1, 1, 1);
        transformProxy.attach(model);
        transformControl.attach(transformProxy);
    } else {
        transformControl.detach();
    }
}

function removeModel(model) {
    if (activeModel === model) {
        transformControl.detach();
        scene.attach(model);
        activeModel = null;
    }
    scene.remove(model);
    loadedModels = loadedModels.filter(m => m !== model);
}

function clearPlate() {
    transformControl.detach();
    loadedModels.forEach(m => {
        scene.attach(m);
        scene.remove(m);
    });
    loadedModels = [];
    activeModel = null;
}

function addModelToPlate(btnElement, fileId, url, name, status) {
    const iconSpan = btnElement.querySelector('i');
    const originalClass = iconSpan.className;
    iconSpan.className = 'spinner-border spinner-border-sm text-primary';
    btnElement.disabled = true;

    const loader = new THREE.STLLoader();
    loader.load(url, function (geometry) {
        // By default STLs center or are offset, let's normalize slightly to be on top of the plate
        geometry.computeBoundingBox();
        const center = geometry.boundingBox.getCenter(new THREE.Vector3());
        const minZ = geometry.boundingBox.min.z;
        geometry.translate(-center.x, -center.y, -minZ);

        const material = new THREE.MeshPhongMaterial({ color: 0x0d6efd, specular: 0x111111, shininess: 200 });
        const mesh = new THREE.Mesh(geometry, material);
        mesh.userData = { 
            fileId: fileId, 
            name: name,
            status: status,
            geomTrans: new THREE.Matrix4().makeTranslation(-center.x, -center.y, -minZ)
        };
        
        let matrixData = btnElement.getAttribute('data-matrix');
        if (matrixData && matrixData.trim() !== '' && matrixData !== 'None') {
            try {
                let mArray = JSON.parse(matrixData);
                let savedMatrix = new THREE.Matrix4().fromArray(mArray);
                savedMatrix.decompose(mesh.position, mesh.quaternion, mesh.scale);
            } catch (e) {
                console.error('Failed to parse saved matrix:', e);
                mesh.position.x = (Math.random() - 0.5) * 50;
                mesh.position.y = (Math.random() - 0.5) * 50;
            }
        } else {
            // Random slight offset so they don't exactly stack
            mesh.position.x = (Math.random() - 0.5) * 50;
            mesh.position.y = (Math.random() - 0.5) * 50;
        }
        
        scene.add(mesh);
        loadedModels.push(mesh);
        selectModel(mesh);

        iconSpan.className = originalClass;
        btnElement.disabled = false;
    }, undefined, function (error) {
        console.error(error);
        iconSpan.className = originalClass;
        btnElement.disabled = false;
        alert("{{ _('Error loading STL model file.') }}");
    });
}

function checkBounds() {
    if (!bedBoxOutline) return false;
    let boundsViolation = {
        minX: false, maxX: false,
        minY: false, maxY: false,
        minZ: false, maxZ: false
    };
    
    let outOfBounds = false;
    
    for (let i = 0; i < loadedModels.length; i++) {
        let m = loadedModels[i];
        let box = new THREE.Box3().setFromObject(m);
        
        if (box.min.x < -bedWidth / 2 - 0.05) boundsViolation.minX = true;
        if (box.max.x > bedWidth / 2 + 0.05) boundsViolation.maxX = true;
        if (box.min.y < -bedDepth / 2 - 0.05) boundsViolation.minY = true;
        if (box.max.y > bedDepth / 2 + 0.05) boundsViolation.maxY = true;
        if (box.min.z < -0.05) boundsViolation.minZ = true;
        if (box.max.z > bedHeight + 0.05) boundsViolation.maxZ = true;
    }
    
    outOfBounds = boundsViolation.minX || boundsViolation.maxX || 
                  boundsViolation.minY || boundsViolation.maxY || 
                  boundsViolation.minZ || boundsViolation.maxZ;
                  
    for (let key in boundsViolation) {
        if (boundPlanes && boundPlanes[key]) {
            boundPlanes[key].visible = boundsViolation[key];
        }
    }
    
    if (outOfBounds) {
        bedBoxOutline.material.color.setHex(0xffaaaa);
    } else {
        bedBoxOutline.material.color.setHex(0xcccccc);
    }
    return outOfBounds;
}

function animate() {
    requestAnimationFrame(animate);
    checkBounds();
    orbit.update();
    renderer.render(scene, camera);
}

function mergeAndSlice() {
    if (loadedModels.length === 0) {
        alert("{{ _('Please add at least one model to the build plate.') }}");
        return;
    }
    
    if (checkBounds()) {
        alert("{{ _('One or more models are outside the print area. Please adjust them before slicing.') }}");
        return;
    }
    
    if (loadedModels.length === 1) {
        const singleModel = loadedModels[0];
        if (singleModel.userData.status === 'sliced') {
            if (!confirm("{{ _('This model has already been sliced. The existing GCode will be overwritten. Continue?') }}")) {
                return;
            }
        }
    }
    
    const pieces = loadedModels.map(m => {
        m.updateMatrixWorld(true);
        const mat = m.matrixWorld.clone();
        if (m.userData.geomTrans) {
            mat.multiply(m.userData.geomTrans);
        }
        const translation = new THREE.Matrix4().makeTranslation((bedWidth / 2) + offsetX, (bedDepth / 2) + offsetY, 0);
        mat.premultiply(translation);
        return {
            file_id: m.userData.fileId,
            matrix: mat.elements, // Array of 16 numbers used for slicing
            raw_matrix: m.matrix.elements // Local visual properties
        };
    });
    
    const quality = document.getElementById('quality').value;
    const infill = document.getElementById('infill-density').value;
    const support = document.getElementById('support-type').value;
    const supportPattern = document.getElementById('support-pattern').value;
    
    const btn = document.getElementById('btn-merge');
    const icon = document.getElementById('merge-icon');
    const text = document.getElementById('merge-text');
    
    btn.disabled = true;
    icon.className = 'spinner-border spinner-border-sm me-2';
    text.innerText = '{{ _("Slicing queued!") }}';
    
    // Ajax request
    fetch('{{ url_for("main.merge_and_slice") }}', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ pieces: pieces, quality: quality, infill: infill, support: support, support_pattern: supportPattern })
    })
    .then(response => response.json())
    .then(data => {
        if(data.success) {
            window.location.href = "{{ url_for('main.files') }}";
        } else {
            alert("Error: " + data.error);
            btn.disabled = false;
            icon.className = 'bi bi-gear-fill me-2';
            text.innerText = '{{ _("Merge & Slice") }}';
        }
    })
    .catch(err => {
        alert("Error: " + String(err));
        btn.disabled = false;
        icon.className = 'bi bi-gear-fill me-2';
        text.innerText = '{{ _("Merge & Slice") }}';
    });
}

document.addEventListener('DOMContentLoaded', () => {
    const supportType = document.getElementById('support-type');
    const supportPattern = document.getElementById('support-pattern');
    if (supportType && supportPattern) {
        supportType.addEventListener('change', function() {
            supportPattern.disabled = (this.value === 'false');
        });
    }

    const params = new URLSearchParams(window.location.search);
    const addId = params.get('add');
    if (addId) {
        const btn = document.getElementById('add-model-btn-' + addId);
        if (btn) {
            btn.click();
        }
    }
});
</script>
{% endblock %}
