///|
pub enum ActionRef {
  GitHubRepo(String, String, String, String?)
  LocalPath(String)
  DockerImage(String)
  Registry(String, String, String)
}

///|
pub struct ActionRefParseResult {
  action : ActionRef?
  errors : Array[String]
}

///|
pub struct BackendCapabilities {
  host : Bool
  docker : Bool
  wasm : Bool
}

///|
pub fn new_backend_capabilities(
  host? : Bool = false,
  docker? : Bool = false,
  wasm? : Bool = false,
) -> BackendCapabilities {
  { host, docker, wasm }
}

///|
pub struct ResolvedAction {
  uses : String
  action_ref : ActionRef
  kind : String
  backend : String
  capabilities : BackendCapabilities
  action_path : String?
  entrypoint : String?
  image : String?
  args : Array[String]
}

///|
pub struct ActionResolutionResult {
  action : ResolvedAction?
  errors : Array[String]
}

///|
fn substring(text : String, start : Int, end_ : Int) -> String {
  String::unsafe_substring(text, start~, end=end_)
}

///|
fn split_action_version(text : String) -> (String, String) {
  let mut idx = text.length()
  while idx > 0 {
    idx -= 1
    if text.unsafe_get(idx) == '@' {
      return (substring(text, 0, idx), substring(text, idx + 1, text.length()))
    }
  }
  (text, "")
}

///|
fn split_path_segments(text : String) -> Array[String] {
  let parts : Array[String] = []
  for part in text.split("/") {
    parts.push(part.to_owned())
  }
  parts
}

///|
fn action_ref_text(action : ActionRef) -> String {
  match action {
    GitHubRepo(owner, repo, version, subpath) => {
      let path = match subpath {
        Some(path) if path.length() > 0 => "/" + path
        _ => ""
      }
      if version.length() > 0 {
        owner + "/" + repo + path + "@" + version
      } else {
        owner + "/" + repo + path
      }
    }
    LocalPath(path) => path
    DockerImage(image) => "docker://" + image
    Registry(scheme, name, version) =>
      if version.length() > 0 {
        scheme + "://" + name + "@" + version
      } else {
        scheme + "://" + name
      }
  }
}

///|
pub fn backend_capabilities_for(backend : String) -> BackendCapabilities {
  match backend {
    "builtin" => new_backend_capabilities(host=true)
    "node" => new_backend_capabilities(host=true)
    "docker" => new_backend_capabilities(docker=true)
    "wasm" => new_backend_capabilities(wasm=true)
    _ => new_backend_capabilities()
  }
}

///|
fn action_parse_error(text : String, msg : String) -> ActionRefParseResult {
  { action: None, errors: ["action ref '\{text}' " + msg] }
}

///|
pub fn parse_action_ref(text : String) -> ActionRefParseResult {
  let uses = text.trim(chars=" ").to_owned()
  if uses.length() == 0 {
    return { action: None, errors: ["action ref is empty"] }
  }
  if uses.has_prefix("./") || uses.has_prefix("../") {
    return { action: Some(LocalPath(uses)), errors: [] }
  }
  if uses.has_prefix("docker://") {
    let image = substring(uses, 9, uses.length())
    if image.length() == 0 {
      return action_parse_error(uses, "must include an image name")
    }
    return { action: Some(DockerImage(image)), errors: [] }
  }
  match uses.find("://") {
    Some(idx) => {
      let scheme = substring(uses, 0, idx)
      let remainder = substring(uses, idx + 3, uses.length())
      if scheme.length() == 0 {
        return action_parse_error(uses, "has an empty scheme")
      }
      let (name, version) = split_action_version(remainder)
      if name.length() == 0 {
        return action_parse_error(uses, "must include an action name")
      }
      { action: Some(Registry(scheme, name, version)), errors: [] }
    }
    None => {
      let (path, version) = split_action_version(uses)
      let parts = split_path_segments(path)
      if parts.length() < 2 {
        return action_parse_error(
          uses, "must be owner/repo@ref, docker://image, ./path, or scheme://name@version",
        )
      }
      let owner = parts[0]
      let repo = parts[1]
      if owner.length() == 0 || repo.length() == 0 {
        return action_parse_error(uses, "must include owner and repo")
      }
      let sub_parts : Array[String] = []
      for idx in 2.. 0 {
          sub_parts.push(part)
        }
      }
      let subpath = if sub_parts.length() > 0 {
        Some(sub_parts.join("/"))
      } else {
        None
      }
      { action: Some(GitHubRepo(owner, repo, version, subpath)), errors: [] }
    }
  }
}

