///|
/// Internal RGBA8 exchange between the still encoder and GIF sampler.
priv struct RasterPixels {
  width : Int32
  height : Int32
  data : Bytes
}

///|
fn raster_fail(
  code : String,
  message : String,
  source_id? : String,
) -> RasterFailure {
  RasterInvalid(code, message, source_id)
}

///|
fn validate_raster_limits(limits : RasterLimits) -> Unit raise RasterFailure {
  guard limits.max_source_units > 0 &&
    limits.max_svg_nodes > 0 &&
    limits.max_dimension > 0 &&
    limits.max_pixels > 0L &&
    limits.max_glyphs > 0 &&
    limits.max_output_bytes > 0 &&
    limits.max_frames > 0 &&
    limits.max_cumulative_pixels > 0L else {
    raise raster_fail(
      "raster/invalid-limits", "All raster budgets must be positive",
    )
  }
}

///|
fn validate_background(color : Rgba) -> Unit raise RasterFailure {
  for value in [color.red, color.green, color.blue, color.alpha] {
    guard value >= 0 && value <= 255 else {
      raise raster_fail(
        "raster/invalid-background", "Color components must be integers from 0 to 255",
      )
    }
  }
}

///|
fn checked_pixels(
  width : Int32,
  height : Int32,
  limits : RasterLimits,
) -> Int32 raise RasterFailure {
  guard width > 0 &&
    height > 0 &&
    width <= limits.max_dimension &&
    height <= limits.max_dimension else {
    raise raster_fail(
      "raster/dimension-limit", "Output dimensions exceed the configured dimension budget",
    )
  }
  let pixels = width.to_int64() * height.to_int64()
  guard pixels <= limits.max_pixels && pixels <= 536870911L else {
    raise raster_fail(
      "raster/pixel-limit", "Output pixels exceed the configured pixel budget",
    )
  }
  pixels.to_int()
}

///|
fn still_dimensions(
  source : String,
  size : RasterSize,
  limits : RasterLimits,
) -> (Int32, Int32) raise RasterFailure {
  validate_raster_size(size, limits)
  let nodes = parse_svg_xml(source, max_nodes=limits.max_svg_nodes)
  let roots = nodes[0].children.filter(i => nodes[i].kind == Element)
  let root = nodes[roots[0]]
  let mut width = 0.0
  let mut height = 0.0
  if node_attr(root, "viewBox") is Some(box) {
    let parts = box
      .replace(old=",", new=" ")
      .split(" ")
      .filter(s => s.trim() != "")
      .to_array()
    guard parts.length() == 4 else {
      raise raster_fail(
        "raster/malformed-svg", "Generated viewBox needs four values",
      )
    }
    width = num(parts[2].to_owned())
    height = num(parts[3].to_owned())
  } else {
    width = num(node_attr(root, "width").unwrap_or("0"))
    height = num(node_attr(root, "height").unwrap_or("0"))
  }
  guard width > 0.0 &&
    height > 0.0 &&
    raster_finite(width) &&
    raster_finite(height) else {
    raise raster_fail(
      "raster/malformed-svg", "Generated SVG requires finite positive dimensions",
    )
  }
  let scale = match size {
    Original => 1.0
    Scale(value) => value
    Width(value) => value.to_double() / width
    Height(value) => value.to_double() / height
    Fit(w, h) => {
      guard w > 0 && h > 0 else {
        raise raster_fail(
          "raster/invalid-size", "Fit dimensions must be positive",
        )
      }
      (w.to_double() / width).min(h.to_double() / height)
    }
  }
  guard scale > 0.0 && raster_finite(scale) else {
    raise raster_fail(
      "raster/invalid-scale", "Scale must be finite and positive",
    )
  }
  // Keep explicit axes exact, and derive the other axis without introducing
  // a divide-then-multiply overshoot at an integral aspect result.
  let (w, h) = match size {
    Width(value) => (value.to_double(), ratio_dimension(height, value, width))
    Height(value) => (ratio_dimension(width, value, height), value.to_double())
    Fit(max_w, max_h) => {
      let width_limited = if width <= 2147483647.0 &&
        height <= 2147483647.0 &&
        width.floor() == width &&
        height.floor() == height {
        max_w.to_int64() * height.to_int().to_int64() <=
        max_h.to_int64() * width.to_int().to_int64()
      } else {
        max_w.to_double() / width <= max_h.to_double() / height
      }
      if width_limited {
        (
          max_w.to_double(),
          ratio_dimension(height, max_w, width).min(max_h.to_double()),
        )
      } else {
        (
          ratio_dimension(width, max_h, height).min(max_w.to_double()),
          max_h.to_double(),
        )
      }
    }
    _ => ((width * scale).ceil(), (height * scale).ceil())
  }
  guard raster_finite(w) &&
    raster_finite(h) &&
    w <= limits.max_dimension.to_double() &&
    h <= limits.max_dimension.to_double() else {
    raise raster_fail(
      "raster/dimension-limit", "Scaled output exceeds the dimension budget",
    )
  }
  let result = (w.to_int(), h.to_int())
  ignore(checked_pixels(result.0, result.1, limits))
  result
}

