/* global faceapi, CAPSQ_DATA */
/* Capsquery Facial Authentication shortcode JS — mirrors capsq-login.js but targets capsq-sc-* element IDs */
( function () {
    'use strict';

    let stream        = null;
    let modelsLoaded  = false;
    let modelsPromise = null;
    let matchToken    = null;
    let scanning      = false;
    let missCount     = 0;

    const MIN_FACE_WIDTH_RATIO = 0.18;

    /* ---- DOM refs ---- */
    let openBtn, closeBtn, modal, video, statusEl;
    let stepCamera, stepConfirm, stepPin;
    let confirmUserEl, confirmYesBtn, confirmNoBtn;
    let pinInput, pinError, submitPinBtn, modelLoader;

    /* ---- Model loading ---- */
    function loadModels() {
        if ( modelsLoaded ) return Promise.resolve( true );
        if ( modelsPromise ) return modelsPromise;

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

        return modelsPromise;
    }

    function preloadModels() {
        if ( ! window.faceapi || ! window.CAPSQ_DATA ) {
            setTimeout( preloadModels, 100 );
            return;
        }

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

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

    /* ---- Status helper ---- */
    function setStatus( text, type ) {
        if ( ! statusEl ) return;
        statusEl.textContent = text;
        statusEl.className   = 'capsq-status-' + ( type || 'idle' );
    }

    function updateGuide( ok ) {
        const el = document.getElementById( 'capsq-sc-guide' );
        if ( el ) el.setAttribute( 'stroke', ok ? '#00ff88' : '#00ffcc' );
    }

    /* ---- Camera ---- */
    function stopCamera() {
        if ( stream ) {
            stream.getTracks().forEach( function ( t ) { t.stop(); } );
            stream = null;
        }
    }

    function waitForVideoReady( videoEl ) {
        return new Promise( function ( resolve ) {
            if ( videoEl.readyState >= 2 && videoEl.videoWidth > 0 ) {
                videoEl.play().then( resolve ).catch( resolve );
                return;
            }
            var onReady = function () {
                videoEl.removeEventListener( 'loadeddata', onReady );
                videoEl.play().then( resolve ).catch( resolve );
            };
            videoEl.addEventListener( 'loadeddata', onReady );
            setTimeout( function () {
                videoEl.removeEventListener( 'loadeddata', onReady );
                resolve();
            }, 4000 );
        } );
    }

    /* ---- Modal open/close ---- */
    function showModal() {
        modal.style.display         = 'flex';
        stepCamera.style.display    = 'block';
        stepConfirm.style.display   = 'none';
        stepPin.style.display       = 'none';
        pinInput.value              = '';
        pinError.textContent        = '';
        missCount                   = 0;
        startFlow();
    }

    function hideModal() {
        modal.style.display = 'none';
        scanning            = false;
        stopCamera();
    }

    /* ---- Face scan flow ---- */
    async function startFlow() {
        setStatus( 'Loading face recognition…', 'busy' );
        try {
            await loadModels();
            stream = await navigator.mediaDevices.getUserMedia( {
                video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: 'user' },
            } );
            video.srcObject = stream;
            await waitForVideoReady( video );
            setStatus( 'Look at the camera', 'idle' );
            scanning = true;
            scanLoop();
        } catch ( err ) {
            setStatus( 'Camera error: ' + err.message, 'error' );
        }
    }

    function withTimeout( promise, ms ) {
        return new Promise( function ( resolve, reject ) {
            var timer = setTimeout( function () { reject( new Error( 'timeout' ) ); }, ms );
            promise.then(
                function ( v ) { clearTimeout( timer ); resolve( v ); },
                function ( e ) { clearTimeout( timer ); reject( e ); }
            );
        } );
    }

    async function collectVectors( videoEl, count ) {
        var vectors = [];
        for ( var i = 0; i < count; i++ ) {
            var result = await faceapi
                .detectSingleFace( videoEl, new faceapi.SsdMobilenetv1Options( { minConfidence: 0.3 } ) )
                .withFaceLandmarks()
                .withFaceDescriptor();
            if ( result ) vectors.push( Array.from( result.descriptor ) );
            await new Promise( function ( r ) { setTimeout( r, 300 ); } );
        }
        return vectors;
    }

    function averageVectors( vectors ) {
        var avg = new Array( vectors[ 0 ].length ).fill( 0 );
        vectors.forEach( function ( v ) {
            v.forEach( function ( val, i ) { avg[ i ] += val; } );
        } );
        return avg.map( function ( val ) { return val / vectors.length; } );
    }
    
   
    async function scanLoop() {
        if ( ! scanning ) return;

        var detection = null;
        try {
            detection = await withTimeout(
                faceapi
                    .detectSingleFace( video, new faceapi.SsdMobilenetv1Options( { minConfidence: 0.3 } ) )
                    .withFaceLandmarks()
                    .withFaceDescriptor(),
                6000
            );
        } catch ( e ) {
            setStatus( 'Detection retry…', 'warn' );
            return scheduleNext();
        }

        if ( ! detection ) {
            updateGuide( false );
            setStatus( 'No face detected — center your face', 'warn' );
            missCount = 0;
            return scheduleNext();
        }

        var ratio = detection.detection.box.width / video.videoWidth;
        if ( ratio < MIN_FACE_WIDTH_RATIO ) {
            setStatus( 'Move a little closer', 'warn' );
            return scheduleNext();
        }

        updateGuide( true );
        setStatus( 'Face detected — verifying…', 'busy' );

        var vectors = await collectVectors( video, 5 );
        if ( vectors.length < 3 ) {
            setStatus( 'Keep still, collecting samples…', 'busy' );
            return scheduleNext();
        }

        var descriptor = averageVectors( vectors );

        try {
            var res  = await fetch( CAPSQ_DATA.restUrl + '/match', {
                method:  'POST',
                headers: { 'Content-Type': 'application/json' },
                body:    JSON.stringify( { descriptor: descriptor } ),
            } );
            var json = await res.json();

            if ( res.ok && json.token ) {
                var displayName = json.user.toUpperCase();

                // Update PIN step heading — plain JS, no jQuery dependency
                var pinHeading = document.querySelector( '#capsq-sc-step-pin h3' );
                if ( pinHeading ) {
                    pinHeading.textContent = displayName.toUpperCase() + ' — Enter your PIN';
                }

                matchToken = json.token;
                scanning   = false;
                stopCamera();
                stepCamera.style.display  = 'none';
                confirmUserEl.textContent = 'Login as: ' + displayName + '?';
                stepConfirm.style.display = 'block';
                return;
            }

            missCount++;
            setStatus( missCount >= 3 ? 'Face not recognized — try again' : 'Processing…', missCount >= 3 ? 'error' : 'busy' );
        } catch ( e ) {
            setStatus( 'Network error, retrying…', 'error' );
        }

        scheduleNext();
    }

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

    /* ---- PIN submission ---- */
    async function submitPin() {
        var pin = pinInput.value.replace( /[^0-9]/g, '' );
        if ( ! pin ) {
            pinError.textContent = 'Enter your PIN.';
            return;
        }
        if ( ! matchToken ) {
            pinError.textContent = 'Face match expired, please scan again.';
            setTimeout( showModal, 1200 );
            return;
        }

        submitPinBtn.disabled = true;
        pinError.textContent  = '';

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

            if ( res.ok && json.redirect ) {
                // Use per-shortcode redirect if set, otherwise use server redirect.
                var customRedirect = modal.getAttribute( 'data-redirect' );
                window.location.href = customRedirect || json.redirect;
                return;
            }

            pinError.textContent = ( json && json.message ) ? json.message : 'Incorrect PIN.';
            pinInput.value       = '';
            pinInput.focus();

            if ( json && json.code === 'capsq_locked' ) {
                setTimeout( showModal, 1200 );
            }
        } catch ( e ) {
            pinError.textContent = 'Network error, please try again.';
        } finally {
            submitPinBtn.disabled = false;
        }
    }

    /* ---- Boot ---- */
    document.addEventListener( 'DOMContentLoaded', function () {
        openBtn        = document.getElementById( 'capsq-sc-open-login' );
        closeBtn       = document.getElementById( 'capsq-sc-close-modal' );
        modal          = document.getElementById( 'capsq-sc-login-modal' );
        video          = document.getElementById( 'capsq-sc-video' );
        statusEl       = document.getElementById( 'capsq-sc-status' );
        stepCamera     = document.getElementById( 'capsq-sc-step-camera' );
        stepConfirm    = document.getElementById( 'capsq-sc-step-confirm' );
        stepPin        = document.getElementById( 'capsq-sc-step-pin' );
        confirmUserEl  = document.getElementById( 'capsq-sc-confirm-user' );
        confirmYesBtn  = document.getElementById( 'capsq-sc-confirm-yes' );
        confirmNoBtn   = document.getElementById( 'capsq-sc-confirm-no' );
        pinInput       = document.getElementById( 'capsq-sc-pin' );
        pinError       = document.getElementById( 'capsq-sc-pin-error' );
        submitPinBtn   = document.getElementById( 'capsq-sc-submit-pin' );
        modelLoader    = document.getElementById( 'capsq-sc-model-loader' );

        // Only run if the shortcode is actually on this page.
        if ( ! openBtn ) return;

        preloadModels();

        openBtn.addEventListener( 'click', showModal );
        closeBtn.addEventListener( 'click', hideModal );

        confirmYesBtn.addEventListener( 'click', function () {
            stepConfirm.style.display = 'none';
            stepPin.style.display     = 'block';
            pinInput.focus();
        } );

        confirmNoBtn.addEventListener( 'click', function () {
            matchToken                = null;
            stepConfirm.style.display = 'none';
            stepCamera.style.display  = 'block';
            missCount                 = 0;
            startFlow();
        } );

        submitPinBtn.addEventListener( 'click', submitPin );
        pinInput.addEventListener( 'keydown', function ( e ) {
            if ( e.key === 'Enter' ) submitPin();
        } );
    } );

} )();
