///|
fn artifact_parent_dir(path : String) -> String {
  let parts : Array[String] = []
  for part in path.split("/") {
    parts.push(part.to_owned())
  }
  if parts.length() <= 1 {
    return "."
  }
  let dirs : Array[String] = []
  for idx in 0..<(parts.length() - 1) {
    let part = parts[idx]
    if part.length() > 0 {
      dirs.push(part)
    }
  }
  let joined = dirs.join("/")
  if joined.length() > 0 {
    if path.has_prefix("/") {
      "/" + joined
    } else {
      joined
    }
  } else if path.has_prefix("/") {
    "/"
  } else {
    "."
  }
}

///|
fn parse_artifact_if_no_files_found(value : String) -> String? {
  let normalized = value.trim(chars=" \t\r\n").to_lower()
  if normalized.length() == 0 || normalized == "warn" {
    Some("warn")
  } else if normalized == "ignore" {
    Some("ignore")
  } else if normalized == "error" {
    Some("error")
  } else {
    None
  }
}

///|
fn artifact_store_root(workspace_root : String) -> String {
  let configured = @xsys.get_env_var("ACTRUN_ARTIFACT_ROOT").unwrap_or(
    "_build/actrun/artifacts",
  )
  ignore(ensure_exec_dir_recursive(configured))
  let workspace_key = sanitize_task_id(
    absolute_exec_path(resolve_task_cwd(workspace_root, "")),
  )
  let root = absolute_exec_path(configured + "/" + workspace_key)
  ignore(ensure_exec_dir_recursive(root))
  root
}

///|
priv struct ArtifactUploadSelection {
  root_paths : Array[String]
  file_paths : Array[String]
}

///|
fn artifact_has_wildcard(pattern : String) -> Bool {
  pattern.contains("*") || pattern.contains("?")
}

///|
fn artifact_normalize_pattern(pattern : String) -> String {
  let trimmed = pattern.trim(chars=" \t\r\n").to_owned()
  let without_prefix = if trimmed.has_prefix("./") {
    exec_text_slice(trimmed, 2, trimmed.length())
  } else if trimmed.has_prefix("/") {
    exec_text_slice(trimmed, 1, trimmed.length())
  } else {
    trimmed
  }
  if without_prefix.has_suffix("/") {
    exec_text_slice(without_prefix, 0, without_prefix.length() - 1)
  } else {
    without_prefix
  }
}

///|
fn artifact_path_join(base : String, child : String) -> String {
  if child.has_prefix("/") {
    return child
  }
  if base.length() == 0 || base == "." {
    child
  } else if base.has_suffix("/") {
    base + child
  } else {
    base + "/" + child
  }
}

///|
fn artifact_canonical_path(path : String) -> String {
  let absolute = absolute_exec_path(path)
  if absolute.length() > 1 && absolute.has_suffix("/") {
    exec_text_slice(absolute, 0, absolute.length() - 1)
  } else {
    absolute
  }
}

///|
fn artifact_split_path(path : String) -> Array[String] {
  let out : Array[String] = []
  if path.length() == 0 {
    return out
  }
  for part_view in path.split("/") {
    let part = part_view.to_owned()
    if part.length() > 0 {
      out.push(part)
    }
  }
  out
}

///|
fn artifact_match_segment(pattern : String, text : String) -> Bool {
  let mut pi = 0
  let mut si = 0
  let mut star = -1
  let mut mark = 0
  while si < text.length() {
    if pi < pattern.length() &&
      (
        pattern.unsafe_get(pi) == '?'.to_int().to_uint16() ||
        pattern.unsafe_get(pi) == text.unsafe_get(si)
      ) {
      pi += 1
      si += 1
    } else if pi < pattern.length() &&
      pattern.unsafe_get(pi) == '*'.to_int().to_uint16() {
      star = pi
      mark = si
      pi += 1
    } else if star != -1 {
      pi = star + 1
      mark += 1
      si = mark
    } else {
      return false
    }
  }
  while pi < pattern.length() &&
        pattern.unsafe_get(pi) == '*'.to_int().to_uint16() {
    pi += 1
  }
  pi == pattern.length()
}