///|
fn encode_png_pixels(
  pixels : RasterPixels,
  limits : RasterLimits,
) -> Bytes raise RasterFailure {
  let encoded = @image.encode_png({
    width: pixels.width,
    height: pixels.height,
    data: pixels.data,
  }) catch {
    _ => raise raster_fail("raster/png-encoding", "PNG encoding failed")
  }
  guard encoded.length() <= limits.max_output_bytes else {
    raise raster_fail(
      "raster/output-limit", "PNG exceeds the encoded byte budget",
    )
  }
  encoded
}

///|
fn encode_jpeg_pixels(
  pixels : RasterPixels,
  quality : Int32,
  limits : RasterLimits,
) -> Bytes raise RasterFailure {
  guard quality >= 1 && quality <= 100 else {
    raise raster_fail(
      "raster/invalid-quality", "JPEG quality must be from 1 to 100",
    )
  }
  let encoded = raster_encode_jpeg_unchecked(
    { width: pixels.width, height: pixels.height, data: pixels.data, },
    quality~,
  ) catch {
    _ => raise raster_fail("raster/jpeg-encoding", "JPEG encoding failed")
  }
  guard encoded.length() <= limits.max_output_bytes else {
    raise raster_fail(
      "raster/output-limit", "JPEG exceeds the encoded byte budget",
    )
  }
  encoded
}

///|
/// Still conversion always receives the complete canonical SVG. Viewport and
/// playback state have no representation in this API.
pub fn rasterize(
  source : String,
  options~ : StillOptions = StillOptions::new(),
  fonts? : FontSet,
  limits~ : RasterLimits = RasterLimits::default(),
) -> RasterResult {
  try {
    validate_raster_limits(limits)
    validate_background(options.background)
    validate_raster_size(options.size, limits)
    guard source.length() <= limits.max_source_units else {
      raise raster_fail(
        "raster/source-limit", "Generated SVG exceeds the source budget",
      )
    }
    if options.format is Jpeg(quality~) {
      guard quality >= 1 && quality <= 100 else {
        raise raster_fail(
          "raster/invalid-quality", "JPEG quality must be from 1 to 100",
        )
      }
      guard options.background.alpha == 255 else {
        raise raster_fail(
          "raster/jpeg-background", "JPEG requires an explicit opaque background",
        )
      }
    }
    ignore(still_dimensions(source, options.size, limits))
    rasterize_explicit(
      source,
      options,
      fonts.unwrap_or_else(FontSet::default),
      limits,
    )
  } catch {
    error => { image: None, diagnostics: [raster_diagnostic(error)], }
  }
}

///|
/// The browser Rasterizer supplies fonts explicitly, keeping default font
/// literals out of its compiled adapter through ordinary dead-code elimination.
fn rasterize_explicit(
  source : String,
  options : StillOptions,
  fonts : FontSet,
  limits : RasterLimits,
) -> RasterResult {
  try {
    validate_raster_limits(limits)
    validate_background(options.background)
    guard source.length() <= limits.max_source_units else {
      raise raster_fail(
        "raster/source-limit", "Generated SVG exceeds the source budget",
      )
    }
    if options.format is Jpeg(quality~) {
      guard quality >= 1 && quality <= 100 else {
        raise raster_fail(
          "raster/invalid-quality", "JPEG quality must be from 1 to 100",
        )
      }
      guard options.background.alpha == 255 else {
        raise raster_fail(
          "raster/jpeg-background", "JPEG requires an explicit opaque background",
        )
      }
    }
    let (width, height) = still_dimensions(source, options.size, limits)
    let pixels = render_rgba(
      source,
      width,
      height,
      options.theme,
      fonts,
      options.background,
      limits,
    )
    let (bytes, media_type, format) = match options.format {
      Png => (encode_png_pixels(pixels, limits), "image/png", RasterFormat::Png)
      Jpeg(quality~) =>
        (
          encode_jpeg_pixels(pixels, quality, limits),
          "image/jpeg",
          RasterFormat::Jpeg,
        )
    }
    {
      image: Some({ bytes, media_type, width, height, format, }),
      diagnostics: [],
    }
  } catch {
    error => { image: None, diagnostics: [raster_diagnostic(error)], }
  }
}

