///|
struct Canvas {
  width : Int
  height : Int
  cells : FixedArray[Char]
} derive(Show, Eq)

///|
fn Canvas::new(width : Int, height : Int) -> Canvas {
  { width, height, cells: FixedArray::make(width * height, ' ') }
}

///|
fn Canvas::set(self : Canvas, x : Int, y : Int, ch : Char) -> Unit {
  if x >= 0 && y >= 0 && x < self.width && y < self.height {
    self.cells[y * self.width + x] = ch
  }
}

///|
let dir_up : Int = 1

///|
let dir_down : Int = 2

///|
let dir_left : Int = 4

///|
let dir_right : Int = 8

///|
fn line_directions(ch : Char) -> Int {
  match ch {
    '─' | '-' => dir_left | dir_right
    '│' | '|' => dir_up | dir_down
    '┌' => dir_right | dir_down
    '┐' => dir_left | dir_down
    '└' => dir_right | dir_up
    '┘' => dir_left | dir_up
    '├' => dir_up | dir_down | dir_right
    '┤' => dir_up | dir_down | dir_left
    '┬' => dir_left | dir_right | dir_down
    '┴' => dir_left | dir_right | dir_up
    '┼' | '+' => dir_up | dir_down | dir_left | dir_right
    _ => 0
  }
}

// Lookup table: index = direction bitmask (0-15)
// Bits: up=1, down=2, left=4, right=8

///|
let line_lut_unicode : FixedArray[Char] = [
  ' ', '│', '│', '│', '─', '┘', '┐', '┤', '─', '└', '┌', '├',
  '─', '┴', '┬', '┼',
]

///|
let line_lut_ascii : FixedArray[Char] = [
  ' ', '|', '|', '|', '-', '+', '+', '+', '-', '+', '+', '+', '-', '+', '+', '+',
]

///|
fn resolve_line_char(dirs : Int, use_ascii : Bool) -> Char {
  let idx = dirs & 0xF
  if use_ascii {
    line_lut_ascii[idx]
  } else {
    line_lut_unicode[idx]
  }
}

///|
fn Canvas::set_line(
  self : Canvas,
  x : Int,
  y : Int,
  ch : Char,
  use_ascii : Bool,
) -> Unit {
  if x < 0 || y < 0 || x >= self.width || y >= self.height {
    return
  }
  let idx = y * self.width + x
  let old = self.cells[idx]
  let old_dirs = line_directions(old)
  let new_dirs = line_directions(ch)
  if old_dirs != 0 && new_dirs != 0 {
    self.cells[idx] = resolve_line_char(old_dirs | new_dirs, use_ascii)
  } else {
    self.cells[idx] = ch
  }
}

///|
fn Canvas::draw_text(self : Canvas, x : Int, y : Int, text : String) -> Unit {
  let mut cx = x
  for c in text {
    self.set(cx, y, c)
    cx += 1
  }
}

///|
fn box_chars(use_ascii : Bool) -> (Char, Char, Char, Char, Char, Char) {
  if use_ascii {
    ('+', '+', '+', '+', '-', '|')
  } else {
    ('┌', '┐', '└', '┘', '─', '│')
  }
}

///|
fn rounded_box_chars(use_ascii : Bool) -> (Char, Char, Char, Char, Char, Char) {
  if use_ascii {
    ('+', '+', '+', '+', '-', '|')
  } else {
    ('╭', '╮', '╰', '╯', '─', '│')
  }
}

///|
fn Canvas::draw_box(
  self : Canvas,
  x : Int,
  y : Int,
  w : Int,
  h : Int,
  use_ascii : Bool,
) -> Unit {
  if w <= 1 || h <= 1 {
    return
  }
  let (tl, tr, bl, br, hch, vch) = box_chars(use_ascii)
  self.set(x, y, tl)
  self.set(x + w - 1, y, tr)
  self.set(x, y + h - 1, bl)
  self.set(x + w - 1, y + h - 1, br)
  for i in (x + 1)..<(x + w - 1) {
    self.set(i, y, hch)
    self.set(i, y + h - 1, hch)
  }
  for j in (y + 1)..<(y + h - 1) {
    self.set(x, j, vch)
    self.set(x + w - 1, j, vch)
  }
}

