///|
#callsite(autofill(loc, argsloc))
pub fn TestBlock::inspect(
  self : TestBlock,
  actual : &Show,
  expected? : String = "",
  loc~ : SourceLoc,
  argsloc~ : ArgsLoc,
) -> Unit {
  let actual = actual.to_string()
  if actual != expected {
    self.0.push(
      Failure(
        actual~,
        expected~,
        loc=try! @quote.SourceLocation::parse(loc.to_string()),
        argsloc=argsloc.0.map(lo => lo.map(l => try! @quote.SourceLocation::parse(
            l.to_string(),
          ))),
      ),
    )
  } else {
    self.0.push(Success)
  }
}

///|
#callsite(autofill(loc))
pub fn TestBlock::define(
  test_block : (TestBlock) -> Unit,
  loc~ : SourceLoc,
) -> Unit {
  let b = TestBlock::begin(loc~)
  test_block(b)
  b.end(loc~)
}

///|
#calsite(autofill(loc))
fn TestBlock::begin(loc~ : SourceLoc) -> TestBlock {
  let loc = try! @quote.SourceLocation::parse(loc.to_string())
  let file_path = loc.path.to_string()
  let file = try! @fs.read_file_to_string(file_path.to_string())
  match test_manager.0.get(file_path) {
    None =>
      test_manager.0[file_path] = TestFileManager::{
        file,
        count: moonbit_test_block_count(file),
        test_blocks: [],
        current_test_block_index: 1,
      }
    Some(manager) => manager.current_test_block_index += 1
  }
  []
}

///|
#callsite(autofill(loc))
fn TestBlock::end(self : TestBlock, loc~ : SourceLoc) -> Unit {
  if is_inspect_update {
    let loc = try! @quote.SourceLocation::parse(loc.to_string())
    let file_path = loc.path.to_string()
    let file_manager = test_manager.0[file_path]
    file_manager.test_blocks.push(self)
    if file_manager.test_blocks.length() == file_manager.count {
      file_manager.update_source_file()
    }
  } else {
    self.check_source_file()
  }
}

///|
fn[A, B] coerce(x : A) -> B = "%identity"

///|
fn TestBlock::check_source_file(self : TestBlock) -> Unit {
  for state in self.0 {
    guard state is Failure(actual~, expected~, loc~) else { continue }
    print_error_message(
      (
        $| test fail in \{loc.path}:\{loc.start.row}:\{loc.start.column}-\{loc.end.row}:\{loc.end.column}
        $| 
        $| expected: \{expected.escape()}
        $|
        $| actual: \{actual.escape()}
      ),
    )
  }
}

///|
fn moonbit_test_block_count(file : String) -> Int {
  let mut count = 0
  let lines = file.split("\n")
  for line in lines {
    // Find the start of non-whitespace content
    let mut start_idx = 0
    while start_idx < line.length() {
      let ch = line.get_char(start_idx)
      match ch {
        Some(' ') | Some('\t') => start_idx += 1
        _ => break
      }
    }

    // Check if line starts with "test" (after whitespace)
    if start_idx + 4 <= line.length() {
      let mut matches_test = true
      let test_word = "test"
      for i = 0; i < 4; i = i + 1 {
        if line.get_char(start_idx + i) != test_word.get_char(i) {
          matches_test = false
          break
        }
      }
      if matches_test {
        // Check what follows "test"
        let after_test_idx = start_idx + 4
        if after_test_idx >= line.length() {
          continue // "test" at end of line, likely not a test block
        }
        let next_char = line.get_char(after_test_idx)
        match next_char {
          Some(' ') | Some('\t') | Some('"') | Some('{') =>
            // Verify this is actually a test block by checking for opening brace
            if line.contains("{") {
              count += 1
            }
          _ => continue
        }
      }
    }
  }
  count
}