///|
fn render_rgba(
  source : String,
  width : Int32,
  height : Int32,
  theme : ExportTheme,
  fonts : FontSet,
  background : Rgba,
  limits : RasterLimits,
) -> RasterPixels raise RasterFailure {
  validate_raster_limits(limits)
  validate_background(background)
  ignore(checked_pixels(width, height, limits))
  guard source.length() <= limits.max_source_units else {
    raise raster_fail(
      "raster/source-limit", "Generated SVG exceeds the source budget",
    )
  }
  let parsed = load_font_set(fonts, limits)
  let normalized = normalize_generated_svg(
    source,
    match theme {
      Light => "light"
      Dark => "dark"
    },
    parsed,
    limits,
  )
  guard normalized.length() <= limits.max_source_units else {
    raise raster_fail(
      "raster/normalized-limit", "Outlined SVG exceeds the derived source budget",
    )
  }
  render_normalized_rgba(normalized, width, height, theme, background, limits)
}

///|
/// GIF can normalize the canonical scene once, then decorate the outlined
/// derived scene and use this helper for each bounded logical sample.
fn render_normalized_rgba(
  source : String,
  width : Int32,
  height : Int32,
  theme : ExportTheme,
  background : Rgba,
  limits : RasterLimits,
) -> RasterPixels raise RasterFailure {
  let pixels = checked_pixels(width, height, limits)
  guard source.length() <= limits.max_source_units else {
    raise raster_fail(
      "raster/source-limit", "Derived SVG exceeds the source budget",
    )
  }
  let environment = @backend.RenderEnvironment::default()
  let options = {
    ..@backend.RenderOptions::default(),
    environment: {
      ..environment,
      color_scheme: match theme {
        Light => Light
        Dark => Dark
      },
    },
    max_external_depth: 0,
    max_external_resources: 0,
    max_external_bytes: 0,
  }
  let rendered = @backend.render_svg(source, width, height, options)
  guard rendered.diagnostics.length() == 0 else {
    let diagnostic = rendered.diagnostics[0]
    raise raster_fail(
      "raster/backend-resource",
      "Generated SVG has an unresolved, cyclic or unsupported resource",
      source_id=diagnostic.node_id,
    )
  }
  let rgba = FixedArray::make(pixels * 4, b'\x00')
  for y in 0.. 0 {
        rgba[offset] = ((
          color.r * color.a + background.red * back_alpha + alpha / 2
        ) /
        alpha).to_byte()
        rgba[offset + 1] = ((
          color.g * color.a + background.green * back_alpha + alpha / 2
        ) /
        alpha).to_byte()
        rgba[offset + 2] = ((
          color.b * color.a + background.blue * back_alpha + alpha / 2
        ) /
        alpha).to_byte()
        rgba[offset + 3] = alpha.to_byte()
      }
    }
  }
  { width, height, data: Bytes::from_array(rgba), }
}

///|
fn raster_finite(value : Double) -> Bool {
  value == value && value.abs() <= 1.7976931348623157e308
}

///|
fn validate_raster_size(
  size : RasterSize,
  limits : RasterLimits,
) -> Unit raise RasterFailure {
  match size {
    Original => ()
    Scale(value) => {
      guard value > 0.0 && raster_finite(value) else {
        raise raster_fail(
          "raster/invalid-scale", "Scale must be finite and positive",
        )
      }
    }
    Width(value) | Height(value) => {
      guard value > 0 && value <= limits.max_dimension else {
        raise raster_fail(
          "raster/invalid-size", "Requested dimension is outside the dimension budget",
        )
      }
    }
    Fit(w, h) => {
      guard w > 0 &&
        h > 0 &&
        w <= limits.max_dimension &&
        h <= limits.max_dimension else {
        raise raster_fail(
          "raster/invalid-size", "Fit dimensions are outside the dimension budget",
        )
      }
    }
  }
}

///|
/// Integral generated dimensions admit an exact, overflow-safe ratio. The
/// largest product of two positive Int32 values fits Int64. Fractional input
/// multiplies before dividing; ratio-first avoids overflow for extreme values.
fn ratio_dimension(
  axis : Double,
  requested : Int32,
  opposite : Double,
) -> Double {
  if axis <= 2147483647.0 &&
    opposite <= 2147483647.0 &&
    axis.floor() == axis &&
    opposite.floor() == opposite {
    let numerator = axis.to_int().to_int64() * requested.to_int64()
    let denominator = opposite.to_int().to_int64()
    let extra = if numerator % denominator == 0L { 0L } else { 1L }
    let rounded = numerator / denominator + extra
    return rounded.to_double()
  }
  let product = axis * requested.to_double()
  let value = if raster_finite(product) {
    product / opposite
  } else {
    axis / opposite * requested.to_double()
  }
  value.ceil().max(1.0)
}