#pragma once
/// TRACES: AR-001 | SR-002
#include "config.hpp"
#include "inference/face_detector.hpp"

#include <algorithm>
#include <memory>
#include <string>
#include <vector>

// ── FaceDetectorFunc ──────────────────────────────────────────────────────────
// KPN node: runs SCRFD-500MF to detect ALL faces in a frame.
//
// The inference backend (ONNX Runtime or raw TensorRT) is selected at compile
// time; this node talks only to IFaceDetector via make_face_detector(cfg).

struct FaceDetectorFunc {
    static constexpr std::string_view label() { return "face_detector"; }

    explicit FaceDetectorFunc(const Config& cfg)
        : detector_(make_face_detector(cfg))
        , max_faces_(cfg.max_faces)
        , min_face_px_(cfg.min_face_px)
    {}

    /// TRACES: AR-002 | SR-002
    // Drop faces below the minimum size — too small for reliable ArcFace
    // alignment, and below the resolution where identification still holds
    // (VR-013 measured the knee end to end).
    //
    // The minimum is expressed in ORIGINAL video resolution, which is what makes
    // it a property of the footage rather than of a throughput knob. When
    // dense_scale downscaled the frame the detector's boxes are in downscaled
    // space, and `bbox_upscale` is what maps them back; dividing the threshold by
    // it rather than multiplying every box keeps the comparison on the detector's
    // own numbers and the physical cutoff constant across scales.
    //
    // Strictly less-than: a face exactly at the minimum is admissible, which is
    // what a "minimum of 40x40" means.
    static void drop_undersized(std::vector<DetectedFace>& faces,
                                float min_face_px,
                                float bbox_upscale) {
        const float min_px = (bbox_upscale > 0.f) ? min_face_px / bbox_upscale
                                                  : min_face_px;
        faces.erase(
            std::remove_if(faces.begin(), faces.end(), [&](const DetectedFace& d) {
                return d.bbox.width < min_px || d.bbox.height < min_px;
            }),
            faces.end());
    }

    SceneFrame operator()(Frame f) {
        if (f.eof) return {std::move(f), {}};

        auto faces = detector_->detect(f.image);

        drop_undersized(faces, min_face_px_, f.bbox_upscale);

        // Sort largest-first so max_faces_ keeps the most informative detections
        std::sort(faces.begin(), faces.end(),
                  [](const DetectedFace& a, const DetectedFace& b) {
                      return a.bbox.area() > b.bbox.area();
                  });
        // TRACES: AR-003 | SR-002
        // Largest-first ordering is kept regardless: it is load-bearing for
        // deterministic association, since the Hungarian solver tie-breaks on
        // index order (see the replay determinism test).
        if (max_faces_ > 0 && static_cast<int>(faces.size()) > max_faces_)
            faces.resize(max_faces_);

        return {std::move(f), std::move(faces)};
    }

private:
    std::unique_ptr<IFaceDetector> detector_;
    int   max_faces_{10};
    float min_face_px_{40.f};
};
