///|
/// Shared image placement geometry. Skia and Sun each maintained their own
/// contain/cover/stretch/scale-down/fit-width/fit-height placement logic with
/// identical decision math but different source-crop representations (Skia
/// normalized UV, Sun integer pixel rect). This module owns the geometry
/// once; renderers keep their native crop conversion for zero behavior change.

///|
/// Which source axis is cropped by `visible` (the fraction of the source that
/// remains visible). `None` means the full source is sampled.
pub enum ImageCropAxis {
  None
  Horizontal
  Vertical
}

///|
pub struct ImagePlacement {
  frame : @core.Rect
  crop_axis : ImageCropAxis
  visible : Double
}

///|
pub fn image_placement(
  frame : @core.Rect,
  image_size : @core.Size,
  fit : @core.ImageFit,
) -> ImagePlacement {
  if image_size.width <= 0.0 ||
    image_size.height <= 0.0 ||
    frame.size.width <= 0.0 ||
    frame.size.height <= 0.0 {
    return { frame, crop_axis: None, visible: 1.0, }
  }
  let image_ratio = image_size.width / image_size.height
  let frame_ratio = frame.size.width / frame.size.height
  match fit {
    Stretch => { frame, crop_axis: None, visible: 1.0, }
    ScaleDown =>
      if image_size.width <= frame.size.width &&
        image_size.height <= frame.size.height {
        {
          frame: @core.Rect::new(
            x=frame.origin.x + (frame.size.width - image_size.width) / 2.0,
            y=frame.origin.y + (frame.size.height - image_size.height) / 2.0,
            width=image_size.width,
            height=image_size.height,
          ),
          crop_axis: None,
          visible: 1.0,
        }
      } else {
        image_contain_placement(frame, image_size)
      }
    FitWidth => image_fit_width_placement(frame, image_ratio)
    FitHeight => image_fit_height_placement(frame, image_ratio)
    Contain => image_contain_placement(frame, image_size)
    Cover =>
      if image_ratio > frame_ratio {
        { frame, crop_axis: Horizontal, visible: frame_ratio / image_ratio, }
      } else {
        { frame, crop_axis: Vertical, visible: image_ratio / frame_ratio, }
      }
  }
}

///|
fn image_fit_width_placement(
  frame : @core.Rect,
  image_ratio : Double,
) -> ImagePlacement {
  let scaled_height = frame.size.width / image_ratio
  if scaled_height <= frame.size.height {
    {
      frame: @core.Rect::new(
        x=frame.origin.x,
        y=frame.origin.y + (frame.size.height - scaled_height) / 2.0,
        width=frame.size.width,
        height=scaled_height,
      ),
      crop_axis: None,
      visible: 1.0,
    }
  } else {
    { frame, crop_axis: Vertical, visible: frame.size.height / scaled_height, }
  }
}

///|
fn image_fit_height_placement(
  frame : @core.Rect,
  image_ratio : Double,
) -> ImagePlacement {
  let scaled_width = frame.size.height * image_ratio
  if scaled_width <= frame.size.width {
    {
      frame: @core.Rect::new(
        x=frame.origin.x + (frame.size.width - scaled_width) / 2.0,
        y=frame.origin.y,
        width=scaled_width,
        height=frame.size.height,
      ),
      crop_axis: None,
      visible: 1.0,
    }
  } else {
    { frame, crop_axis: Horizontal, visible: frame.size.width / scaled_width, }
  }
}

///|
fn image_contain_placement(
  frame : @core.Rect,
  image_size : @core.Size,
) -> ImagePlacement {
  let scale = (frame.size.width / image_size.width).min(
    frame.size.height / image_size.height,
  )
  let width = image_size.width * scale
  let height = image_size.height * scale
  {
    frame: @core.Rect::new(
      x=frame.origin.x + (frame.size.width - width) / 2.0,
      y=frame.origin.y + (frame.size.height - height) / 2.0,
      width~,
      height~,
    ),
    crop_axis: None,
    visible: 1.0,
  }
}