// visualizer.js
const orbCanvas = document.getElementById('orbCanvas');
const orbCtx = orbCanvas.getContext('2d');
const statusText = document.getElementById('status-text');
const userVideo = document.getElementById('userVideo');
const canvasOverlay = document.getElementById('enrollmentCanvas');
const overlayCtx = canvasOverlay.getContext('2d');

// Orb Visualizer Dimensions
// Internal resolution stays 600 so the gradient looks sharp
orbCanvas.width = 600;
orbCanvas.height = 600;

// Internal resolution for biometric data stays 640x480
canvasOverlay.width = 640;
canvasOverlay.height = 480;

let audioContext;
let analyser;
let dataArray;
let primed = false;
let lastAudio = Date.now();
let nextStartTime = 0; 
const BUFFER_LOOKAHEAD = 0.1;

// Load Shutter Sound
const shutterSound = new Audio('/app/content/js/shutter.mp3');
shutterSound.load(); 

function takePhoto() {
    primed = false;
    shutterSound.currentTime = 0; 
    shutterSound.play();
    // Re-prime after a delay to allow the AI to process the image
    setTimeout(function() {
        primed = true;
    }, 1000);
}

/**
 * Renders the Mirrored Video and the Vertical Oval Guide
 * onto the enrollmentCanvas.
 */
function startMirrorLoop() {
    function render() {
        // 1. Clear the specific overlay canvas
        overlayCtx.clearRect(0, 0, canvasOverlay.width, canvasOverlay.height);

        // 2. DRAW MIRRORED VIDEO
        overlayCtx.save();
        overlayCtx.translate(canvasOverlay.width, 0);
        overlayCtx.scale(-1, 1); 
        overlayCtx.drawImage(userVideo, 0, 0, canvasOverlay.width, canvasOverlay.height);
        overlayCtx.restore();

        // 3. DRAW CENTERED OVAL OVERLAY
        const centerX = canvasOverlay.width / 2;
        const centerY = canvasOverlay.height / 2;
		const radiusX = 165; 
		        const radiusY = 225;
        const verticalOffset = 0; // Shift up slightly for eye alignment
        const activeCenterY = centerY + verticalOffset;

        // Dimmed Background
        overlayCtx.fillStyle = "rgba(0, 0, 0, 0.6)";
        overlayCtx.beginPath();
        overlayCtx.rect(0, 0, canvasOverlay.width, canvasOverlay.height);
        overlayCtx.ellipse(centerX, activeCenterY, radiusX, radiusY, 0, 0, Math.PI * 2, true);
        overlayCtx.fill();

        // Green Dashed Border
        overlayCtx.strokeStyle = "#00FF00";
        overlayCtx.lineWidth = 3;
        overlayCtx.setLineDash([10, 10]);
        overlayCtx.beginPath();
        overlayCtx.ellipse(centerX, activeCenterY, radiusX, radiusY, 0, 0, Math.PI * 2);
        overlayCtx.stroke();
        
        // Target Crosshair
        overlayCtx.setLineDash([]);
        overlayCtx.strokeStyle = "rgba(0, 255, 0, 0.3)";
        overlayCtx.beginPath();
        overlayCtx.moveTo(centerX - 10, activeCenterY);
        overlayCtx.lineTo(centerX + 10, activeCenterY);
        overlayCtx.moveTo(centerX, activeCenterY - 10);
        overlayCtx.lineTo(centerX, activeCenterY + 10);
        overlayCtx.stroke();

        requestAnimationFrame(render);
    }
    render();
}

/**
 * The Vision Loop captures a raw (unmirrored) square crop 
 * and sends it to the Java backend via WebSocket.
 */
function startVisionLoop() {
    const captureCanvas = document.createElement('canvas');
    const captureCtx = captureCanvas.getContext('2d');
    captureCanvas.width = 480;
    captureCanvas.height = 480;

    setInterval(() => {
        if(window.socket && window.socket.readyState === WebSocket.OPEN && primed) {
            // Crop center square for AI focus (Assuming 640x480 input)
            captureCtx.drawImage(userVideo, 80, 0, 480, 480, 0, 0, 480, 480);
            
            captureCanvas.toBlob(async (blob) => {
                const arrayBuffer = await blob.arrayBuffer();
                const packet = new Uint8Array(1 + arrayBuffer.byteLength);
                packet[0] = 1; // HEADER: 1 = Video
                packet.set(new Uint8Array(arrayBuffer), 1);
                window.socket.send(packet.buffer);
            }, 'image/jpeg', 0.7);
        }
    }, 500); 
}

/**
 * Initializes the AI Audio and Video pipeline.
 */
