///|
/// Declarative CLI request model shared by examples and application wrappers.
pub(all) enum GifCommand {
  Inspect
  Encode
  Decode
  Optimize
  Preview
  Benchmark
} derive(Eq, Debug)

///|
pub(all) struct GifCliRequest {
  command : GifCommand
  input : String
  output : String
  width : Int?
  height : Int?
  colors : Int
  delay_cs : Int
  profile : ExportProfile
} derive(Debug)

///|
pub fn GifCliRequest::default(command : GifCommand) -> GifCliRequest {
  {
    command,
    input: "",
    output: "",
    width: None,
    height: None,
    colors: 256,
    delay_cs: 6,
    profile: Balanced,
  }
}

///|
pub fn GifCliRequest::input(
  self : GifCliRequest,
  path : String,
) -> GifCliRequest {
  { ..self, input: path }
}

///|
pub fn GifCliRequest::output(
  self : GifCliRequest,
  path : String,
) -> GifCliRequest {
  { ..self, output: path }
}

///|
pub fn GifCliRequest::size(
  self : GifCliRequest,
  width : Int,
  height : Int,
) -> GifCliRequest {
  { ..self, width: Some(width), height: Some(height) }
}

///|
pub fn GifCliRequest::colors(
  self : GifCliRequest,
  count : Int,
) -> GifCliRequest {
  { ..self, colors: clamp_palette_size(count) }
}

///|
pub fn GifCliRequest::delay(
  self : GifCliRequest,
  delay_cs : Int,
) -> GifCliRequest {
  {
    ..self,
    delay_cs: if delay_cs < 0 {
      0
    } else if delay_cs > 65535 {
      65535
    } else {
      delay_cs
    },
  }
}

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

///|
pub(all) struct GifCliPlan {
  command : GifCommand
  animation : Animation?
  encode_options : EncodeOptions
  optimization_options : OptimizationOptions
  preview_size : (Int, Int)?
} derive(Debug)

///|
pub fn GifCliPlan::command(self : GifCliPlan) -> GifCommand {
  self.command
}

///|
pub fn GifCliPlan::animation(self : GifCliPlan) -> Animation? {
  self.animation
}

///|
pub fn GifCliPlan::encode_options(self : GifCliPlan) -> EncodeOptions {
  self.encode_options
}

///|
pub fn GifCliPlan::optimization_options(
  self : GifCliPlan,
) -> OptimizationOptions {
  self.optimization_options
}

///|
pub fn GifCliPlan::preview_size(self : GifCliPlan) -> (Int, Int)? {
  self.preview_size
}

///|
/// Convert a request and source animation into an executable plan.
pub fn make_cli_plan(
  request : GifCliRequest,
  animation : Animation?,
) -> GifCliPlan raise GifError {
  let source = match animation {
    None => None
    Some(value) => Some(value)
  }
  let optimization = match source {
    None => OptimizationOptions::default().colors(request.colors)
    Some(value) => value.plan_export(request.profile).optimization()
  }
  let encode = EncodeOptions::new(
    max_colors=request.colors,
    default_delay_cs=request.delay_cs,
  )
  GifCliPlan::{
    command: request.command,
    animation: source,
    encode_options: encode,
    optimization_options: optimization,
    preview_size: match (request.width, request.height) {
      (Some(width), Some(height)) => Some((width, height))
      _ => None
    },
  }
}

///|
/// Return a short command description for shell help.
pub fn command_description(command : GifCommand) -> String {
  match command {
    Inspect => "inspect GIF structure and metadata"
    Encode => "encode a frame sequence as GIF89a"
    Decode => "decode GIF89a into RGBA frames"
    Optimize => "estimate and apply palette/delta optimizations"
    Preview => "render a contact sheet or thumbnail strip"
    Benchmark => "run deterministic encode/decode measurements"
  }
}

///|
/// Validate request values before opening an input resource.
pub fn validate_cli_request(request : GifCliRequest) -> Array[String] {
  let issues : Array[String] = []
  if request.command != Benchmark && request.input.length() == 0 {
    issues.push("input path is required")
  }
  if request.command == Encode && request.output.length() == 0 {
    issues.push("output path is required for encode")
  }
  match (request.width, request.height) {
    (Some(width), Some(height)) if width <= 0 || height <= 0 =>
      issues.push("preview dimensions must be positive")
    _ => ()
  }
  issues
}