///|
/// High-level export intent used by applications instead of raw encoder flags.
pub(all) enum ExportProfile {
  Preview
  Balanced
  SmallFile
  Archive
  Sticker
} derive(Eq, Debug)

///|
pub(all) struct ExportPlan {
  profile : ExportProfile
  optimization : OptimizationOptions
  encode : EncodeOptions
  estimated_bytes : Int
  quality_target : Int
} derive(Debug)

///|
pub fn ExportPlan::profile(self : ExportPlan) -> ExportProfile {
  self.profile
}

///|
pub fn ExportPlan::optimization(self : ExportPlan) -> OptimizationOptions {
  self.optimization
}

///|
pub fn ExportPlan::encode(self : ExportPlan) -> EncodeOptions {
  self.encode
}

///|
pub fn ExportPlan::estimated_bytes(self : ExportPlan) -> Int {
  self.estimated_bytes
}

///|
pub fn ExportPlan::quality_target(self : ExportPlan) -> Int {
  self.quality_target
}

///|
fn profile_options(
  profile : ExportProfile,
) -> (OptimizationOptions, EncodeOptions, Int) {
  match profile {
    Preview => {
      let optimization = OptimizationOptions::default()
        .colors(32)
        .merge_equal(false)
        .crop_deltas(false)
      (optimization, EncodeOptions::new(max_colors=32, default_delay_cs=8), 450)
    }
    Balanced => {
      let optimization = OptimizationOptions::default().colors(128).threshold(4)
      (
        optimization,
        EncodeOptions::new(max_colors=128, default_delay_cs=6),
        800,
      )
    }
    SmallFile => {
      let optimization = OptimizationOptions::default().colors(64).threshold(8)
      (optimization, EncodeOptions::new(max_colors=64, default_delay_cs=6), 620)
    }
    Archive => {
      let optimization = OptimizationOptions::default()
        .colors(256)
        .threshold(0)
        .merge_equal(false)
      (
        optimization,
        EncodeOptions::new(max_colors=256, default_delay_cs=4),
        980,
      )
    }
    Sticker => {
      let optimization = OptimizationOptions::default()
        .colors(128)
        .threshold(2)
        .merge_equal(true)
      (
        optimization,
        EncodeOptions::new(max_colors=128, default_delay_cs=10),
        760,
      )
    }
  }
}

///|
/// Choose a practical optimization and palette plan for a target use case.
pub fn Animation::plan_export(
  self : Animation,
  profile : ExportProfile,
) -> ExportPlan raise GifError {
  let (optimization, encode, quality_target) = profile_options(profile)
  let report = self.optimization_report(optimization)
  ExportPlan::{
    profile,
    optimization,
    encode,
    estimated_bytes: report.estimated_bytes(),
    quality_target,
  }
}

///|
/// Apply an export plan and encode it in one operation.
pub fn Animation::export_with_plan(
  self : Animation,
  plan : ExportPlan,
) -> Bytes raise GifError {
  let optimized = self.optimize_for_size(plan.optimization)
  encode_with_options(optimized, plan.encode)
}

///|
/// Produce a complete profile report for size and frame-retention review.
pub(all) struct ExportReport {
  profile : ExportProfile
  input_frames : Int
  output_frames : Int
  estimated_bytes : Int
  actual_bytes : Int
  retained_pixels_per_mille : Int
  quality_target : Int
} derive(Debug)

///|
pub fn ExportReport::profile(self : ExportReport) -> ExportProfile {
  self.profile
}

///|
pub fn ExportReport::input_frames(self : ExportReport) -> Int {
  self.input_frames
}

///|
pub fn ExportReport::output_frames(self : ExportReport) -> Int {
  self.output_frames
}

///|
pub fn ExportReport::estimated_bytes(self : ExportReport) -> Int {
  self.estimated_bytes
}

///|
pub fn ExportReport::actual_bytes(self : ExportReport) -> Int {
  self.actual_bytes
}

///|
pub fn ExportReport::retained_pixels_per_mille(self : ExportReport) -> Int {
  self.retained_pixels_per_mille
}

///|
pub fn ExportReport::quality_target(self : ExportReport) -> Int {
  self.quality_target
}

///|
/// Run an export profile and return measured encoded bytes.
pub fn Animation::export_report(
  self : Animation,
  profile : ExportProfile,
) -> ExportReport raise GifError {
  let plan = self.plan_export(profile)
  let optimized = self.optimize_for_size(plan.optimization)
  let bytes = encode_with_options(optimized, plan.encode)
  let full_pixels = self.width * self.height * self.frame_count()
  let mut output_pixels = 0
  for frame in optimized.frames {
    output_pixels += frame.canvas().len()
  }
  let retained = if full_pixels <= 0 {
    0
  } else {
    output_pixels * 1000 / full_pixels
  }
  ExportReport::{
    profile,
    input_frames: self.frame_count(),
    output_frames: optimized.frame_count(),
    estimated_bytes: plan.estimated_bytes,
    actual_bytes: bytes.length(),
    retained_pixels_per_mille: retained,
    quality_target: plan.quality_target,
  }
}

///|
/// Select a profile from a size budget using the optimizer's exact estimate.
pub fn Animation::profile_for_budget(
  self : Animation,
  budget_bytes : Int,
) -> ExportProfile raise GifError {
  let safe = if budget_bytes < 1 { 1 } else { budget_bytes }
  let profiles = [SmallFile, Sticker, Balanced, Archive]
  let mut selected = SmallFile
  for profile in profiles {
    let plan = self.plan_export(profile)
    if plan.estimated_bytes() <= safe {
      selected = profile
    }
  }
  selected
}