///|
fn Canvas::draw_rounded_box(
  self : Canvas,
  x : Int,
  y : Int,
  w : Int,
  h : Int,
  use_ascii : Bool,
) -> Unit {
  if w <= 1 || h <= 1 {
    return
  }
  let (tl, tr, bl, br, hch, vch) = rounded_box_chars(use_ascii)
  self.set(x, y, tl)
  self.set(x + w - 1, y, tr)
  self.set(x, y + h - 1, bl)
  self.set(x + w - 1, y + h - 1, br)
  for i in (x + 1)..<(x + w - 1) {
    self.set(i, y, hch)
    self.set(i, y + h - 1, hch)
  }
  for j in (y + 1)..<(y + h - 1) {
    self.set(x, j, vch)
    self.set(x + w - 1, j, vch)
  }
}

///|
fn Canvas::draw_hline(
  self : Canvas,
  x1 : Int,
  x2 : Int,
  y : Int,
  use_ascii : Bool,
) -> Unit {
  let (_, _, _, _, hch, _) = box_chars(use_ascii)
  let (start, end) = if x1 <= x2 { (x1, x2) } else { (x2, x1) }
  for x in start..<=end {
    self.set(x, y, hch)
  }
}

///|
fn Canvas::draw_vline(
  self : Canvas,
  x : Int,
  y1 : Int,
  y2 : Int,
  use_ascii : Bool,
) -> Unit {
  let (_, _, _, _, _, vch) = box_chars(use_ascii)
  let (start, end) = if y1 <= y2 { (y1, y2) } else { (y2, y1) }
  for y in start..<=end {
    self.set(x, y, vch)
  }
}

///|
fn arrow_char(dx : Int, dy : Int, use_ascii : Bool) -> Char {
  if use_ascii {
    if dx > 0 {
      '>'
    } else if dx < 0 {
      '<'
    } else if dy > 0 {
      'v'
    } else {
      '^'
    }
  } else if dx > 0 {
    '▶'
  } else if dx < 0 {
    '◀'
  } else if dy > 0 {
    '▼'
  } else {
    '▲'
  }
}

///|
fn corner_char(dx : Int, dy : Int, use_ascii : Bool) -> Char {
  if use_ascii {
    '+'
  } else if dx > 0 && dy > 0 {
    '┐'
  } else if dx > 0 && dy < 0 {
    '┘'
  } else if dx < 0 && dy > 0 {
    '┌'
  } else {
    '└'
  }
}

///|
fn Canvas::draw_edge_line(
  self : Canvas,
  start : Point,
  end_ : Point,
  use_ascii : Bool,
) -> Unit {
  let (_, _, _, _, hch, vch) = box_chars(use_ascii)
  let dx = end_.x - start.x
  let dy = end_.y - start.y
  if start.y == end_.y {
    let (s, e) = if dx > 0 { (start.x, end_.x) } else { (end_.x, start.x) }
    for x in s..<=e {
      self.set_line(x, start.y, hch, use_ascii)
    }
    self.set(end_.x, end_.y, arrow_char(dx, dy, use_ascii))
  } else if start.x == end_.x {
    let (s, e) = if dy > 0 { (start.y, end_.y) } else { (end_.y, start.y) }
    for y in s..<=e {
      self.set_line(start.x, y, vch, use_ascii)
    }
    self.set(end_.x, end_.y, arrow_char(dx, dy, use_ascii))
  } else {
    // L-shaped: horizontal then vertical
    // Horizontal segment (excluding bend and start points)
    let (hs, he) = if dx > 0 {
      (start.x + 1, end_.x - 1)
    } else {
      (end_.x + 1, start.x - 1)
    }
    for x in hs..<=he {
      self.set_line(x, start.y, hch, use_ascii)
    }
    // Vertical segment (excluding bend point)
    let (vs, ve) = if dy > 0 {
      (start.y + 1, end_.y)
    } else {
      (end_.y, start.y - 1)
    }
    for y in vs..<=ve {
      self.set_line(end_.x, y, vch, use_ascii)
    }
    // Corner at bend point (merging with other edges)
    self.set_line(end_.x, start.y, corner_char(dx, dy, use_ascii), use_ascii)
    // Arrow points in last segment direction (vertical)
    self.set(end_.x, end_.y, arrow_char(0, dy, use_ascii))
  }
}

///|
fn Canvas::draw_state_marker(
  self : Canvas,
  box : LayoutBox,
  use_ascii : Bool,
  filled : Bool,
) -> Unit {
  let cx = box.x + box.width / 2
  let cy = box.y + box.height / 2
  let ch = if use_ascii {
    if filled {
      'o'
    } else {
      'O'
    }
  } else if filled {
    '●'
  } else {
    '◎'
  }
  self.set(cx, cy, ch)
}

