#pragma once

#include "types.h"
#include "backend.h"
#include "lru_cache.h"
#include "preview_cache.h"
#include "thread_pool.h"

#include <condition_variable>
#include <cmath>
#include <future>
#include <memory>
#include <mutex>
#include <semaphore>
#include <unordered_set>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/dict.h>
#include <libavutil/display.h>
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libavutil/pixdesc.h>
#include <libswscale/swscale.h>
}

namespace mediadb {

// Internal result from FFmpeg rendering
struct PreviewResult {
    std::vector<std::uint8_t> bytes;
    std::string mime;
    std::string etag;
};

class FFmpegPreviewer {
public:
    std::optional<PreviewResult> render_streaming(MediaBackendApi& db, ThreadPool& io_pool,
                                                  BlobCache& cache,
                                                  const std::string& media_id,
                                                  std::uint64_t media_size, const std::string& media_kind,
                                                  int width, int height, const std::string& fmt, int t_seconds,
                                                  int duration, std::string& error_out);

private:
    std::optional<PreviewResult> render_video_clip(
        AVFormatContext* format_ctx, int stream_index, AVCodecContext* dec_ctx,
        int src_w, int src_h, int out_w, int out_h,
        SwsContext* sws, AVFrame* scaled,
        int duration_seconds, const std::string& codec_tag, std::string& error_out);

    std::optional<PreviewResult> encode_frame(AVFrame* frame, const std::string& fmt, std::string& error_out);
};

class PreviewService {
public:
    explicit PreviewService(BlobCache& cache, MediaBackendApi& db,
                            std::size_t render_threads = 0,
                            PreviewCache* l2 = nullptr);

    // Build the cache key string for a preview request
    static std::string make_key(const std::string& media_id,
                                int width, int height, const std::string& fmt,
                                int t_seconds, int duration);

    std::shared_ptr<const BlobValue> cache_get(const std::string& key);
    std::shared_ptr<const BlobValue> get_or_create_streaming(const MediaRecord& media,
                                              int width, int height, const std::string& fmt,
                                              int t_seconds, int duration, std::string& error_out);

    // Submit a fire-and-forget I/O task on the bounded io pool.
    // Silently drops the task if the queue depth exceeds 2× pool size
    // to prevent unbounded accumulation during heavy streaming.
    template<typename F> void submit_io(F&& fn) {
        if (io_pool_.queue_depth() < io_pool_.size() * 2)
            io_pool_.submit(std::forward<F>(fn));
    }

private:
    BlobCache& cache_;
    MediaBackendApi& db_;
    PreviewCache* l2_ = nullptr;
    FFmpegPreviewer ffmpeg_;
    ThreadPool io_pool_;       // prefetch I/O only — never blocked by renders
    std::counting_semaphore<256> render_sem_;  // backpressure: limits concurrent renders on HTTP workers
    std::mutex inflight_mutex_;
    std::condition_variable inflight_cv_;
    std::unordered_set<std::string> inflight_;
};

} // namespace mediadb
