#include "media/ffmpeg_inventory.hpp"
#include "media/sei_injector.hpp"
#include "media/ffmpeg_raii.hpp"
#include <sstream>
#include <fstream>
#include <filesystem>
#include <cstring>
#include <set>
#ifdef SMARTCUT_HAS_FFMPEG
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavcodec/bsf.h>
#include <libavformat/avformat.h>
#include <libavutil/error.h>
#include <libavutil/opt.h>
#include <libavutil/mastering_display_metadata.h>
#include <libavutil/hdr_dynamic_metadata.h>
#include <libavutil/mem.h>
}
#endif
namespace smartcut::media {
#ifndef SMARTCUT_HAS_FFMPEG
bool extract_hevc_annex_b(const std::string&, const std::string&) { return false; }
#else
bool extract_hevc_annex_b(const std::string& input, const std::string& output) {
  AVFormatContext* format = nullptr; AVPacket* packet = av_packet_alloc(); AVPacket* filtered = av_packet_alloc(); AVBSFContext* filter = nullptr; std::ofstream stream(output, std::ios::binary | std::ios::trunc); bool ok = false;
  auto cleanup = [&] { av_bsf_free(&filter); av_packet_free(&filtered); av_packet_free(&packet); avformat_close_input(&format); stream.close(); if (!ok) { std::error_code ignored; std::filesystem::remove(output, ignored); } };
  if (!packet || !filtered || !stream || avformat_open_input(&format, input.c_str(), nullptr, nullptr) < 0 || avformat_find_stream_info(format, nullptr) < 0) { cleanup(); return false; }
  int video_stream = -1; for (unsigned i = 0; i < format->nb_streams; ++i) if (format->streams[i]->codecpar->codec_id == AV_CODEC_ID_HEVC) { video_stream = static_cast<int>(i); break; }
  const auto* implementation = av_bsf_get_by_name("hevc_mp4toannexb");
  if (video_stream < 0 || !implementation || av_bsf_alloc(implementation, &filter) < 0 || !filter || avcodec_parameters_copy(filter->par_in, format->streams[video_stream]->codecpar) < 0) { cleanup(); return false; }
  filter->time_base_in = format->streams[video_stream]->time_base; if (av_bsf_init(filter) < 0) { cleanup(); return false; }
  auto receive = [&] { while (av_bsf_receive_packet(filter, filtered) >= 0) { stream.write(reinterpret_cast<const char*>(filtered->data), filtered->size); av_packet_unref(filtered); } return stream.good(); };
  while (av_read_frame(format, packet) >= 0) { if (packet->stream_index == video_stream && av_bsf_send_packet(filter, packet) >= 0 && !receive()) { cleanup(); return false; } av_packet_unref(packet); }
  av_bsf_send_packet(filter, nullptr); ok = receive(); cleanup(); return ok;
}
#endif
#ifdef SMARTCUT_HAS_FFMPEG
static std::pair<std::size_t, std::size_t> count_hevc_packet_units(const AVPacket* packet, std::set<std::uint64_t>& sei_hashes) {
  if (!packet || packet->size < 2) return {0, 0};
  const auto* bytes = reinterpret_cast<const std::uint8_t*>(packet->data);
  const std::size_t size = static_cast<std::size_t>(packet->size);
  std::size_t sei = 0, rpu = 0;
  auto hash_range = [&](std::size_t begin, std::size_t end) {
    std::uint64_t hash = 1469598103934665603ULL;
    for (std::size_t i = begin; i < end; ++i) { hash ^= bytes[i]; hash *= 1099511628211ULL; }
    return hash;
  };
  auto count_header = [&](std::size_t offset, std::size_t end) {
    if (offset + 2 > size) return;
    const unsigned type = (bytes[offset] >> 1) & 0x3f;
    if (type == 39) ++sei;
    if (type == 62) ++rpu;
    if (type == 39) sei_hashes.insert(hash_range(offset, end));
  };
  bool annex_b = false;
  for (std::size_t i = 0; i + 3 < size; ++i) if (bytes[i] == 0 && bytes[i + 1] == 0 && (bytes[i + 2] == 1 || (bytes[i + 2] == 0 && bytes[i + 3] == 1))) { annex_b = true; break; }
  if (annex_b) {
    for (std::size_t i = 0; i + 3 < size;) {
      const bool start_code = bytes[i] == 0 && bytes[i + 1] == 0 && (bytes[i + 2] == 1 || (bytes[i + 2] == 0 && bytes[i + 3] == 1));
      if (!start_code) { ++i; continue; }
      const std::size_t start = i + (bytes[i + 2] == 1 ? 3 : 4);
      std::size_t end = start;
      while (end + 3 < size) { const bool next_start = bytes[end] == 0 && bytes[end + 1] == 0 && (bytes[end + 2] == 1 || (bytes[end + 2] == 0 && bytes[end + 3] == 1)); if (next_start) break; ++end; }
      count_header(start, end); i = start;
    }
  } else {
    std::size_t offset = 0;
    while (offset + 4 <= size) {
      const std::size_t length = (static_cast<std::size_t>(bytes[offset]) << 24) | (static_cast<std::size_t>(bytes[offset + 1]) << 16) | (static_cast<std::size_t>(bytes[offset + 2]) << 8) | bytes[offset + 3];
      offset += 4; if (length < 2 || offset + length > size) break;
      count_header(offset, offset + length); offset += length;
    }
  }
  return {sei, rpu};
}
#endif
ContainerInventory inventory_container(const std::string& path, std::size_t max_frames) {
#ifdef SMARTCUT_HAS_FFMPEG
  AVFormatContext* format = nullptr;
  if (avformat_open_input(&format, path.c_str(), nullptr, nullptr) < 0) return {"{\"error\":\"avformat_open_input failed\"}"};
  if (avformat_find_stream_info(format, nullptr) < 0) { avformat_close_input(&format); return {"{\"error\":\"avformat_find_stream_info failed\"}"}; }
  std::size_t packets = 0, packet_side_data = 0, packet_sei_nals = 0, packet_rpu_nals = 0, decoded_frames = 0, frame_side_data = 0; std::set<std::uint64_t> packet_sei_hashes; int decoded_width = 0, decoded_height = 0, decoded_pix_fmt = -1;
  std::size_t encoded_packets = 0, encoded_frames = 0, hdr10_frames = 0, hdr10plus_frames = 0, hdr10plus_bytes = 0, hdr10plus_sei_bytes = 0;
  bool main10_encoder_available = false;
  bool main10_encoder_found = false;
  int main10_encoder_open_error = 0;
  const AVCodec* main10_encoder = avcodec_find_encoder_by_name("libx265");
  if (main10_encoder) {
    main10_encoder_found = true; AVCodecContext* enc = avcodec_alloc_context3(main10_encoder); if (enc) { enc->width = 64; enc->height = 64; enc->time_base = AVRational{1, 25}; enc->framerate = AVRational{25, 1}; enc->pix_fmt = AV_PIX_FMT_YUV420P10LE; enc->sample_aspect_ratio = AVRational{1, 1}; enc->bit_rate = 1000000; enc->gop_size = 25; enc->max_b_frames = 0; AVDictionary* options = nullptr; main10_encoder_open_error = avcodec_open2(enc, main10_encoder, &options); main10_encoder_available = main10_encoder_open_error >= 0; av_dict_free(&options); if (main10_encoder_available) { AVFrame* test_frame = av_frame_alloc(); AVPacket* test_packet = av_packet_alloc(); if (test_frame && test_packet) { test_frame->format = enc->pix_fmt; test_frame->width = enc->width; test_frame->height = enc->height; if (av_frame_get_buffer(test_frame, 32) >= 0) { AVMasteringDisplayMetadata* mastering = av_mastering_display_metadata_create_side_data(test_frame); if (mastering) { mastering->display_primaries[0][0] = AVRational{13250, 100000}; mastering->display_primaries[0][1] = AVRational{34500, 100000}; mastering->display_primaries[1][0] = AVRational{7500, 100000}; mastering->display_primaries[1][1] = AVRational{3000, 100000}; mastering->display_primaries[2][0] = AVRational{34000, 100000}; mastering->display_primaries[2][1] = AVRational{16000, 100000}; mastering->white_point[0] = AVRational{3127, 10000}; mastering->white_point[1] = AVRational{3290, 10000}; mastering->min_luminance = AVRational{1, 100}; mastering->max_luminance = AVRational{1000, 1}; mastering->has_primaries = 1; mastering->has_luminance = 1; } AVContentLightMetadata* light = av_content_light_metadata_create_side_data(test_frame); if (light) { light->MaxCLL = 1000; light->MaxFALL = 400; } AVDynamicHDRPlus* hdr10plus = av_dynamic_hdr_plus_create_side_data(test_frame); int hdr10plus_status = -1; if (hdr10plus) { hdr10plus->itu_t_t35_country_code = 0xB5; hdr10plus->application_version = 0; hdr10plus->num_windows = 1; hdr10plus->targeted_system_display_maximum_luminance = AVRational{1000, 1}; AVHDRPlusColorTransformParams& params = hdr10plus->params[0]; params.window_upper_left_corner_x = AVRational{0, 1}; params.window_upper_left_corner_y = AVRational{0, 1}; params.window_lower_right_corner_x = AVRational{1, 1}; params.window_lower_right_corner_y = AVRational{1, 1}; params.center_of_ellipse_x = 32; params.center_of_ellipse_y = 32; params.semimajor_axis_internal_ellipse = 1; params.semimajor_axis_external_ellipse = 1; params.semiminor_axis_external_ellipse = 1; params.maxscl[0] = AVRational{1, 1}; params.maxscl[1] = AVRational{1, 1}; params.maxscl[2] = AVRational{1, 1}; params.average_maxrgb = AVRational{1, 1}; params.num_distribution_maxrgb_percentiles = 1; params.distribution_maxrgb[0].percentage = 50; params.distribution_maxrgb[0].percentile = AVRational{1, 1}; params.fraction_bright_pixels = AVRational{1, 1}; params.tone_mapping_flag = 1; params.knee_point_x = AVRational{1, 2}; params.knee_point_y = AVRational{1, 2}; params.color_saturation_mapping_flag = 1; params.color_saturation_weight = AVRational{1, 1}; uint8_t* t35 = nullptr; size_t t35_size = 0; hdr10plus_status = av_dynamic_hdr_plus_to_t35(hdr10plus, &t35, &t35_size); if (hdr10plus_status >= 0) { hdr10plus_bytes = t35_size; hdr10plus_sei_bytes = make_hevc_prefix_sei_t35(std::span<const std::uint8_t>(t35, t35_size)).size(); } av_free(t35); } for (int i = 0; i < 5; ++i) { if (av_frame_make_writable(test_frame) < 0) break; std::memset(test_frame->data[0], 0x80, static_cast<std::size_t>(test_frame->linesize[0]) * test_frame->height); std::memset(test_frame->data[1], 0x80, static_cast<std::size_t>(test_frame->linesize[1]) * ((test_frame->height + 1) / 2)); test_frame->pts = i; if (mastering && light) ++hdr10_frames; if (hdr10plus && hdr10plus_status >= 0) ++hdr10plus_frames; if (avcodec_send_frame(enc, test_frame) < 0) break; ++encoded_frames; while (avcodec_receive_packet(enc, test_packet) >= 0) { ++encoded_packets; av_packet_unref(test_packet); } } avcodec_send_frame(enc, nullptr); while (avcodec_receive_packet(enc, test_packet) >= 0) { ++encoded_packets; av_packet_unref(test_packet); } } } av_frame_free(&test_frame); av_packet_free(&test_packet); } avcodec_free_context(&enc); } }
  int video_stream = -1;
  for (unsigned i = 0; i < format->nb_streams; ++i) if (format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream = static_cast<int>(i); break; }
  const AVCodec* decoder = video_stream >= 0 ? ffmpeg::preferred_video_decoder(format->streams[video_stream]->codecpar->codec_id) : nullptr;
  AVCodecContext* decoder_context = decoder ? avcodec_alloc_context3(decoder) : nullptr;
  if (decoder_context && avcodec_parameters_to_context(decoder_context, format->streams[video_stream]->codecpar) >= 0 && avcodec_open2(decoder_context, decoder, nullptr) < 0) { avcodec_free_context(&decoder_context); decoder_context = nullptr; }
  AVFrame* frame = av_frame_alloc();
  AVPacket* packet = av_packet_alloc();
  auto receive_frames = [&] { if (!decoder_context || !frame) return; while (avcodec_receive_frame(decoder_context, frame) >= 0) { ++decoded_frames; decoded_width = frame->width; decoded_height = frame->height; decoded_pix_fmt = frame->format; frame_side_data += frame->nb_side_data; av_frame_unref(frame); } };
  while (packet && av_read_frame(format, packet) >= 0) { ++packets; packet_side_data += packet->side_data_elems; if (packet->stream_index == video_stream) { const auto counts = count_hevc_packet_units(packet, packet_sei_hashes); packet_sei_nals += counts.first; packet_rpu_nals += counts.second; } if (decoder_context && packet->stream_index == video_stream && (max_frames == 0 || decoded_frames < max_frames)) { if (avcodec_send_packet(decoder_context, packet) >= 0) receive_frames(); } av_packet_unref(packet); }
  if (decoder_context && (max_frames == 0 || decoded_frames < max_frames)) { avcodec_send_packet(decoder_context, nullptr); receive_frames(); }
  av_frame_free(&frame); av_packet_free(&packet); avcodec_free_context(&decoder_context);
  std::ostringstream out;
  out << "{\"schema\":\"metadata-lab-container-v1\",\"format\":\"" << (format->iformat ? format->iformat->name : "unknown") << "\",\"streams\":" << format->nb_streams << ",\"packets\":" << packets << ",\"packet_side_data\":" << packet_side_data << ",\"packet_sei_nals\":" << packet_sei_nals << ",\"packet_sei_variants\":" << packet_sei_hashes.size() << ",\"packet_rpu_nals\":" << packet_rpu_nals << ",\"decode_limit\":" << max_frames << ",\"decoded_frames\":" << decoded_frames << ",\"frame_side_data\":" << frame_side_data << ",\"decoded_width\":" << decoded_width << ",\"decoded_height\":" << decoded_height << ",\"decoded_pix_fmt\":" << decoded_pix_fmt << ",\"decoded_is_10bit\":" << ((decoded_pix_fmt == AV_PIX_FMT_YUV420P10LE || decoded_pix_fmt == AV_PIX_FMT_YUV422P10LE || decoded_pix_fmt == AV_PIX_FMT_YUV444P10LE) ? "true" : "false") << ",\"main10_encoder_found\":" << (main10_encoder_found ? "true" : "false") << ",\"main10_encoder_open_error\":" << main10_encoder_open_error << ",\"main10_encoder_available\":" << (main10_encoder_available ? "true" : "false") << ",\"encoded_test_frames\":" << encoded_frames << ",\"encoded_test_packets\":" << encoded_packets << ",\"hdr10_metadata_frames\":" << hdr10_frames << ",\"hdr10plus_metadata_frames\":" << hdr10plus_frames << ",\"hdr10plus_t35_bytes\":" << hdr10plus_bytes << ",\"hdr10plus_sei_bytes\":" << hdr10plus_sei_bytes << ",\"stream_details\":[";
  for (unsigned i = 0; i < format->nb_streams; ++i) { const auto* p = format->streams[i]->codecpar; if (i) out << ','; out << "{\"index\":" << i << ",\"codec_type\":" << p->codec_type << ",\"codec_id\":" << p->codec_id << ",\"width\":" << p->width << ",\"height\":" << p->height << ",\"bit_depth\":" << p->bits_per_raw_sample << ",\"color_primaries\":" << p->color_primaries << ",\"color_trc\":" << p->color_trc << ",\"color_space\":" << p->color_space << ",\"codec_side_data\":" << p->nb_coded_side_data << "}"; }
  out << "]}";
  avformat_close_input(&format);
  return {out.str()};
#else
  (void)path;
  (void)max_frames;
  return {"{\"error\":\"FFmpeg support is not enabled; configure with SMARTCUT_MEDIA_ROOT\"}"};
#endif
}
}