async function initAI() {
    audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
    
    try {
        await audioContext.audioWorklet.addModule('/app/content/js/pcm-processor.js');
        
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
        userVideo.srcObject = stream;

        // Audio Pipeline
        const source = audioContext.createMediaStreamSource(stream);
        const pcmWorker = new AudioWorkletNode(audioContext, 'pcm-processor');

        pcmWorker.port.onmessage = (event) => {
            if(window.socket && window.socket.readyState === WebSocket.OPEN && primed) {
                window.socket.send(event.data); 
            }
        };

        source.connect(pcmWorker);
        pcmWorker.connect(audioContext.destination);

        analyser = audioContext.createAnalyser();
        analyser.fftSize = 256;
        dataArray = new Uint8Array(analyser.frequencyBinCount);
        
        // Start the loops
        drawOrb();
        startVisionLoop();
        startMirrorLoop();
        
        statusText.innerText = "Secure Connection Active";
    } catch (err) {
        console.error("Initialization failed:", err);
        statusText.innerText = "Error: Check Camera/Mic Permissions";
    }
}

/**
 * The AI Orb Visualizer Loop (rendered on orbCanvas)
 */
function drawOrb() {
    requestAnimationFrame(drawOrb);
    if (analyser) analyser.getByteFrequencyData(dataArray);

    orbCtx.clearRect(0, 0, orbCanvas.width, orbCanvas.height);
    const centerX = orbCanvas.width / 2;
    const centerY = orbCanvas.height / 2;
    const baseRadius = 120;

    const gradient = orbCtx.createRadialGradient(centerX, centerY, 50, centerX, centerY, 200);
    gradient.addColorStop(0, 'rgba(0, 212, 255, 0.8)');
    gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');

    orbCtx.beginPath();
    for (let i = 0; i < dataArray.length; i++) {
        const amplitude = dataArray[i] / 2.0;
        const angle = (i / dataArray.length) * Math.PI * 2;
        const x = centerX + Math.cos(angle) * (baseRadius + amplitude);
        const y = centerY + Math.sin(angle) * (baseRadius + amplitude);
        if (i === 0) orbCtx.moveTo(x, y); else orbCtx.lineTo(x, y);
    }
    orbCtx.closePath();
    orbCtx.strokeStyle = '#00d4ff';
    orbCtx.lineWidth = 3;
    orbCtx.stroke();
    orbCtx.fillStyle = gradient;
    orbCtx.fill();
}

/**
 * Handles security triggers sent from the Java backend
 */
function handleJsonControl(msg) {
    if (msg.type === "AUTHENTICATION_SUCCESS") {
        statusText.innerText = "ACCESS GRANTED";
        statusText.style.color = "#00ff00";
    } else if(msg.type === "READY") {
        primed = true;
    } else if(msg.type === "BUSY") {
        statusText.innerText = "Responding...";
        statusText.style.color = "#00d4ff";
        primed = false;
    } else if(msg.type == "CAPTURED") {
        takePhoto();
    } else if(msg.type === "DONE") {
        primed = false;
        statusText.innerText = "Session complete.";
        
        const checkInterval = setInterval(() => {
            const silenceDuration = Date.now() - lastAudio;
            if (silenceDuration >= 5000) {
                clearInterval(checkInterval);
                window.location.reload();
            }
        }, 500);
    } else if(msg.type === "CAPTURING") {
        statusText.innerText = "Mapping facial features...";
        primed = false;
    } 
}

window.onclick = () => { 
    if(!audioContext) {
        initAI(); 
        
        window.socket = new WebSocket('wss://' 
            + window.location.hostname 
            + ':' + window.location.port 
            + document.body.getAttribute('data-url')
        );

        window.socket.binaryType = 'arraybuffer'; 
        
        window.socket.onmessage = async (event) => {
            if (event.data instanceof ArrayBuffer) {
                if (audioContext.state === 'suspended') await audioContext.resume();

                const data = new Uint8Array(event.data);
                const type = data[0];
                const payload = data.slice(1);

                if (type === 0) { // AUDIO DATA
                    lastAudio = Date.now();
					lastAudio = Date.now();
					
					const int16Data = new Int16Array(payload.buffer, payload.byteOffset, payload.byteLength / 2);
					const audioBuffer = audioContext.createBuffer(1, int16Data.length, 24000);
					const float32Data = audioBuffer.getChannelData(0);
					for (let i = 0; i < int16Data.length; i++) float32Data[i] = int16Data[i] / 32768.0;

					const source = audioContext.createBufferSource();
					source.buffer = audioBuffer;
					source.connect(analyser);
					analyser.connect(audioContext.destination);

					const currentTime = audioContext.currentTime;
					if (nextStartTime < currentTime) nextStartTime = currentTime + 0.1;

					source.start(nextStartTime);
					nextStartTime += audioBuffer.duration;
                } 
                else if (type === 1) { // TURN COMPLETE
                    const delayUntilDone = (nextStartTime - audioContext.currentTime) * 1000;
                    setTimeout(() => {
                        primed = true; 
                        statusText.innerText = "Listening";
                        statusText.style.color = "#00ff00";
                    }, Math.max(0, delayUntilDone));
                }
            } else {
                handleJsonControl(JSON.parse(event.data));
            }
        };
    }
};