///|
/// 2D drawing context. Mirrors a subset of `CanvasRenderingContext2D`.
pub struct Context {
  canvas : Canvas
  mut state : DrawState
  stack : Array[DrawState]
  mut current_path : Path2D
  glyph_cache : Map[Int, @font.GlyphBitmap]
}

///|
pub fn Context::new(canvas : Canvas) -> Context {
  {
    canvas,
    state: DrawState::default(),
    stack: [],
    current_path: Path2D::new(),
    glyph_cache: {},
  }
}

///|
pub fn Context::set_fill_style(self : Context, c : Color) -> Unit {
  self.state.fill_style = FillStyle::Solid(c)
}

///|
pub fn Context::set_stroke_style(self : Context, c : Color) -> Unit {
  self.state.stroke_style = FillStyle::Solid(c)
}

///|
pub fn Context::set_fill_style_of(self : Context, style : FillStyle) -> Unit {
  self.state.fill_style = style
}

///|
pub fn Context::set_stroke_style_of(self : Context, style : FillStyle) -> Unit {
  self.state.stroke_style = style
}

///|
pub fn Context::set_line_width(self : Context, w : Double) -> Unit {
  self.state.line_width = w
}

///|
pub fn Context::set_global_alpha(self : Context, a : Double) -> Unit {
  self.state.global_alpha = a
}

///|
pub fn Context::set_font(
  self : Context,
  font : @font.TTFont,
  size_px : Double,
) -> Unit {
  self.glyph_cache.clear()
  self.state.font = Some(font)
  self.state.font_size = size_px
}

///|
pub fn Context::set_text_align(self : Context, align : TextAlign) -> Unit {
  self.state.text_align = align
}

///|
pub fn Context::set_text_baseline(
  self : Context,
  baseline : TextBaseline,
) -> Unit {
  self.state.text_baseline = baseline
}

///|
/// Push the current draw state onto the save stack.
pub fn Context::save(self : Context) -> Unit {
  self.stack.push(self.state.snapshot())
}

///|
/// Pop the last saved state off the stack. A `restore` with no prior `save`
/// is a no-op (matches HTML Canvas semantics).
pub fn Context::restore(self : Context) -> Unit {
  match self.stack.pop() {
    Some(prev) => self.state = prev
    None => ()
  }
}

///|
pub fn Context::translate(self : Context, x : Double, y : Double) -> Unit {
  self.state.transform = self.state.transform.translate(x, y)
}

///|
pub fn Context::scale(self : Context, sx : Double, sy : Double) -> Unit {
  self.state.transform = self.state.transform.scale(sx, sy)
}

///|
pub fn Context::rotate(self : Context, angle : Double) -> Unit {
  self.state.transform = self.state.transform.rotate(angle)
}

///|
pub fn Context::transform(
  self : Context,
  a : Double,
  b : Double,
  c : Double,
  d : Double,
  e : Double,
  f : Double,
) -> Unit {
  self.state.transform = self.state.transform.multiply(
    Matrix2D::of(a, b, c, d, e, f),
  )
}

///|
pub fn Context::set_transform(
  self : Context,
  a : Double,
  b : Double,
  c : Double,
  d : Double,
  e : Double,
  f : Double,
) -> Unit {
  self.state.transform = Matrix2D::of(a, b, c, d, e, f)
}

///|
pub fn Context::reset_transform(self : Context) -> Unit {
  self.state.transform = Matrix2D::identity()
}

///|
pub fn Context::fill_path(self : Context, path : Path2D) -> Unit {
  let polylines = flatten(path.commands, self.state.transform, 0.25)
  fill_polylines(
    self.canvas.pixels,
    self.canvas.width,
    self.canvas.height,
    polylines,
    self.state.fill_style,
    self.state.transform,
    self.state.clip,
    self.state.global_alpha,
    self.canvas.antialias,
  )
}