///|
fn artifact_match_path_segments(
  patterns : Array[String],
  pattern_index : Int,
  segments : Array[String],
  segment_index : Int,
) -> Bool {
  if pattern_index >= patterns.length() {
    return segment_index >= segments.length()
  }
  let pattern = patterns[pattern_index]
  if pattern == "**" {
    let mut next_index = segment_index
    while true {
      if artifact_match_path_segments(
          patterns,
          pattern_index + 1,
          segments,
          next_index,
        ) {
        return true
      }
      if next_index >= segments.length() {
        break
      }
      next_index += 1
    }
    false
  } else {
    if segment_index >= segments.length() {
      return false
    }
    if artifact_match_segment(pattern, segments[segment_index]) {
      artifact_match_path_segments(
        patterns,
        pattern_index + 1,
        segments,
        segment_index + 1,
      )
    } else {
      false
    }
  }
}

///|
fn artifact_path_matches(pattern : String, relative_path : String) -> Bool {
  let normalized = artifact_normalize_pattern(pattern)
  if normalized.length() == 0 {
    return false
  }
  artifact_match_path_segments(
    artifact_split_path(normalized),
    0,
    artifact_split_path(relative_path),
    0,
  )
}

///|
fn artifact_collect_relative_files(
  current_abs : String,
  relative_prefix : String,
  files : Array[String],
  include_hidden? : Bool = false,
) -> Unit {
  let entries = try @xfs.read_dir(current_abs) catch {
    _ => []
  } noraise {
    value => value
  }
  entries.sort_by(String::lexical_compare)
  for entry in entries {
    if !include_hidden && entry.has_prefix(".") {
      continue
    }
    let next_abs = artifact_path_join(current_abs, entry)
    let next_relative = if relative_prefix.length() == 0 {
      entry
    } else {
      relative_prefix + "/" + entry
    }
    if exec_is_dir(next_abs) {
      artifact_collect_relative_files(
        next_abs,
        next_relative,
        files,
        include_hidden~,
      )
    } else {
      files.push(next_relative)
    }
  }
}

///|
fn artifact_collect_absolute_files(
  current_abs : String,
  files : Array[String],
) -> Unit {
  let entries = try @xfs.read_dir(current_abs) catch {
    _ => []
  } noraise {
    value => value
  }
  entries.sort_by(String::lexical_compare)
  for entry in entries {
    let next_abs = artifact_path_join(current_abs, entry)
    if exec_is_dir(next_abs) {
      artifact_collect_absolute_files(next_abs, files)
    } else {
      files.push(next_abs)
    }
  }
}

///|
fn artifact_workspace_relative_files(
  workspace_root : String,
  include_hidden? : Bool = false,
) -> Array[String] {
  let workspace_abs = absolute_exec_path(resolve_task_cwd(workspace_root, ""))
  let files : Array[String] = []
  if !@xfs.path_exists(workspace_abs) {
    return files
  }
  artifact_collect_relative_files(workspace_abs, "", files, include_hidden~)
  files
}

///|
fn artifact_glob_root(pattern : String) -> String {
  let stable_segments : Array[String] = []
  for segment in artifact_split_path(artifact_normalize_pattern(pattern)) {
    if artifact_has_wildcard(segment) {
      break
    }
    stable_segments.push(segment)
  }
  stable_segments.join("/")
}

///|
fn artifact_is_prefix_dir(path : String, prefix : String) -> Bool {
  if path == prefix {
    return true
  }
  if prefix == "/" {
    return path.has_prefix("/")
  }
  path.has_prefix(prefix + "/")
}

///|
fn artifact_common_ancestor(paths : Array[String]) -> String {
  if paths.length() == 0 {
    return absolute_exec_path(".")
  }
  let mut current = artifact_canonical_path(paths[0])
  let mut found = false
  while !found {
    let mut all_contained = true
    for path in paths {
      if !artifact_is_prefix_dir(artifact_canonical_path(path), current) {
        all_contained = false
        break
      }
    }
    if all_contained {
      found = true
    } else {
      let parent = artifact_parent_dir(current)
      if parent == current {
        found = true
      } else {
        current = artifact_canonical_path(parent)
      }
    }
  }
  current
}

