
function mouthMAR(mouth) {
    return (
        dist(mouth[13], mouth[19]) + // atas–bawah tengah
        dist(mouth[14], mouth[18]) // atas–bawah samping
    ) / (2 * dist(mouth[12], mouth[16])); // lebar mulut
}

const MODEL_URL = "../template/vendor/webcame/models";
const VIDEO_WIDTH = 320;
const VIDEO_HEIGHT = 240;

const CENTER_X = VIDEO_WIDTH / 2;
const MAX_OFFSET = 35;
const MIN_FACE_SIZE = 90;

const QUALITY_MIN = 60;
const EAR_THRESHOLD = 0.20;
const BLINK_TARGET = 2;

const MOTION_THRESHOLD = 1.8;
const STATIC_LIMIT = 6;

const MAR_THRESHOLD = 0.55;
const MOUTH_TARGET = 2;

/*********************************
 * ELEMENT
 *********************************/
const video = document.getElementById("video");
const statusText = document.getElementById("status");
const guide = document.querySelector(".overlay-recognition");

/*********************************
 * STATE
 *********************************/
let step = 0; // 0=center,1=right,2=left,3=blink,4=done
let lastX = null;

let mouthCount = 0;
let mouthOpen = false;
let mouthCooldown = false;

let lastLandmarks = null;
let staticFrameCount = 0;
let motionScore = 0;

let alreadySubmitted = false;

/*********************************
 * UTIL
 *********************************/
function dist(a, b) {
    return Math.hypot(a.x - b.x, a.y - b.y);
}

function eyeEAR(eye) {
    return (
        dist(eye[1], eye[5]) +
        dist(eye[2], eye[4])
    ) / (2 * dist(eye[0], eye[3]));
}

/*********************************
 * QUALITY SCORE
 *********************************/
function faceQuality(box) {
    let score = 100;
    if (box.width < 90) score -= 40;
    if (box.width > 180) score -= 20;
    return score;
}

/*********************************
 * ANTI SPOOF MOTION
 *********************************/
function antiSpoofMotion(landmarks) {
    const curr = landmarks.positions;

    if (!lastLandmarks) {
        lastLandmarks = curr;
        return true;
    }

    let motion = 0;
    for (let i = 0; i < curr.length; i++) {
        motion += Math.hypot(
            curr[i].x - lastLandmarks[i].x,
            curr[i].y - lastLandmarks[i].y
        );
    }

    motion /= curr.length;

    if (motion < MOTION_THRESHOLD) {
        staticFrameCount++;
    } else {
        staticFrameCount = 0;
        motionScore += motion;
    }

    lastLandmarks = curr;

    return staticFrameCount < STATIC_LIMIT;
}



