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

canvas.width = 600;
canvas.height = 600;

let audioContext;
let analyser;
let dataArray;
let primed = false;
let lastAudio = Date.now();

// Do this once when the app loads
const shutterSound = new Audio('/app/content/js/shutter.mp3');
shutterSound.load(); 

// Call this whenever the photo is taken
function takePhoto() {
	primed = false;
    shutterSound.currentTime = 0; // Reset to start if clicked rapidly
    shutterSound.play();
	setTimeout(function() {
		primed = true;
	}, 1000);
}



function startMirrorLoop() {
    function render() {
        // 1. Reset and Clear
        ctx.clearRect(0, 0, canvasOverlay.width, canvasOverlay.height);

        // 2. APPLY HORIZONTAL MIRROR
        // We save the state, flip the world, draw the video, then restore
        ctx.save();
        ctx.translate(canvasOverlay.width, 0);
        ctx.scale(-1, 1); 
        ctx.drawImage(userVideo, 0, 0, canvasOverlay.width, canvasOverlay.height);
        ctx.restore();
		const centerX = canvasOverlay.width / 2;

	    const centerY = canvasOverlay.height / 2;
	    
	    // Radii for the vertical oval
	    const radiusX = 120; 
	    const radiusY = 160; 

	    // Optional: Shift slightly up if the 'dead center' feels too low for eyes
	    const verticalOffset = -20; 
	    const activeCenterY = centerY + verticalOffset;

	    // 1. DIMMED OVERLAY
	    ctx.fillStyle = "rgba(0, 0, 0, 0.6)"; // Darker for better focus
	    ctx.beginPath();
	    ctx.rect(0, 0, canvasOverlay.width, canvasOverlay.height);
	    // Subtract the oval
	    ctx.ellipse(centerX, activeCenterY, radiusX, radiusY, 0, 0, Math.PI * 2, true);
	    ctx.fill();

	    // 2. THE GUIDE BORDER
	    ctx.strokeStyle = "#00FF00";
	    ctx.lineWidth = 3;
	    ctx.setLineDash([10, 10]); // Clean dashed look
	    ctx.beginPath();
	    ctx.ellipse(centerX, activeCenterY, radiusX, radiusY, 0, 0, Math.PI * 2);
	    ctx.stroke();
	    
	    // 3. OPTIONAL: TARGET CROSSHAIR
	    // Small dots or lines to help the user find the center
	    ctx.setLineDash([]); // Reset to solid
	    ctx.strokeStyle = "rgba(0, 255, 0, 0.3)";
	    ctx.beginPath();
	    ctx.moveTo(centerX - 10, activeCenterY);
	    ctx.lineTo(centerX + 10, activeCenterY);
	    ctx.moveTo(centerX, activeCenterY - 10);
	    ctx.lineTo(centerX, activeCenterY + 10);
	    ctx.stroke();

        requestAnimationFrame(render);
    }
    render();
}
// 1. Initialize Audio, Video, and Visuals
async function initAI() {
    // Gemini Input prefers 16kHz PCM16
    audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
    
    try {
        // Load the external processor module
        await audioContext.audioWorklet.addModule('/app/content/js/pcm-processor.js');
        
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
        userVideo.srcObject = stream;

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

        // Handle PCM16 bytes coming from the worker
        pcmWorker.port.onmessage = (event) => {
			if(window.socket) {
	            if (window.socket.readyState === WebSocket.OPEN && primed) {
	                window.socket.send(event.data); 
	            }
			}
        };

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

        // Setup Visualizer Analyser
        analyser = audioContext.createAnalyser();
        analyser.fftSize = 256;
        dataArray = new Uint8Array(analyser.frequencyBinCount);
        
        // Start the drawing loop
        draw();
        
        // Start the Vision (Video Frame) Loop
        startVisionLoop();
		startMirrorLoop();
        statusText.innerText = "Secure Connection Active";
    } catch (err) {
        console.error("Initialization failed:", err);
        statusText.innerText = "Error: Check Camera/Mic Permissions";
    }
}

// 2. The Vision Loop (Sends JPEGs to Java)
function startVisionLoop() {
    const captureCanvas = document.createElement('canvas');
    const captureCtx = captureCanvas.getContext('2d');

    setInterval(() => {
		if(window.socket) {
	        if (window.socket.readyState === WebSocket.OPEN && primed) {
	            captureCanvas.width = 480;
	            captureCanvas.height = 480;
	            // Crop center square for AI focus
	            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); // Now deterministic!
				}, 'image/jpeg', 0.7);
	        }
		}
    }, 500); // 2 frames per second recommended
}

// 3. The AI Orb Drawing Loop
function draw() {
    requestAnimationFrame(draw);
    if (analyser) analyser.getByteFrequencyData(dataArray);

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

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

    ctx.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) ctx.moveTo(x, y); else ctx.lineTo(x, y);
    }
    ctx.closePath();
    ctx.strokeStyle = '#00d4ff';
    ctx.lineWidth = 3;
    ctx.stroke();
    ctx.fillStyle = gradient;
    ctx.fill();
}

// 4. WebSocket Communication

let nextStartTime = 0; // Global tracking of the AI's "timeline"
const BUFFER_LOOKAHEAD = 0.1; // 100ms safety margin to prevent gaps

/**
 * 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 currentTime = Date.now();
	        const silenceDuration = currentTime - lastAudio;

	        // Check if 5 seconds have passed since the last audio packet
	        if (silenceDuration >= 5000) {
	            console.log("Redirecting to Vault. Silence confirmed.");
	            clearInterval(checkInterval); // Kill the timer
	            window.location.reload();
	        } else {
	            // Calculate remaining wait time for the console (optional)
	            const remaining = Math.ceil((5000 - silenceDuration) / 1000);
	            console.log(`AI is still active or recently spoke. Waiting... ${remaining}s`);
	        }
	    }, 500); // Check every 500ms for high precision
		
		// Move on
	} 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')
		);

		// visualizer.js update

		// 1. Ensure the socket handles binary as ArrayBuffer for direct PCM access
		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();
					/*
			        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 (AI finished speaking)
			        const delayUntilDone = (nextStartTime - audioContext.currentTime) * 1000;
			        
			        // Wait until the speaker actually goes silent before opening the mic
			        setTimeout(() => {
			            primed = true; 
			            statusText.innerText = "Listening";
			            statusText.style.color = "#00ff00";
			        }, Math.max(0, delayUntilDone));
			    }
			
		    } else {
		        handleJsonControl(JSON.parse(event.data));
		    }
		};
	}
};