///|
pub fn resolve_action_ref(action : ActionRef) -> ActionResolutionResult {
  match action {
    GitHubRepo(owner, repo, _, None) if owner == "actions" && repo == "checkout" =>
      {
        action: Some({
          uses: action_ref_text(action),
          action_ref: action,
          kind: "checkout",
          backend: "builtin",
          capabilities: backend_capabilities_for("builtin"),
          action_path: None,
          entrypoint: None,
          image: None,
          args: [],
        }),
        errors: [],
      }
    GitHubRepo("actions", "upload-artifact", _, None) =>
      {
        action: Some({
          uses: action_ref_text(action),
          action_ref: action,
          kind: "upload-artifact",
          backend: "builtin",
          capabilities: backend_capabilities_for("builtin"),
          action_path: None,
          entrypoint: None,
          image: None,
          args: [],
        }),
        errors: [],
      }
    GitHubRepo("actions", "download-artifact", _, None) =>
      {
        action: Some({
          uses: action_ref_text(action),
          action_ref: action,
          kind: "download-artifact",
          backend: "builtin",
          capabilities: backend_capabilities_for("builtin"),
          action_path: None,
          entrypoint: None,
          image: None,
          args: [],
        }),
        errors: [],
      }
    GitHubRepo("actions", "setup-node", _, None) =>
      {
        action: Some({
          uses: action_ref_text(action),
          action_ref: action,
          kind: "setup-node",
          backend: "builtin",
          capabilities: backend_capabilities_for("builtin"),
          action_path: None,
          entrypoint: None,
          image: None,
          args: [],
        }),
        errors: [],
      }
    GitHubRepo("actions", "cache", _, None) =>
      {
        action: Some({
          uses: action_ref_text(action),
          action_ref: action,
          kind: "cache",
          backend: "builtin",
          capabilities: backend_capabilities_for("builtin"),
          action_path: None,
          entrypoint: None,
          image: None,
          args: [],
        }),
        errors: [],
      }
    GitHubRepo("actions", "cache", _, Some("restore")) =>
      {
        action: Some({
          uses: action_ref_text(action),
          action_ref: action,
          kind: "cache-restore",
          backend: "builtin",
          capabilities: backend_capabilities_for("builtin"),
          action_path: None,
          entrypoint: None,
          image: None,
          args: [],
        }),
        errors: [],
      }
    GitHubRepo("actions", "cache", _, Some("save")) =>
      {
        action: Some({
          uses: action_ref_text(action),
          action_ref: action,
          kind: "cache-save",
          backend: "builtin",
          capabilities: backend_capabilities_for("builtin"),
          action_path: None,
          entrypoint: None,
          image: None,
          args: [],
        }),
        errors: [],
      }
    Registry(scheme, name, _) if scheme == "builtin" && name == "checkout" =>
      {
        action: Some({
          uses: action_ref_text(action),
          action_ref: action,
          kind: "checkout",
          backend: "builtin",
          capabilities: backend_capabilities_for("builtin"),
          action_path: None,
          entrypoint: None,
          image: None,
          args: [],
        }),
        errors: [],
      }
    Registry("wasm", name, version) =>
      {
        action: Some({
          uses: action_ref_text(action),
          action_ref: action,
          kind: "wasm-module",
          backend: "wasm",
          capabilities: backend_capabilities_for("wasm"),
          action_path: None,
          entrypoint: Some(
            if version.length() > 0 {
              name + "@" + version
            } else {
              name
            },
          ),
          image: None,
          args: [],
        }),
        errors: [],
      }
    DockerImage(image) =>
      {
        action: Some({
          uses: action_ref_text(action),
          action_ref: action,
          kind: "docker-image",
          backend: "docker",
          capabilities: backend_capabilities_for("docker"),
          action_path: None,
          entrypoint: None,
          image: Some(image),
          args: [],
        }),
        errors: [],
      }
    _ =>
      {
        action: None,
        errors: ["action '\{action_ref_text(action)}' is not supported in MVP"],
      }
  }
}

///|
pub fn resolve_action_uses(text : String) -> ActionResolutionResult {
  let parsed = parse_action_ref(text)
  match parsed.action {
    Some(action) =>
      if parsed.errors.length() > 0 {
        { action: None, errors: parsed.errors }
      } else {
        resolve_action_ref(action)
      }
    None => { action: None, errors: parsed.errors }
  }
}