///|
pub fn Context::stroke_path(self : Context, path : Path2D) -> Unit {
  let polylines = flatten(path.commands, self.state.transform, 0.25)
  let effective = if is_valid_dash_pattern(self.state.line_dash) {
    dash_polylines(polylines, self.state.line_dash, self.state.line_dash_offset)
  } else {
    polylines
  }
  stroke_polylines(
    self.canvas.pixels,
    self.canvas.width,
    self.canvas.height,
    effective,
    self.state.stroke_style,
    self.state.line_width,
    self.state.line_cap,
    self.state.line_join,
    self.state.transform,
    self.state.clip,
    self.state.global_alpha,
    self.canvas.antialias,
  )
}

///|
pub fn Context::fill_rect(
  self : Context,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
) -> Unit {
  let path = Path2D::new()
  path.rect(x, y, w, h)
  self.fill_path(path)
}

///|
pub fn Context::stroke_rect(
  self : Context,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
) -> Unit {
  let path = Path2D::new()
  path.rect(x, y, w, h)
  self.stroke_path(path)
}

///|
pub fn Context::begin_path(self : Context) -> Unit {
  self.current_path = Path2D::new()
}

///|
pub fn Context::close_path(self : Context) -> Unit {
  self.current_path.close()
}

///|
pub fn Context::move_to(self : Context, x : Double, y : Double) -> Unit {
  self.current_path.move_to(x, y)
}

///|
pub fn Context::line_to(self : Context, x : Double, y : Double) -> Unit {
  self.current_path.line_to(x, y)
}

///|
pub fn Context::quadratic_curve_to(
  self : Context,
  cpx : Double,
  cpy : Double,
  x : Double,
  y : Double,
) -> Unit {
  self.current_path.quadratic_curve_to(cpx, cpy, x, y)
}

///|
pub fn Context::bezier_curve_to(
  self : Context,
  cp1x : Double,
  cp1y : Double,
  cp2x : Double,
  cp2y : Double,
  x : Double,
  y : Double,
) -> Unit {
  self.current_path.bezier_curve_to(cp1x, cp1y, cp2x, cp2y, x, y)
}

///|
pub fn Context::arc(
  self : Context,
  x : Double,
  y : Double,
  radius : Double,
  start_angle : Double,
  end_angle : Double,
  counterclockwise? : Bool = false,
) -> Unit {
  self.current_path.arc(x, y, radius, start_angle, end_angle, counterclockwise~)
}

///|
pub fn Context::rect(
  self : Context,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
) -> Unit {
  self.current_path.rect(x, y, w, h)
}

///|
/// Fills the current_path (built by begin_path / move_to / line_to / ...).
pub fn Context::fill(self : Context) -> Unit {
  self.fill_path(self.current_path)
}

///|
/// Strokes the current_path (built by begin_path / move_to / line_to / ...).
pub fn Context::stroke(self : Context) -> Unit {
  self.stroke_path(self.current_path)
}

///|
pub fn Context::clear_rect(
  self : Context,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
) -> Unit {
  let cmds : Array[PathCmd] = [
    PathCmd::MoveTo(x, y),
    PathCmd::LineTo(x + w, y),
    PathCmd::LineTo(x + w, y + h),
    PathCmd::LineTo(x, y + h),
    PathCmd::Close,
  ]
  let polylines = flatten(cmds, self.state.transform, 0.25)
  clear_polylines(
    self.canvas.pixels,
    self.canvas.width,
    self.canvas.height,
    polylines,
  )
}

