///|
pub(all) struct PairNode {
  key : String
  value : String
} derive(Eq, Debug)

///|
pub extend PairNode with Eq::{not_equal, equal}

///|
pub extend PairNode with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub(all) struct FromNode {
  image : String
  tag : String?
  digest : Bool
  platform : String?
  as_name : String?
} derive(Eq, Debug)

///|
pub extend FromNode with Eq::{not_equal, equal}

///|
pub extend FromNode with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub(all) struct CopyNode {
  from_stage : String?
  chown : String?
  sources : Array[String]
  dest : String
} derive(Eq, Debug)

///|
pub extend CopyNode with Eq::{not_equal, equal}

///|
pub extend CopyNode with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub(all) struct RunNode {
  form : InstructionForm
  script : String
  argv : Array[String]
  has_pipe : Bool
  has_heredoc : Bool
} derive(Eq, Debug)

///|
pub extend RunNode with Eq::{not_equal, equal}

///|
pub extend RunNode with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub(all) struct ExecNode {
  form : InstructionForm
  argv : Array[String]
  raw : String
} derive(Eq, Debug)

///|
pub extend ExecNode with Eq::{not_equal, equal}

///|
pub extend ExecNode with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub(all) enum Node {
  From(FromNode)
  Run(RunNode)
  Copy(CopyNode)
  Add(CopyNode)
  Env(Array[PairNode])
  Arg(PairNode)
  Label(Array[PairNode])
  Expose(Array[String])
  Cmd(ExecNode)
  Entrypoint(ExecNode)
  Workdir(String)
  User(String)
  Other
} derive(Debug)

///|
pub extend Node with @moonbitlang/core/debug.Debug::{to_repr}

///|
fn pairs_of(arguments : String) -> Array[PairNode] {
  let result : Array[PairNode] = []
  for pair in env_pairs(arguments) {
    let (key, value) = pair
    result.push({ key, value, })
  }
  result
}

///|
fn parse_copy_node(arguments : String, form : InstructionForm) -> CopyNode {
  let paths = if form is Json {
    parse_json_argv(arguments).unwrap_or([])
  } else {
    copy_paths(arguments)
  }
  let dest = if paths.length() > 0 { paths[paths.length() - 1] } else { "" }
  let sources : Array[String] = []
  let last = paths.length() - 1
  let mut i = 0
  while i < last {
    sources.push(paths[i])
    i += 1
  }
  {
    from_stage: flag_value(arguments, "from"),
    chown: flag_value(arguments, "chown"),
    sources,
    dest,
  }
}

///|
fn parse_exec_node(arguments : String, form : InstructionForm) -> ExecNode {
  let argv = if form is Json {
    parse_json_argv(arguments).unwrap_or([])
  } else {
    tokens(arguments)
  }
  { form, argv, raw: arguments, }
}

///|
fn parse_from_node(arguments : String) -> Node {
  match parse_from_image(arguments) {
    None => Other
    Some(image) =>
      From({
        image: image.name,
        tag: image.tag,
        digest: image.digest,
        platform: flag_value(arguments, "platform"),
        as_name: parse_from_stage(arguments),
      })
  }
}

///|
fn parse_node(
  kind : InstructionKind,
  arguments : String,
  form : InstructionForm,
) -> Node {
  match kind {
    From => parse_from_node(arguments)
    Run => {
      let argv = if form is Json {
        parse_json_argv(arguments).unwrap_or([])
      } else {
        []
      }
      Run({
        form,
        script: arguments,
        argv,
        has_pipe: arguments.contains("|"),
        has_heredoc: arguments.contains("<<"),
      })
    }
    Copy => Copy(parse_copy_node(arguments, form))
    Add => Add(parse_copy_node(arguments, form))
    Env => Env(pairs_of(arguments))
    Arg => {
      let pairs = pairs_of(arguments)
      if pairs.length() > 0 {
        Arg(pairs[0])
      } else {
        Other
      }
    }
    Label => Label(pairs_of(arguments))
    Expose => Expose(tokens(arguments))
    Cmd => Cmd(parse_exec_node(arguments, form))
    Entrypoint => Entrypoint(parse_exec_node(arguments, form))
    Workdir => Workdir(first_arg(arguments))
    User => User(first_arg(arguments))
    _ => Other
  }
}