# typed: strict
# frozen_string_literal: true

class LoadedMedia::Webm < LoadedMedia
  include HasColorLogger

  ::FFMPEG.logger = Logger.new(nil)
  FFMPEG_BIN = T.let(`which ffmpeg`.strip, String)
  FFPROBE_BIN = T.let(`which ffprobe`.strip, String)

  sig { params(media_path: String).void }
  def initialize(media_path)
    @media_path = media_path
    @media = T.let(FFMPEG::Media.new(media_path), FFMPEG::Media)
    duration = @media.duration
    # frame_rate = @media.frame_rate
    # @num_frames = T.let((frame_rate * duration).to_i, Integer)

    output, error, status =
      Open3.capture3(
        FFPROBE_BIN,
        "-v",
        "error",
        "-count_frames",
        "-select_streams",
        "v:0",
        "-show_entries",
        "stream=nb_read_frames",
        "-of",
        "default=nokey=1:noprint_wrappers=1",
        media_path,
      )

    if status.success?
      @num_frames = T.let(output.strip.to_i, Integer)
    else
      $stderr.print error
      raise "Failed to get frame count from ffprobe: exit code #{status.exitstatus}"
    end

    @duration = T.let(duration, Float)
    raise("no frames found in webm") if @num_frames.zero?
  end

  sig { override.returns(Integer) }
  def num_frames
    @num_frames
  end

  sig do
    override
      .params(frame: Integer, path: String, options: ThumbnailOptions)
      .void
  end
  def write_frame_thumbnail(frame, path, options)
    # Clamp the frame number to valid range for calculation
    clamped_frame = frame.clamp(0, @num_frames - 1)

    # Calculate frame time using the clamped frame number
    frame_time = (clamped_frame.to_f * @duration) / @num_frames
    frame_time = frame_time.clamp(0.0..(@duration * 0.98))

    logger.info(
      format_tags(
        make_tag("frame", frame),
        make_tag("frame_time", frame_time),
        make_tag("duration", @duration),
        make_tag("num_frames", @num_frames),
      ),
    )

    # Use the original frame number in the filename
    tmp_path =
      File.join(BlobFile::TMP_DIR, "webm-#{frame}-#{SecureRandom.uuid}.png")
    FileUtils.mkdir_p(File.dirname(tmp_path))

    # @media.screenshot(tmp_path, { seek_time: frame_time })

    # Determine if we should seek from start or end based on where we are in the file
    past_halfway = frame_time / @duration > 0.5

    cmd = [FFMPEG_BIN, "-y", "-xerror", "-abort_on", "empty_output"] # Overwrite output files

    if past_halfway
      # For frames in the second half of the file, seek from the end
      # Convert to a negative offset from the end
      end_offset = frame_time - @duration
      cmd.concat(["-sseof", end_offset.round(2).to_s])
    else
      # For frames in the first half, seek from the beginning
      cmd.concat(["-ss", frame_time.round(2).to_s])
    end

    # Add input file and frame extraction options
    cmd.concat(
      [
        "-i",
        @media_path, # Input file
        "-vframes",
        "1", # Extract one frame
        "-f",
        "image2", # Force format to image2
        "-update",
        "1", # Update existing file
        tmp_path,
      ],
    )

    _output, error, status = Open3.capture3(*cmd)
    unless status.exitstatus == 0
      $stderr.print error
      raise "Failed to extract frame with ffmpeg: #{error}"
    end

    # Use the original frame number in the error message
    raise("screenshot @ #{clamped_frame} failed") unless File.exist?(tmp_path)
    image = Vips::Image.new_from_file(tmp_path)
    write_image_thumbnail(image, path, options)
  ensure
    FileUtils.rm_rf(tmp_path) if tmp_path && File.exist?(tmp_path)
  end
end