///|
fn artifact_select_upload_files(
  workspace_root : String,
  upload_paths : Array[String],
  include_hidden? : Bool = false,
) -> ArtifactUploadSelection {
  let included_roots : Array[String] = []
  let included_files : Map[String, Bool] = {}
  let excluded_patterns : Array[String] = []
  let workspace_files = artifact_workspace_relative_files(
    workspace_root,
    include_hidden~,
  )
  for raw_path in upload_paths {
    let path = raw_path.trim(chars=" \t\r\n").to_owned()
    if path.has_prefix("!") {
      excluded_patterns.push(exec_text_slice(path, 1, path.length()))
      continue
    }
    if artifact_has_wildcard(path) {
      let root_relative = artifact_glob_root(path)
      let root_abs = artifact_canonical_path(
        resolve_task_cwd(workspace_root, root_relative),
      )
      if @xfs.path_exists(root_abs) {
        included_roots.push(root_abs)
      }
      for relative_path in workspace_files {
        if artifact_path_matches(path, relative_path) {
          included_files[absolute_exec_path(
            resolve_task_cwd(workspace_root, relative_path),
          )] = true
        }
      }
      continue
    }
    // Reject paths that escape the workspace
    guard validate_workspace_path(workspace_root, path) is Some(_) else {
      continue
    }
    let resolved_path = resolve_task_cwd(workspace_root, path)
    if !@xfs.path_exists(resolved_path) {
      continue
    }
    let absolute_path = artifact_canonical_path(resolved_path)
    if exec_is_dir(absolute_path) {
      included_roots.push(absolute_path)
      let child_files : Array[String] = []
      artifact_collect_absolute_files(absolute_path, child_files)
      for child in child_files {
        included_files[child] = true
      }
    } else {
      let root = artifact_canonical_path(artifact_parent_dir(absolute_path))
      included_roots.push(root)
      included_files[absolute_path] = true
    }
  }
  let selected_files : Array[String] = []
  for absolute_path, _ in included_files {
    let mut excluded = false
    for pattern in excluded_patterns {
      guard workspace_relative_store_path(workspace_root, absolute_path)
        is Some(relative_path) else {
        continue
      }
      if artifact_path_matches(pattern, relative_path) {
        excluded = true
        break
      }
    }
    if !excluded {
      selected_files.push(absolute_path)
    }
  }
  selected_files.sort_by(String::lexical_compare)
  { root_paths: included_roots, file_paths: selected_files }
}

///|
fn execute_upload_artifact_native(
  plan : TaskPlan,
  workspace_root : String,
  resolved_plan_env : Map[String, String],
) -> TaskRunReport {
  let name = resolved_plan_env.get("INPUT_NAME").unwrap_or("")
  let path_input = resolved_plan_env.get("INPUT_PATH").unwrap_or("")
  let overwrite = parse_bool_false_default(
    resolved_plan_env.get("INPUT_OVERWRITE").unwrap_or("false"),
  )
  if name.length() == 0 {
    return task_report_failure(
      plan, workspace_root, "upload-artifact requires with.name",
    )
  }
  let include_hidden = parse_bool_false_default(
    resolved_plan_env.get("INPUT_INCLUDE_HIDDEN_FILES").unwrap_or("false"),
  )
  let upload_paths = split_nonempty_lines(path_input)
  if upload_paths.length() == 0 {
    return task_report_failure(
      plan, workspace_root, "upload-artifact requires with.path",
    )
  }
  let artifact_root = artifact_store_root(workspace_root) +
    "/" +
    sanitize_task_id(name)
  let selection = artifact_select_upload_files(
    workspace_root,
    upload_paths,
    include_hidden~,
  )
  let if_no_files_found = match
    parse_artifact_if_no_files_found(
      resolved_plan_env.get("INPUT_IF_NO_FILES_FOUND").unwrap_or(""),
    ) {
    Some(value) => value
    None =>
      return task_report_failure(
        plan, workspace_root, "upload-artifact if-no-files-found must be one of warn, ignore, error",
      )
  }
  if selection.file_paths.length() == 0 {
    return if if_no_files_found == "error" {
      task_report_failure(
        plan,
        workspace_root,
        "upload-artifact found no files for '\{path_input}'",
      )
    } else {
      task_report_success(plan, workspace_root)
    }
  }
  if @xfs.path_exists(artifact_root) {
    if !overwrite {
      return task_report_failure(
        plan,
        workspace_root,
        "upload-artifact artifact '\{name}' already exists and overwrite is false",
      )
    }
    if !exec_remove_tree(artifact_root) {
      return task_report_failure(
        plan,
        workspace_root,
        "failed to clear artifact store for '\{name}'",
      )
    }
  }
  if !ensure_exec_dir_recursive(artifact_root) {
    return task_report_failure(
      plan,
      workspace_root,
      "failed to prepare artifact store for '\{name}'",
    )
  }
  let artifact_root_path = artifact_common_ancestor(selection.root_paths)
  for absolute_path in selection.file_paths {
    let relative_path = exec_relative_path(artifact_root_path, absolute_path)
    let target = if relative_path.length() > 0 {
      artifact_root + "/" + relative_path
    } else {
      artifact_root + "/" + exec_leaf_name(absolute_path)
    }
    if !exec_copy_tree(absolute_path, target) {
      return task_report_failure(
        plan,
        workspace_root,
        "failed to upload artifact path '\{absolute_path}'",
      )
    }
  }
  task_report_success(plan, workspace_root)
}

