///|
pub(all) enum Shape {
  Running(Array[Point])
  Satin(Array[Point], Array[Point], Int32)
  Fill(Array[Array[Point]], Int32)
}

///|
pub(all) struct Object {
  shape : Shape
  thread : Int32
}

///|
pub(all) enum Kind {
  Stitch
  Jump
  Color
  End
} derive(Eq)

///|
pub struct Command {
  position : Point
  kind : Kind
  thread : Int32
}

///|
pub struct Plan {
  priv moves : Array[Command]
  priv width : Int32
  priv height : Int32
  priv max_step : Int32
}

///|
pub fn Plan::commands(self : Plan) -> Array[Command] {
  self.moves.copy()
}

///|
pub fn kind_name(k : Kind) -> String {
  match k {
    Stitch => "stitch"
    Jump => "jump"
    Color => "color"
    End => "end"
  }
}

///|
/// Compiles independent objects in caller order; no automatic thread sorting.
pub fn compile(
  objects : Array[Object],
  width : Int32,
  height : Int32,
  max_step : Int32,
) -> Result[Plan, String] {
  if width < 100 ||
    height < 100 ||
    width > 10000 ||
    height > 10000 ||
    max_step < 3 ||
    max_step > 121 ||
    objects.is_empty() ||
    objects.length() > 256 {
    return Err("invalid hoop, step, or object count")
  }
  let moves : Array[Command] = []
  let mut current : Point = { x: 0, y: 0, }
  let mut thread = objects[0].thread
  for obj in objects {
    if obj.thread < 0 || obj.thread > 63 {
      return Err("thread id must be 0..63")
    }
    let spans = match obj.shape {
      Running(p) =>
        match running(p, max_step) {
          Err(e) => return Err(e)
          Ok(v) => [v]
        }
      Satin(l, r, s) =>
        match satin(l, r, s, max_step) {
          Err(e) => return Err(e)
          Ok(v) => [v]
        }
      Fill(r, s) =>
        match fill(r, s, max_step) {
          Err(e) => return Err(e)
          Ok(v) => v
        }
    }
    if obj.thread != thread {
      moves.push({ position: current, kind: Color, thread: obj.thread, })
      thread = obj.thread
    }
    for span in spans {
      for p in span {
        if p.x.abs() * 2 > width || p.y.abs() * 2 > height {
          return Err("needle position outside centered hoop")
        }
      }
      let start = span[0]
      if start != current {
        let path = match running([current, start], 121) {
          Err(e) => return Err(e)
          Ok(v) => v
        }
        for i = 1; i < path.length(); i = i + 1 {
          moves.push({ position: path[i], kind: Jump, thread, })
        }
      }
      moves.push({ position: start, kind: Stitch, thread, })
      for i = 1; i < span.length(); i = i + 1 {
        moves.push({ position: span[i], kind: Stitch, thread, })
      }
      current = span[span.length() - 1]
      if moves.length() >= 200000 {
        return Err("command limit: 200000")
      }
    }
  }
  moves.push({ position: current, kind: End, thread, })
  Ok({ moves, width, height, max_step, })
}

///|
pub fn Plan::settings(self : Plan) -> (Int32, Int32, Int32) {
  (self.width, self.height, self.max_step)
}