/* global faceapi, CAPSQ_DATA */

(function () {
	'use strict';

	let stream = null;
	let modelsLoaded = false;
	let modelsPromise = null;

	let matchToken = null;
	let matchedUser = '';

	let scanning = false;
	let missCount = 0;
	let consecutiveMatches = 0;

	const REQUIRED_CONSECUTIVE_MATCHES = 1;
	const MIN_FACE_WIDTH_RATIO = 0.18;

	let cameraReadyTime = 0;
	let openBtn;
	let closeBtn;
	let modal;
	let video;
	let statusEl;
	let stepCamera;
	let stepConfirm;
	let stepPin;
	let confirmUser;
	let confirmBtn;
	let cancelBtn;
	let pinInput;
	let pinError;
	let submitPinBtn;
	let modelLoader;
	let faceReady = false;

	/**
	 * Load face models once
	 */
	async function loadModels() {
		if (modelsLoaded) {
			return true;
		}

		if (modelsPromise) {
			return modelsPromise;
		}

		const url = CAPSQ_DATA.modelsUrl;

		modelsPromise = Promise.all([
			faceapi.nets.ssdMobilenetv1.loadFromUri(url),
			faceapi.nets.faceLandmark68Net.loadFromUri(url),
			faceapi.nets.faceRecognitionNet.loadFromUri(url)
		]).then(function(){
			modelsLoaded = true;
			return true;
		});
		return modelsPromise;
	}

	/**
	 * Preload during page load
	 */
	function preloadModels(){
		if(!window.faceapi || !window.CAPSQ_DATA){
			setTimeout(preloadModels,100);
			return;
		}

		if(modelLoader){
			modelLoader.style.display='flex';
			openBtn.style.display='none';
		}

		loadModels().then(function(){
			faceReady=true;
			if(modelLoader){
				modelLoader.style.display='none';
				openBtn.style.display='flex';
			}
		}).catch(function(error){
			console.error('Face model preload failed',error);
		});
	}

	/**
	 * Collect multiple vectors
	 */
	async function collectVectors(videoElement,count){
		const vectors=[];

		for(let i=0;i<count;i++){
			const result =
				await faceapi
				.detectSingleFace(
					videoElement,
					new faceapi.SsdMobilenetv1Options({
						minConfidence:0.3
					})
				).withFaceLandmarks().withFaceDescriptor();

			if(result){
				vectors.push(
					Array.from(
						result.descriptor
					)
				);
			}

			await new Promise(
				resolve=>setTimeout(resolve,300)
			);
		}
		return vectors;
	}

	function averageVectors(vectors){

		const avg =
			new Array(
				vectors[0].length
			).fill(0);

		vectors.forEach(function(vector){
			vector.forEach(function(value,index){
				avg[index]+=value;
			});
		});

		return avg.map(
			value=>value/vectors.length
		);
	}

	function setStatus(text,type){

		if(!statusEl){
			return;
		}
		statusEl.textContent=text;
		statusEl.className =
			'capsq-status-' +
			(type || 'idle');
	}

	function updateGuide(ok){
		const el =
			document.querySelector(
				'#capsq-login-guide ellipse'
			);

		if(el){
			el.setAttribute(
				'stroke',
				ok ? '#00ff88' : '#00ccff'
			);
		}
	}

	async function startFlow(){
		try{
			setStatus(
				'Loading face recognition...',
				'busy'
			);

			await loadModels();

			stream =
				await navigator.mediaDevices.getUserMedia({
					video:{
						width:{ ideal: 1280 },
						height:{ ideal: 720 },
						facingMode: 'user'
					}
				});

			video.srcObject=stream;

			await waitForVideoReady(video);

			cameraReadyTime = Date.now();

			scanning=true;

			setStatus(
				'Look at the camera',
				'idle'
			);
			scanLoop();
		}
		catch(error){
			setStatus(
				'Camera error: '+error.message,
				'error'
			);
		}
	}

	function waitForVideoReady(videoEl){
		return new Promise(function(resolve){
			if(
				videoEl.readyState>=2 &&
				videoEl.videoWidth>0
			){

				videoEl.play()
				.then(resolve)
				.catch(resolve);

				return;
			}

			const ready=function(){
				videoEl.removeEventListener(
					'loadeddata',
					ready
				);

				videoEl.play()
				.then(resolve)
				.catch(resolve);
			};

			videoEl.addEventListener(
				'loadeddata',
				ready
			);
			setTimeout(function(){
				videoEl.removeEventListener(
					'loadeddata',
					ready
				);
				resolve();
			},4000);
		});
	}

	async function scanLoop(){
		if(!scanning){
			return;
		}

		let detection=null;

		try{
			detection =
				await withTimeout(

					faceapi
					.detectSingleFace(
						video,
						new faceapi.SsdMobilenetv1Options({
							minConfidence:0.3
						})
					)
					.withFaceLandmarks()
					.withFaceDescriptor(),

					4000

				);
		}
		catch(error){
			setStatus(
				'Detection retry...',
				'warn'
			);
			return scheduleNext();
		}


		if(!detection){
			updateGuide(false);
			if(
				Date.now()-cameraReadyTime < 3000
			){
				setStatus(
					'Camera warming up...',
					'busy'
				);
			}
			else{
				setStatus(
					'No face detected - center your face',
					'warn'
				);
			}
			return scheduleNext();
		}

		const ratio =
			detection.detection.box.width /
			video.videoWidth;



		if(ratio < MIN_FACE_WIDTH_RATIO){
			setStatus(
				'Move closer',
				'warn'
			);
			return scheduleNext();
		}

		updateGuide(true);
		setStatus(
			'Face detected - verifying...',
			'busy'
		);

		const vectors =
			await collectVectors(video,5);

		if(vectors.length < 3){
			return scheduleNext();
		}

		const descriptor =
			averageVectors(vectors);

		try{

			const response =
				await fetch(
					CAPSQ_DATA.restUrl+'/match',
					{

						method:'POST',

						headers:{
							'Content-Type':
							'application/json'
						},

						body:JSON.stringify({
							descriptor
						})

					}
				);

			const json = await response.json();

			if (response.ok) {

				if(json.token){

					consecutiveMatches++;

					matchToken=json.token;

					matchedUser =
						json.user.toUpperCase();

					if(
						consecutiveMatches <
						REQUIRED_CONSECUTIVE_MATCHES
					){

						setStatus(
							'Confirming match...',
							'busy'
						);

						return scheduleNext();

					}

					scanning=false;

					stopCamera();

					stepCamera.style.display='none';


					confirmUser.textContent = 'Login as: '+matchedUser+'?';

					stepConfirm.style.display='block';

					return;
				}
			}

			setStatus(
				json.message ||
				'Face not recognized',
				'warn'
			);

			consecutiveMatches=0;
		}
		catch(error){
			setStatus(
				'Network error',
				'error'
			);
		}
		scheduleNext();
	}

	function scheduleNext(){
		if(scanning){
			setTimeout(
				scanLoop,
				500
			);
		}
	}

	function stopCamera(){
		if(stream){
			stream
			.getTracks()
			.forEach(
				track=>track.stop()
			);

			stream=null;
		}
	}

	function showModal(){
		modal.style.display='flex';

		stepCamera.style.display='block';

		stepConfirm.style.display='none';

		stepPin.style.display='none';


		pinInput.value='';

		pinError.textContent='';

		startFlow();
	}

	function hideModal(){

		modal.style.display='none';

		scanning=false;

		stopCamera();

	}

	function withTimeout(promise,ms){

		return new Promise(function(resolve,reject){

			const timer=setTimeout(
				()=>reject(new Error('timeout')),
				ms
			);


			promise.then(

				result=>{
					clearTimeout(timer);
					resolve(result);
				},

				error=>{
					clearTimeout(timer);
					reject(error);
				}

			);

		});

	}

	async function submitPin(){
		const pin = pinInput.value.replace(/[^0-9]/g, '');

		if(!pin){
			pinError.textContent='Enter PIN';
			return;
		}



		const response =await fetch(
			CAPSQ_DATA.restUrl+'/verify',
			{
				method:'POST',
				headers:{
					'Content-Type':
					'application/json'
				},
				body:JSON.stringify({
					token:matchToken,
					pin
				})
			}
		);

		const json = await response.json();

		if(response.ok && json.redirect){
			window.location.href = json.redirect;
			return;
		}
		pinError.textContent = json.message || 'Incorrect PIN';
	}


	document.addEventListener('DOMContentLoaded',function(){
			openBtn =
				document.getElementById(
					'capsq-open-login'
				);


			closeBtn =
				document.getElementById(
					'capsq-close-modal'
				);


			modal =
				document.getElementById(
					'capsq-login-modal'
				);


			video =
				document.getElementById(
					'capsq-login-video'
				);


			statusEl =
				document.getElementById(
					'capsq-login-status'
				);



			stepCamera =
				document.getElementById(
					'capsq-step-camera'
				);


			stepConfirm =
				document.getElementById(
					'capsq-step-confirm'
				);


			stepPin =
				document.getElementById(
					'capsq-step-pin'
				);


			confirmUser =
				document.getElementById(
					'capsq-confirm-user'
				);


			confirmBtn =
				document.getElementById(
					'capsq-confirm-face'
				);


			cancelBtn =
				document.getElementById(
					'capsq-cancel-face'
				);



			pinInput =
				document.getElementById(
					'capsq-login-pin'
				);


			pinError =
				document.getElementById(
					'capsq-pin-error'
				);


			submitPinBtn =
				document.getElementById(
					'capsq-submit-pin'
				);

			modelLoader =
				document.getElementById(
			    'capsq-model-loader'
			);



			preloadModels();



			if(openBtn)
				openBtn.onclick=showModal;


			if(closeBtn)
				closeBtn.onclick=hideModal;


			if(confirmBtn)
				confirmBtn.onclick=function(){

					stepConfirm.style.display='none';

					stepPin.style.display='block';

					pinInput.focus();

				};


			if(cancelBtn)
				cancelBtn.onclick=hideModal;


			const pinForm = document.getElementById('capsq_pin_varify');

			if (pinForm) {
			    pinForm.addEventListener('submit', async function (e) {
			        e.preventDefault(); // Prevent page reload
			        await submitPin();
			    });
			}


		}
	);
})();