#pragma once

#include <string>

#ifdef SMARTCUT_HAS_FFMPEG
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
#include <libavutil/buffer.h>
#include <libavutil/pixfmt.h>
}
#endif

namespace smartcut::media {

#ifdef SMARTCUT_HAS_FFMPEG

// Opens the best available Windows hardware decoder. D3D11VA remains the
// preferred path for conventional delivery codecs; Vulkan compute is used as
// a cross-vendor fallback and provides FFV1/ProRes acceleration. Callers can
// retry with allow_hardware=false if a driver rejects a particular stream.
class HardwareVideoDecoder {
 public:
  HardwareVideoDecoder() = default;
  ~HardwareVideoDecoder();
  HardwareVideoDecoder(const HardwareVideoDecoder&) = delete;
  HardwareVideoDecoder& operator=(const HardwareVideoDecoder&) = delete;

  bool open(const AVCodecParameters* parameters, AVCodecID codec_id,
            const AVCodec* software_decoder, bool allow_hardware);
  AVCodecContext* context() const { return context_; }

  // Hardware frames stay on the GPU until the caller actually needs CPU
  // pixels. The returned frame is either decoded_frame itself or transfer_frame.
  const AVFrame* cpu_frame(const AVFrame* decoded_frame, AVFrame* transfer_frame);
  bool is_hardware_frame(const AVFrame* frame) const;

  bool hardware_requested() const { return hardware_requested_; }
  bool hardware_active() const { return hardware_frame_seen_; }
  const std::string& backend_name() const { return backend_name_; }

 private:
  static AVPixelFormat select_pixel_format(AVCodecContext* context,
                                            const AVPixelFormat* formats);
  void close();
  bool open_software(const AVCodecParameters* parameters, const AVCodec* decoder);

  AVCodecContext* context_{};
  AVBufferRef* hardware_device_{};
  AVPixelFormat hardware_format_{AV_PIX_FMT_NONE};
  bool hardware_requested_{};
  bool hardware_frame_seen_{};
  std::string backend_name_{"software"};
};

// Reuses one GPU scaling graph so thumbnail and analysis downscaling happens
// before the final GPU-to-CPU transfer. D3D11 and Vulkan frames are supported;
// callers fall back to HardwareVideoDecoder::cpu_frame if a driver rejects it.
class HardwareFrameScaler {
 public:
  HardwareFrameScaler() = default;
  ~HardwareFrameScaler();
  HardwareFrameScaler(const HardwareFrameScaler&) = delete;
  HardwareFrameScaler& operator=(const HardwareFrameScaler&) = delete;

  const AVFrame* scale(const AVFrame* source, AVFrame* scaled,
                       int width, int height);
  void reset();
  const std::string& backend_name() const { return backend_name_; }

 private:
  bool configure(const AVFrame* source, int width, int height);
  AVFilterGraph* graph_{};
  AVFilterContext* source_{};
  AVFilterContext* scaler_{};
  AVFilterContext* sink_{};
  int source_width_{};
  int source_height_{};
  int output_width_{};
  int output_height_{};
  AVPixelFormat source_format_{AV_PIX_FMT_NONE};
  std::string backend_name_;
};

#endif

}  // namespace smartcut::media