function absen_recognition(latitude, radius, radius_aktif, tipe, hak_akses) {
    navigator.mediaDevices.getUserMedia({
        video: {
            facingMode: "user",
            width: VIDEO_WIDTH,
            height: VIDEO_HEIGHT
        }
    }).then(stream => video.srcObject = stream);

    /*********************************
     * LOAD MODEL
     *********************************/
    Promise.all([
        faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
        faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL),
        faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL)
    ]).then(() => {
        //statusText.innerText = "Posisikan wajah di tengah";
    });

    /*********************************
     * LOOP UTAMA
     *********************************/
    setInterval(async () => {
        if (alreadySubmitted) return;
        let res;
        try {
            res = await faceapi
                .detectSingleFace(
                    video,
                    new faceapi.TinyFaceDetectorOptions({
                        inputSize: 160,
                        scoreThreshold: 0.5
                    })
                )
                .withFaceLandmarks()
                .withFaceDescriptor();
        } catch {
            return;
        }

        if (!res || !res.detection || !res.landmarks) return;

        const box = res.detection.box;
        const centerX = box.x + box.width / 2;
        const offset = Math.abs(centerX - CENTER_X);

        // QUALITY
        const quality = faceQuality(box);
        if (quality < QUALITY_MIN) {
            statusText.innerText = "Kualitas wajah buruk";
            guide.style.borderColor = "red";
            $(".result-label").css("background-color", "red");
            return;
        }

        // SIZE & CENTER
        if (box.width < MIN_FACE_SIZE) {
            statusText.innerText = "Wajah terlalu jauh";
            guide.style.borderColor = "red";
            $(".result-label").css("background-color", "red");
            return;
        }

        if (offset > MAX_OFFSET) {
            statusText.innerText = "Posisikan wajah di tengah";
            guide.style.borderColor = "white";
            $(".result-label").css("background-color", "white")
            return;
        }

        // ANTI SPOOF
        if (!antiSpoofMotion(res.landmarks)) {
            statusText.innerText = "Anti spoof aktif";
            return;
        }

        // STEP 0
        if (step === 0) {
            statusText.innerText = "Sekarang nengok kiri";
            guide.style.borderColor = "white";
            $(".result-label").css("background-color", "white");
            step = 1;
            lastX = centerX;
            return;
        }

        // STEP 1
        if (step === 1 && centerX - lastX > 15) {
            statusText.innerText = "Silakan nengok kanan";
            guide.style.borderColor = "white";
            $(".result-label").css("background-color", "white");
            step = 2;
            lastX = centerX;
            return;
        }

        // STEP 2
        if (step === 2 && centerX - lastX < -15) {
            statusText.innerText = "Sekarang buka tutup mulut 2x";
            step = 3;
            return;
        }

        // STEP 3 - BLINK
        if (step === 3) {
            const mar = mouthMAR(res.landmarks.getMouth());

            // Mulut terbuka
            if (mar > MAR_THRESHOLD && !mouthOpen) {
                mouthOpen = true;
            }

            // Mulut tertutup kembali → 1x hitung
            if (mar < MAR_THRESHOLD && mouthOpen && !mouthCooldown) {
                mouthCount++;
                mouthCooldown = true;
                mouthOpen = false;
                setTimeout(() => mouthCooldown = false, 400);
            }

            if (mouthCount >= MOUTH_TARGET) {
                alreadySubmitted = true;
                guide.style.borderColor = "lime";
                $(".result-label").css("background-color", "lime");
                statusText.innerText = "✔ Verifikasi berhasil";
                if (res.descriptor && res.descriptor.length === 128) {
                    absen(res);
                }
            }
        }
        lastX = centerX;
    }, 300);

    function absen(res) {
        // LOADING NOTIFIKASI
        statusText.innerText = "✔ Proses Absen..";
        const canvas = document.createElement("canvas");
        const scale = 400 / video.videoWidth;
        canvas.width = 400;
        canvas.height = video.videoHeight * scale;

        const ctx = canvas.getContext("2d");
        ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
       

        if (tipe == 'masuk') {
            var action = '../'+hak_akses+'/module/absen-in/sw-proses.php?action=absen-recognition';
        } else {
            var action = '../'+hak_akses+'/module/absen-out/sw-proses.php?action=absen-recognition';
        }

        fetch("" + action + "", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify({
                descriptor: Array.from(res.descriptor),
                photo: canvas.toDataURL("image/jpeg", 0.7),
                latitude: latitude,
                radius: radius,
                radius_aktif: radius_aktif,
            })
        })
            .then(async response => {
                const text = await response.text();
                try {
                    return JSON.parse(text);
                } catch (e) {
                    throw new Error(text);
                }
            })
            .then(r => {
                if (r.status === true) {
                    swal({
                        title: "Berhasil",
                        text: r.msg,
                        icon: "success",
                        button: "OK"
                    });
                    setTimeout(() => location.href = './', 3000);
                } else {
                    swal({
                        title: "Gagal",
                        text: r.msg + (r.error ? "\n" + JSON.stringify(r.error) : ""),
                        icon: "error",
                        button: "OK"
                    });
                    setTimeout(() => location.href = './', 3000);
                }
            })
            .catch(err => {
                swal({
                    title: "PHP / Server Error",
                    text: err.message.substring(0, 300),
                    icon: "error",
                    button: "OK"
                });
                console.error("SERVER ERROR:", err.message);

            });
    }

    /** END ABSEN */
}