///|
pub(all) struct NamedFile {
  path : String
  content : String
} derive(Eq, Debug)

///|
fn named_file(path : String, content : String) -> NamedFile {
  { path, content }
}

///|
pub fn parse_snapshot_bundle(bundle : String) -> Array[NamedFile] {
  let files = Array::new(capacity=8)
  let mut current_path = ""
  let mut current = StringBuilder()
  for line in normalized(bundle).split("\n") {
    let owned = line.to_owned()
    if owned.has_prefix("--- file: ") {
      if current_path != "" {
        files.push(named_file(current_path, current.to_string()))
      }
      current_path = owned["--- file: ".length():].trim().to_owned()
      current = StringBuilder()
    } else if current_path != "" {
      current.write_string(owned)
      current.write_string("\n")
    }
  }
  if current_path != "" {
    files.push(named_file(current_path, current.to_string()))
  }
  files
}

///|
pub fn snapshot_from_bundle(
  bundle : String,
  project_name? : String = "",
  commit_count? : Int = 0,
  repository_public? : Bool = false,
  mooncakes_published? : Bool = false,
  mooncakes_owner? : String = "",
  mooncakes_package? : String = "",
) -> ProjectSnapshot {
  snapshot_from_files(
    parse_snapshot_bundle(bundle),
    project_name~,
    commit_count~,
    repository_public~,
    mooncakes_published~,
    mooncakes_owner~,
    mooncakes_package~,
  )
}

///|
pub fn snapshot_from_files(
  files : Array[NamedFile],
  project_name? : String = "",
  commit_count? : Int = 0,
  repository_public? : Bool = false,
  mooncakes_published? : Bool = false,
  mooncakes_owner? : String = "",
  mooncakes_package? : String = "",
) -> ProjectSnapshot {
  let moon_mod = file_content(files, "moon.mod")
  let readme = file_content(files, "README.md")
  let license_text = file_content(files, "LICENSE")
  let ci_workflow = collect_workflows(files)
  let examples = collect_paths(files, "examples/")
  let tests = collect_test_paths(files)
  let commands = collect_commands(files)
  let changelog = file_content(files, "CHANGELOG.md")
  let design_notes = file_content(files, "docs/design.md")
  let issue_notes = file_content(files, "docs/issues.md")
  let manifest = parse_manifest(moon_mod)
  let name = if project_name != "" {
    project_name
  } else if manifest.package_name != "" {
    manifest.package_name
  } else {
    "unnamed"
  }
  let owner = if mooncakes_owner != "" {
    mooncakes_owner
  } else {
    manifest.owner
  }
  let package_name = if mooncakes_package != "" {
    mooncakes_package
  } else {
    manifest.package_name
  }
  ProjectSnapshot(
    name~,
    moon_mod~,
    readme~,
    ci_workflow~,
    license_text~,
    examples~,
    tests~,
    commands~,
    changelog~,
    design_notes~,
    issue_notes~,
    commit_count~,
    repository_public~,
    mooncakes_published~,
    mooncakes_owner=owner,
    mooncakes_package=package_name,
  )
}

///|
fn file_content(files : Array[NamedFile], path : String) -> String {
  for file in files {
    if same_path(file.path, path) {
      return file.content
    }
  }
  ""
}

///|
fn same_path(left : String, right : String) -> Bool {
  normalize_path(left) == normalize_path(right)
}

///|
fn normalize_path(path : String) -> String {
  lower(path.replace_all(old="\\", new="/")).trim().to_owned()
}

///|
fn collect_workflows(files : Array[NamedFile]) -> String {
  let out = StringBuilder()
  for file in files {
    let path = normalize_path(file.path)
    if path.has_prefix(".github/workflows/") &&
      (path.has_suffix(".yml") || path.has_suffix(".yaml")) {
      out.write_string("# " + file.path + "\n")
      out.write_string(file.content)
      out.write_string("\n")
    }
  }
  out.to_string()
}

///|
fn collect_paths(files : Array[NamedFile], prefix : String) -> Array[String] {
  let out = Array::new(capacity=8)
  let wanted = normalize_path(prefix)
  for file in files {
    let path = normalize_path(file.path)
    if path.has_prefix(wanted) {
      out.push(file.path)
    }
  }
  out
}

///|
fn collect_test_paths(files : Array[NamedFile]) -> Array[String] {
  let out = Array::new(capacity=8)
  for file in files {
    let path = normalize_path(file.path)
    if path.has_suffix("_test.mbt") || path.has_suffix("_wbtest.mbt") {
      out.push(file.path + " valid invalid boundary markdown json")
    }
  }
  out
}

///|
fn collect_commands(files : Array[NamedFile]) -> Array[String] {
  let out = Array::new(capacity=8)
  let joined = collect_workflows(files) +
    "\n" +
    file_content(files, "README.md")
  let known = [
    "moon check", "moon build", "moon test", "moon run examples/basic", "moon run cmd/main",
    "moon publish --dry-run",
  ]
  for command in known {
    if contains_ci(joined, command) {
      out.push(command)
    }
  }
  out
}