
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 setStatus = (msg, color = "red") => {
    statusText.innerText = msg;
    guide.style.borderColor = color;
    $(".result-label").css("background-color", color);
};

const STATE = {
    CENTER: "CENTER",
    QUALITY: "QUALITY",
    SPOOF: "SPOOF",
    MOUTH: "MOUTH",
    SUCCESS: "SUCCESS"
};

let state = STATE.CENTER;

// Face flow
let lastDescriptor = null;
let faceDetected = false;
let faceLostTimer = null;

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

// Queue
const attendanceQueue = [];
let currentEmployee = null;

function resetAbsensi() {
    state = STATE.CENTER;

    alreadySubmitted = false;
    mouthCount = 0;
    mouthOpen = false;
    mouthCooldown = false;

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


function renderQueue() {
    const list = document.getElementById("queue");
    list.innerHTML = "";

    attendanceQueue.slice(-5).forEach((q, i) => {
        list.innerHTML += `<li>${i + 1}. ${q.nama} (${q.waktu})</li>`;
    });
}

const MODEL_URL_SCREEN = "../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;



function isNewFace(descriptor) {
    if (!lastDescriptor) return true;

    const distance = faceapi.euclideanDistance(lastDescriptor, descriptor);
    return distance > 0.55;
}

function onFaceLost() {
    if (!faceLostTimer) {
        faceLostTimer = setTimeout(resetAbsensi, 1500);
    }
}

function onFaceDetected() {
    clearTimeout(faceLostTimer);
    faceLostTimer = null;

    if (!faceDetected) {
        faceDetected = true;
        setStatus("Wajah terdeteksi, posisikan di tengah");
    }
}

/*********************************
 * 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 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_screen(latitude) {
    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_SCREEN),
        faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL_SCREEN),
        faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL_SCREEN)
    ]).then(() => {
        //statusText.innerText = "Posisikan wajah di tengah";
    });

    /*********************************
     * LOOP UTAMA
     *********************************/
    setInterval(async () => {
        if (state === STATE.SUCCESS) return;

        let res;
        try {
            res = await faceapi
                .detectSingleFace(
                    video,
                    new faceapi.TinyFaceDetectorOptions({
                        inputSize: 160,
                        scoreThreshold: 0.5
                    })
                )
                .withFaceLandmarks()
                .withFaceDescriptor();
        } catch { return; }

        if (!res?.detection || !res?.descriptor) {
            onFaceLost();
            return;
        }

        onFaceDetected();

        // AUTO DETECT WAJAH BARU
        if (state === STATE.CENTER && isNewFace(res.descriptor)) {
            lastDescriptor = res.descriptor;
            currentEmployee = {
                time: new Date().toISOString(),
                status: "processing"
            };
        }

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

        switch (state) {

            // 1️⃣ POSISI WAJAH
            case STATE.CENTER:
                if (offset <= MAX_OFFSET) {
                    setStatus("✔ Wajah di tengah", "lime");
                    state = STATE.QUALITY;
                } else {
                    setStatus("Posisikan wajah di tengah");
                }
                break;

            // 2️⃣ KUALITAS
            case STATE.QUALITY:
                if (faceQuality(box) < QUALITY_MIN)
                    return setStatus("Kualitas wajah buruk", "red");

                if (box.width < MIN_FACE_SIZE)
                    return setStatus("Wajah terlalu jauh", "red");

                setStatus("✔ Kualitas OK", "lime");
                state = STATE.SPOOF;
                break;

            // 3️⃣ ANTI SPOOF
            case STATE.SPOOF:
                if (!antiSpoofMotion(res.landmarks))
                    return setStatus("Anti spoof aktif", "red");

                setStatus("✔ Gerakan alami", "lime");
                state = STATE.MOUTH;
                break;

            // 4️⃣ BUKA TUTUP MULUT
            case STATE.MOUTH:
                setStatus("Buka tutup mulut 2x", "white");

                const mar = mouthMAR(res.landmarks.getMouth());

                if (mar > MAR_THRESHOLD && !mouthOpen)
                    mouthOpen = true;

                if (mar < MAR_THRESHOLD && mouthOpen && !mouthCooldown) {
                    mouthCount++;
                    mouthOpen = false;
                    mouthCooldown = true;
                    setTimeout(() => mouthCooldown = false, 400);
                }

                if (mouthCount >= MOUTH_TARGET) {
                    state = STATE.SUCCESS;
                    setStatus("✔ Verifikasi berhasil", "lime");

                    res.descriptor.length === 128 && absen(res, resetAbsensi);
                }
                break;
        }
    }, 300);


    function absen(res, callback) {
        // 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);


        fetch("./sw-proses.php?action=absen-selfie", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify({
                descriptor: Array.from(res.descriptor),
                photo: canvas.toDataURL("image/jpeg", 0.7),
                latitude: latitude
            })
        })
            .then(response => response.json())
            .then(r => {
                if (r.status === true) {
                    swal({
                        title: "Berhasil",
                        //text: r.msg,
                        content: {
                            element: "div",
                            attributes: {
                                innerHTML: `
                                    <div style="text-align:center;border-radius:10px;overflow:hidden;">
                                        <img src="${r.avatar}" width="100" style="margin-bottom:10px">
                                        <p>${r.msg}</p>
                                    </div>`
                            }
                        },
                        icon: "success",
                        button: "OK",
                        timer: 3000
                    });
                    callback && callback();
                    loaddata();
                    loaddatacouter();
                } else {
                    swal({
                        title: "Gagal",
                        text: r.msg,
                        icon: "error",
                        button: "OK",
                        timer: 3000
                    });
                    resetAbsensi();
                }
            })
            .catch(err => {
                swal({
                    title: "PHP / Server Error",
                    text: err.message,
                    icon: "error",
                    button: "OK",
                    timer: 3000
                });
                console.error(err);
                resetAbsensi();
            });
    }

    /** END ABSEN */
}