#pragma once
#include "types.hpp"
#include "config.hpp"

#include <kpn/main_thread_node.hpp>

#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

#include <algorithm>
#include <string>

// ── PreviewNode ───────────────────────────────────────────────────────────────
// MainThreadNode: receives MatchedSceneFrame, draws annotations and shows the
// frame in an OpenCV window.  Runs on the main thread via preview.step().
//
// Returns false on EOF (ends the main loop) or when 'q' / Escape is pressed.

class PreviewNode : public kpn::MainThreadNode<PreviewNode,
                                               kpn::in<"matched">,
                                               MatchedSceneFrame> {
public:
    static constexpr std::string_view label() { return "preview"; }
    explicit PreviewNode(const Config& cfg, std::size_t fifo_capacity = 4)
        : kpn::MainThreadNode<PreviewNode, kpn::in<"matched">, MatchedSceneFrame>(fifo_capacity)
        , max_display_w_(1280)
    {
        cv::namedWindow("scene_preview", cv::WINDOW_NORMAL);
        cv::resizeWindow("scene_preview", 1280, 720);
    }

    // Called by step() on the main thread for each ready MatchedSceneFrame.
    bool operator()(MatchedSceneFrame mf) {
        if (mf.source.eof) return false;

        cv::Mat display = mf.source.image.clone();
        draw_detections(display, mf.actors);
        draw_hud(display, mf.source.timestamp_sec, mf.actors);
        draw_cut_meter(display, mf.source.cut_score, mf.source.is_cut);

        // Fit to display width while keeping aspect ratio
        if (display.cols > max_display_w_) {
            float scale = static_cast<float>(max_display_w_) / display.cols;
            cv::resize(display, display, {}, scale, scale, cv::INTER_AREA);
        }

        cv::imshow("scene_preview", display);

        int key = cv::waitKey(1) & 0xFF;
        return (key != 'q' && key != 27 /* Esc */);
    }

private:
    int max_display_w_;

    static void draw_detections(cv::Mat& img,
                                 const std::vector<IdentifiedActor>& actors) {
        for (const auto& ia : actors) {
            bool known = (ia.actor_idx >= 0);

            // Green for identified, orange for unknown
            cv::Scalar box_colour = known
                ? cv::Scalar(0, 210, 60)
                : cv::Scalar(0, 140, 255);

            // Scale bbox to display image
            cv::Rect2f b = ia.bbox;
            cv::rectangle(img, b, box_colour, 2, cv::LINE_AA);

            std::string tid = (ia.track_id >= 0) ? ("  #" + std::to_string(ia.track_id)) : "";
            std::string label = known
                ? (ia.name + tid + "  " + pct(ia.similarity))
                : ("?" + tid);

            // Dark backing strip so text is readable on any background
            int baseline = 0;
            cv::Size ts = cv::getTextSize(label, cv::FONT_HERSHEY_DUPLEX,
                                          0.55, 1, &baseline);
            cv::Point tl(static_cast<int>(b.x),
                         std::max(0, static_cast<int>(b.y) - ts.height - 6));
            cv::Rect backing(tl.x, tl.y, ts.width + 8, ts.height + 8);
            backing &= cv::Rect(0, 0, img.cols, img.rows);
            if (backing.area() > 0)
                cv::rectangle(img, backing, box_colour * 0.6, cv::FILLED);

            cv::putText(img, label,
                        cv::Point(tl.x + 4, tl.y + ts.height + 2),
                        cv::FONT_HERSHEY_DUPLEX, 0.55,
                        cv::Scalar(255, 255, 255), 1, cv::LINE_AA);
        }
    }

    static void draw_hud(cv::Mat& img, double timestamp_sec,
                          const std::vector<IdentifiedActor>& actors) {
        int known   = 0;
        int unknown = 0;
        for (const auto& a : actors) (a.actor_idx >= 0 ? known : unknown)++;

        // Timestamp and actor count in top-left corner
        char buf[128];
        std::snprintf(buf, sizeof(buf),
                      "t = %dm %02ds   |   %d identified   %d unknown",
                      static_cast<int>(timestamp_sec) / 60,
                      static_cast<int>(timestamp_sec) % 60,
                      known, unknown);

        int baseline = 0;
        cv::Size ts = cv::getTextSize(buf, cv::FONT_HERSHEY_SIMPLEX,
                                      0.6, 1, &baseline);
        cv::Rect hud(0, 0, ts.width + 16, ts.height + 12);
        hud &= cv::Rect(0, 0, img.cols, img.rows);
        cv::rectangle(img, hud, cv::Scalar(20, 20, 20), cv::FILLED);
        cv::putText(img, buf, cv::Point(8, ts.height + 6),
                    cv::FONT_HERSHEY_SIMPLEX, 0.6,
                    cv::Scalar(220, 220, 220), 1, cv::LINE_AA);

        // Active actor name strip along the bottom
        if (known > 0) {
            std::string names;
            for (const auto& a : actors) {
                if (a.actor_idx < 0) continue;
                if (!names.empty()) names += "   ";
                names += a.name;
            }
            cv::Size ns = cv::getTextSize(names, cv::FONT_HERSHEY_SIMPLEX,
                                          0.55, 1, &baseline);
            int y = img.rows - ns.height - 10;
            cv::Rect strip(0, y - 6, img.cols, ns.height + 16);
            strip &= cv::Rect(0, 0, img.cols, img.rows);
            cv::rectangle(img, strip, cv::Scalar(10, 10, 10, 180), cv::FILLED);
            cv::putText(img, names, cv::Point(8, img.rows - 10),
                        cv::FONT_HERSHEY_SIMPLEX, 0.55,
                        cv::Scalar(80, 220, 100), 1, cv::LINE_AA);
        }
    }

    // Running cut-score meter (top-right): a horizontal bar whose fill and colour
    // track the histogram cut score in [0,1] (0 = no change, ~1 = hard cut).
    // Flashes a red "CUT" tag on frames where the threshold tripped (is_cut).
    static void draw_cut_meter(cv::Mat& img, float score, bool is_cut) {
        const int   bar_w = 220;
        const int   bar_h = 14;
        const int   pad   = 10;
        const int   x0    = img.cols - bar_w - pad;
        const int   y0    = pad + 16;   // below any top-left HUD row height

        score = std::clamp(score, 0.f, 1.f);

        // Label
        char lbl[32];
        std::snprintf(lbl, sizeof(lbl), "cut %.2f", score);
        cv::putText(img, lbl, cv::Point(x0, y0 - 4),
                    cv::FONT_HERSHEY_SIMPLEX, 0.5,
                    cv::Scalar(220, 220, 220), 1, cv::LINE_AA);

        // Track (dark) + fill (green→red by score)
        cv::rectangle(img, cv::Rect(x0, y0, bar_w, bar_h),
                      cv::Scalar(40, 40, 40), cv::FILLED);
        int fill_w = static_cast<int>(bar_w * score);
        // BGR: low score → green (0,210,60), high → red (0,0,255)
        cv::Scalar fill_col(60.0 * (1.f - score),
                            210.0 * (1.f - score),
                            60.0 * (1.f - score) + 255.0 * score);
        if (fill_w > 0)
            cv::rectangle(img, cv::Rect(x0, y0, fill_w, bar_h), fill_col, cv::FILLED);
        cv::rectangle(img, cv::Rect(x0, y0, bar_w, bar_h),
                      cv::Scalar(120, 120, 120), 1);

        if (is_cut) {
            cv::putText(img, "CUT", cv::Point(x0 - 52, y0 + bar_h),
                        cv::FONT_HERSHEY_DUPLEX, 0.6,
                        cv::Scalar(0, 0, 255), 2, cv::LINE_AA);
        }
    }

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