///|
fn Canvas::render(self : Canvas) -> String {
  // Find last row with non-space content
  let mut last_row = self.height - 1
  while last_row >= 0 {
    let row_start = last_row * self.width
    let mut has_content = false
    for x in 0.. 0 {
      sb.write_char('\n')
    }
    let row_start = y * self.width
    // Find last non-space in this row
    let mut last_col = -1
    for x = self.width - 1; x >= 0; x = x - 1 {
      if self.cells[row_start + x] != ' ' {
        last_col = x
        break
      }
    }
    for x in 0..<=last_col {
      sb.write_char(self.cells[row_start + x])
    }
  }
  sb.to_string()
}

///|
fn render_graph_canvas(diagram : GraphDiagram, options : Options) -> Canvas {
  let layout = layout_graph(diagram, options)
  let canvas = Canvas::new(layout.width + 2, layout.height + 2)
  // Pass 1: draw all edge lines
  for edge in layout.edges {
    canvas.draw_edge_line(edge.start, edge.end, options.use_ascii)
  }
  // Pass 1.5: add start connectors for L-shaped edges
  for edge in layout.edges {
    let dx = edge.end.x - edge.start.x
    let dy = edge.end.y - edge.start.y
    if dx != 0 && dy != 0 {
      let box_dir = match layout.direction {
        Direction::TD => dir_up
        Direction::BT => dir_down
        Direction::LR => dir_left
        Direction::RL => dir_right
      }
      let h_dir = if dx > 0 { dir_right } else { dir_left }
      canvas.set_line(
        edge.start.x,
        edge.start.y,
        resolve_line_char(box_dir | h_dir, options.use_ascii),
        options.use_ascii,
      )
    }
  }
  // Pass 2: draw edge labels on top of lines
  for edge in layout.edges {
    match edge.edge.label {
      Some(label) => {
        let lx = (edge.start.x + edge.end.x) / 2
        let ly = if edge.start.x != edge.end.x && edge.start.y != edge.end.y {
          edge.start.y
        } else {
          (edge.start.y + edge.end.y) / 2 - 1
        }
        canvas.draw_text(lx, ly, label)
      }
      None => ()
    }
  }
  for node in layout.nodes {
    let b = node.box
    match node.node.shape {
      NodeShape::StateStart =>
        canvas.draw_state_marker(b, options.use_ascii, true)
      NodeShape::StateEnd =>
        canvas.draw_state_marker(b, options.use_ascii, false)
      _ => {
        let draw_fn = match node.node.shape {
          NodeShape::Rounded => Canvas::draw_rounded_box
          _ => Canvas::draw_box
        }
        draw_fn(canvas, b.x, b.y, b.width, b.height, options.use_ascii)
        let mut y = b.y + options.padding_y
        let mut section_index = 0
        for section in node.node.sections {
          for line in section {
            canvas.draw_text(b.x + options.padding_x, y, line)
            y += 1
          }
          if section_index < node.node.sections.length() - 1 {
            canvas.draw_hline(b.x + 1, b.x + b.width - 2, y, options.use_ascii)
            y += 1
          }
          section_index += 1
        }
      }
    }
  }
  canvas
}

///|
fn render_sequence_canvas(
  diagram : SequenceDiagram,
  options : Options,
) -> Canvas {
  let layout = layout_sequence(diagram, options)
  let canvas = Canvas::new(layout.width + 2, layout.height + 2)
  for actor in layout.actors {
    let b = actor.box
    canvas.draw_box(b.x, b.y, b.width, b.height, options.use_ascii)
    canvas.draw_text(
      b.x + options.padding_x,
      b.y + options.padding_y,
      actor.actor.label,
    )
    let cx = b.x + b.width / 2
    canvas.draw_vline(cx, b.y + b.height, layout.height - 1, options.use_ascii)
  }
  for msg in layout.messages {
    let dx = msg.end.x - msg.start.x
    canvas.draw_hline(msg.start.x, msg.end.x, msg.y, options.use_ascii)
    canvas.set(msg.end.x, msg.end.y, arrow_char(dx, 0, options.use_ascii))
    if msg.message.text.length() > 0 {
      canvas.draw_text(msg.start.x + 1, msg.y - 1, msg.message.text)
    }
  }
  canvas
}

///|
fn render_graph_ascii(diagram : GraphDiagram, options : Options) -> String {
  render_graph_canvas(diagram, options).render()
}

///|
fn render_sequence_ascii(
  diagram : SequenceDiagram,
  options : Options,
) -> String {
  render_sequence_canvas(diagram, options).render()
}