#pragma once
#ifdef SAE_DEBUG

#include "types.hpp"
#include "config.hpp"

#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <filesystem>
#include <iostream>
#include <string>

namespace fs = std::filesystem;

// ── DebugRendererFunc ─────────────────────────────────────────────────────────
// KPN sink node (SAE_DEBUG only): saves one directory of debug images per frame.
//
// Output layout:
//   debug_frames/
//     t0001.000/
//       annotated.jpg        original frame + coloured bboxes + name overlays
//       brad_pitt_0.82.jpg   112×112 aligned crop | 1.5× context crop (side-by-side)
//       unknown_0_0.77.jpg   same for unidentified faces
//
// The node taps MatchedSceneFrame (before scene tracking) so every raw
// detection — including unknowns — is captured here.

struct DebugRendererFunc {
    static constexpr std::string_view label() { return "debug_renderer"; }

    explicit DebugRendererFunc(const Config& cfg)
        : cfg_(cfg)
    {
        fs::create_directories(cfg_.debug_dir);
        std::cerr << "[debug_renderer] output dir: " << cfg_.debug_dir << "\n";
    }

    void operator()(MatchedSceneFrame mf) {
        if (mf.source.eof || mf.source.image.empty()) return;

        // Directory for this timestamp, e.g. "debug_frames/t0042.000/"
        char buf[32];
        std::snprintf(buf, sizeof(buf), "t%08.3f", mf.source.timestamp_sec);
        fs::path dir = fs::path(cfg_.debug_dir) / buf;
        fs::create_directories(dir);

        // ── Annotated frame ───────────────────────────────────────────────────
        cv::Mat annotated = mf.source.image.clone();
        int unknown_idx = 0;
        for (const auto& ia : mf.actors) {
            bool known = (ia.actor_idx >= 0);
            cv::Scalar colour = known
                ? cv::Scalar(0, 200, 60)    // green for identified
                : cv::Scalar(0, 100, 220);  // orange for unknown

            cv::Rect2f b = ia.bbox;
            cv::rectangle(annotated, b, colour, 2);

            std::string lbl = known
                ? (ia.name + " " + fmt_pct(ia.similarity))
                : ("unknown " + fmt_pct(ia.similarity));

            // Background strip for readability
            int baseline = 0;
            cv::Size ts = cv::getTextSize(lbl, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseline);
            cv::Rect strip(static_cast<int>(b.x), static_cast<int>(b.y) - ts.height - 4,
                           ts.width + 4, ts.height + 6);
            strip &= cv::Rect(0, 0, annotated.cols, annotated.rows);
            if (strip.area() > 0)
                cv::rectangle(annotated, strip, colour, cv::FILLED);

            cv::putText(annotated, lbl,
                        cv::Point(static_cast<int>(b.x) + 2, static_cast<int>(b.y) - 2),
                        cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 255, 255), 1,
                        cv::LINE_AA);

            // ── Per-face crop image ───────────────────────────────────────────
            // Left panel: 112×112 aligned crop.  Right panel: expanded context.
            cv::Mat panel = make_face_panel(mf.source.image, ia);

            std::string stem = known
                ? (sanitise(ia.name) + "_" + fmt_sim(ia.similarity))
                : ("unknown_" + std::to_string(unknown_idx++) + "_" + fmt_sim(ia.similarity));
            cv::imwrite((dir / (stem + ".jpg")).string(), panel,
                        {cv::IMWRITE_JPEG_QUALITY, 90});
        }

        cv::imwrite((dir / "annotated.jpg").string(), annotated,
                    {cv::IMWRITE_JPEG_QUALITY, 90});
    }

private:
    const Config& cfg_;

    // Build a side-by-side panel: [112×112 aligned crop | context crop resized to 112×112]
    cv::Mat make_face_panel(const cv::Mat& frame, const IdentifiedActor& ia) const {
        // Left: aligned 112×112
        cv::Mat left = ia.crop.empty()
            ? cv::Mat(112, 112, CV_8UC3, cv::Scalar(60, 60, 60))
            : ia.crop.clone();

        // Right: expanded bbox from original frame, resized to 112×112
        cv::Rect2f expanded = expand_bbox(ia.bbox, cfg_.crop_context,
                                          frame.cols, frame.rows);
        cv::Mat right_raw = frame(expanded).clone();
        cv::Mat right;
        cv::resize(right_raw, right, {112, 112}, 0, 0, cv::INTER_LINEAR);

        // Separator line
        cv::Mat sep(112, 4, CV_8UC3, cv::Scalar(200, 200, 200));

        cv::Mat panel;
        cv::hconcat(std::vector<cv::Mat>{left, sep, right}, panel);
        return panel;
    }

    static cv::Rect2f expand_bbox(cv::Rect2f b, float factor, int W, int H) {
        float cx  = b.x + b.width  * 0.5f;
        float cy  = b.y + b.height * 0.5f;
        float nw  = b.width  * factor;
        float nh  = b.height * factor;
        float x   = std::max(0.f, cx - nw * 0.5f);
        float y   = std::max(0.f, cy - nh * 0.5f);
        nw = std::min(nw, (float)W - x);
        nh = std::min(nh, (float)H - y);
        return {x, y, nw, nh};
    }

    static std::string fmt_pct(float v) {
        char buf[8];
        std::snprintf(buf, sizeof(buf), "%.0f%%", v * 100.f);
        return buf;
    }

    static std::string fmt_sim(float v) {
        char buf[8];
        std::snprintf(buf, sizeof(buf), "%.2f", v);
        return buf;
    }

    static std::string sanitise(const std::string& s) {
        std::string out;
        out.reserve(s.size());
        for (char c : s)
            out += (std::isalnum(c) ? std::tolower(c) : '_');
        return out;
    }
};

#endif // SAE_DEBUG