///|
/// Draw an image onto the canvas at `(dx, dy)`.
///
/// - `scale` optionally rescales the destination rect (defaults to `(1, 1)`).
/// - `src_rect` optionally selects a sub-region of the source (defaults to
///   the whole image).
/// - `smoothing` toggles bilinear (`true`) vs nearest-neighbor (`false`)
///   sampling. Defaults to `true` to mirror the HTML Canvas default.
pub fn Context::draw_image(
  self : Context,
  image : @image.ImageData,
  dx : Double,
  dy : Double,
  scale? : (Double, Double)? = None,
  src_rect? : (Double, Double, Double, Double)? = None,
  smoothing? : Bool = true,
) -> Unit {
  let (sx, sy, sw, sh) = match src_rect {
    Some((a, b, c, d)) => (a, b, c, d)
    None => (0.0, 0.0, image.width.to_double(), image.height.to_double())
  }
  let (scale_x, scale_y) = match scale {
    Some((a, b)) => (a, b)
    None => (1.0, 1.0)
  }
  draw_image_raw(
    self.canvas.pixels,
    self.canvas.width,
    self.canvas.height,
    image,
    dx,
    dy,
    scale_x,
    scale_y,
    sx,
    sy,
    sw,
    sh,
    self.state.transform,
    self.state.global_alpha,
    smoothing,
  )
}

///|
/// Intersects the current path with the active clip region. Subsequent
/// fills and strokes are masked to the intersection. Matches HTML Canvas
/// `ctx.clip()` semantics (non-zero winding, intersection-only).
///
/// On first use, allocates a zeroed ClipMask sized to the canvas framebuffer
/// and rasterizes the current path into it. On subsequent calls, rasterizes
/// the path into a temporary mask and multiplies it into the existing mask.
/// Uses the canvas's `antialias` setting for the clip rasterization.
pub fn Context::clip(self : Context) -> Unit {
  let polylines = flatten(
    self.current_path.commands,
    self.state.transform,
    0.25,
  )
  let aa = self.canvas.antialias
  match self.state.clip {
    None => {
      let mask = ClipMask::new(self.canvas.width, self.canvas.height)
      for i in 0.. {
      let tmp = ClipMask::new(self.canvas.width, self.canvas.height)
      for i in 0.. Unit {
  self.state.line_dash_offset = offset
}

///|
/// Set the dash pattern for subsequent strokes. An empty array produces a
/// solid line. If the array length is odd, it is internally doubled (e.g.
/// `[5, 10, 15]` becomes `[5, 10, 15, 5, 10, 15]`). Negative, NaN, or
/// infinite values are stored as-is and cause the pattern to be silently
/// treated as solid at stroke time. Matches HTML Canvas `setLineDash`.
pub fn Context::set_line_dash(self : Context, segments : Array[Double]) -> Unit {
  let n = segments.length()
  if n == 0 {
    self.state.line_dash = []
    return
  }
  let len = if n % 2 == 0 { n } else { n * 2 }
  let copy : Array[Double] = []
  for i in 0.. Unit {
  self.state.line_cap = cap
}

///|
/// Set the line join shape for subsequent strokes. Matches HTML Canvas
/// `ctx.lineJoin`. `Miter` preserves the existing automatic bevel
/// fallback at the fixed 10.0 miter limit.
pub fn Context::set_line_join(self : Context, join : LineJoin) -> Unit {
  self.state.line_join = join
}

///|
pub fn Context::ellipse(
  self : Context,
  x : Double,
  y : Double,
  rx : Double,
  ry : Double,
  rotation : Double,
  start_angle : Double,
  end_angle : Double,
  counterclockwise? : Bool = false,
) -> Unit {
  self.current_path.ellipse(
    x,
    y,
    rx,
    ry,
    rotation,
    start_angle,
    end_angle,
    counterclockwise~,
  )
}

///|
pub fn Context::arc_to(
  self : Context,
  x1 : Double,
  y1 : Double,
  x2 : Double,
  y2 : Double,
  radius : Double,
) -> Unit {
  self.current_path.arc_to(x1, y1, x2, y2, radius)
}

///|
pub fn Context::round_rect(
  self : Context,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
  radii : Array[Double],
) -> Unit {
  self.current_path.round_rect(x, y, w, h, radii)
}

///|
/// Convenience for a rounded rect with all four corners at the same radius.
pub fn Context::round_rect_uniform(
  self : Context,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
  r : Double,
) -> Unit {
  self.current_path.round_rect(x, y, w, h, [r])
}