///|
fn execute_download_artifact_native(
  plan : TaskPlan,
  workspace_root : String,
  resolved_plan_env : Map[String, String],
) -> TaskRunReport {
  let name = resolved_plan_env.get("INPUT_NAME").unwrap_or("")
  let pattern = resolved_plan_env.get("INPUT_PATTERN").unwrap_or("")
  let path_input = resolved_plan_env.get("INPUT_PATH").unwrap_or("")
  let merge_multiple = parse_bool_false_default(
    resolved_plan_env.get("INPUT_MERGE_MULTIPLE").unwrap_or("false"),
  )
  let destination = if path_input.length() > 0 {
    resolve_task_cwd(workspace_root, path_input)
  } else {
    resolve_task_cwd(workspace_root, "")
  }
  if !ensure_exec_dir_recursive(destination) {
    return task_report_failure(
      plan,
      workspace_root,
      "failed to prepare artifact download dir '\{destination}'",
    )
  }
  if name.length() == 0 {
    let store_root = artifact_store_root(workspace_root)
    let artifact_entries = try @xfs.read_dir(store_root) catch {
      _ => []
    } noraise {
      value => value
    }
    artifact_entries.sort_by(String::lexical_compare)
    let mut copied_any = false
    for entry in artifact_entries {
      let artifact_root = store_root + "/" + entry
      if !exec_is_dir(artifact_root) {
        continue
      }
      if pattern.length() > 0 && !artifact_path_matches(pattern, entry) {
        continue
      }
      let target = if merge_multiple {
        destination
      } else {
        destination + "/" + entry
      }
      if !exec_copy_tree(artifact_root, target) {
        return task_report_failure(
          plan,
          workspace_root,
          "failed to download artifact '\{entry}'",
        )
      }
      copied_any = true
    }
    if !copied_any {
      return task_report_failure(
        plan, workspace_root, "download-artifact could not find any artifacts",
      )
    }
    return task_report_success(plan, workspace_root)
  }
  let artifact_root = artifact_store_root(workspace_root) +
    "/" +
    sanitize_task_id(name)
  if !@xfs.path_exists(artifact_root) {
    return task_report_failure(
      plan,
      workspace_root,
      "download-artifact could not find artifact '\{name}'",
    )
  }
  let entries = try @xfs.read_dir(artifact_root) catch {
    _ => []
  } noraise {
    value => value
  }
  entries.sort_by(String::lexical_compare)
  for entry in entries {
    if !exec_copy_tree(artifact_root + "/" + entry, destination + "/" + entry) {
      return task_report_failure(
        plan,
        workspace_root,
        "failed to download artifact '\{name}'",
      )
    }
  }
  task_report_success(plan, workspace_root)
}