///|
let null_short : Char = '\u0000'

///|
pub(all) enum Arg {
  Flag(short~ : Char, store~ : Bool, global~ : Bool, common~ : CommonFields)
  Named(
    short~ : Char,
    nargs~ : Nargs,
    choices~ : @hashset.HashSet[String],
    defaults~ : ArrayView[String],
    global~ : Bool,
    value_delimiter~ : String,
    common~ : CommonFields
  )
  Positional(
    nargs~ : Nargs,
    choices~ : @hashset.HashSet[String],
    defaults~ : ArrayView[String],
    value_delimiter~ : String,
    common~ : CommonFields
  )
}

///|
pub struct CommonFields {
  env_var : String
  help : String
}

///|
/// Create a flag argument
pub fn Arg::flag(
  short? : Char = null_short,
  store? : Bool = true,
  global? : Bool = false,
  env_var? : String = "",
  help? : String = "",
) -> Arg {
  let common = { help, env_var }
  Flag(short~, store~, global~, common~)
}

///|
/// Create a named argument
pub fn Arg::named(
  short? : Char = null_short,
  nargs? : Nargs = Any,
  choices? : @hashset.HashSet[String] = @hashset.new(),
  defaults? : ArrayView[String] = [],
  global? : Bool = false,
  value_delimiter? : String = ",",
  env_var? : String = "",
  help? : String = "",
) -> Arg {
  let common = { help, env_var }
  Named(short~, nargs~, choices~, defaults~, global~, value_delimiter~, common~)
}

///|
/// Create a positional argument
pub fn Arg::positional(
  nargs? : Nargs = Any,
  choices? : @hashset.HashSet[String] = @hashset.new(),
  defaults? : ArrayView[String] = [],
  value_delimiter? : String = ",",
  env_var? : String = "",
  help? : String = "",
) -> Arg {
  let common = { help, env_var }
  Positional(nargs~, choices~, defaults~, value_delimiter~, common~)
}

///|
pub fn Arg::short(self : Arg) -> Char? {
  match self {
    Flag(short~, ..) if short != null_short => Some(short)
    Named(short~, ..) if short != null_short => Some(short)
    _ => None
  }
}

///|
pub fn Arg::nargs(self : Arg) -> Nargs? {
  match self {
    Flag(_) => None
    Named(nargs~, ..) => Some(nargs)
    Positional(nargs~, ..) => Some(nargs)
  }
}

///|
pub fn Arg::choices(self : Arg) -> @hashset.HashSet[String]? {
  match self {
    Flag(_) => None
    Named(choices~, ..) => Some(choices)
    Positional(choices~, ..) => Some(choices)
  }
}

///|
pub fn Arg::value_delimiter(self : Arg) -> String? {
  match self {
    Flag(_) => None
    Named(value_delimiter~, ..) => Some(value_delimiter)
    Positional(value_delimiter~, ..) => Some(value_delimiter)
  }
}

///|
pub fn Arg::defaults(self : Arg) -> ArrayView[String] {
  match self {
    Flag(_) => []
    Named(defaults~, ..) => defaults
    Positional(defaults~, ..) => defaults
  }
}

///|
pub fn Arg::common(self : Arg) -> CommonFields {
  match self {
    Flag(common~, ..) => common
    Named(common~, ..) => common
    Positional(common~, ..) => common
  }
}

///|
pub fn Arg::is_global(self : Arg) -> Bool {
  match self {
    Named(global~, ..) => global
    Flag(global~, ..) => global
    Positional(_) => false
  }
}

///|
pub(all) enum Nargs {
  Any
  /// n == 1
  One
  /// n == X
  Fixed(UInt)
  /// n >= X
  AtLeast(UInt)
  /// n <= X
  AtMost(UInt)
  /// X <= n < Y
  Range(UInt, UInt)
} derive(Show, Eq)

///|
/// Check if the length is constrained by Nargs
pub fn Nargs::is_valid_length(self : Nargs, length : UInt) -> Bool {
  match self {
    Any => true
    One => length == 1
    Fixed(value) => length == value
    AtLeast(value) => length >= value
    AtMost(value) => length <= value
    Range(low, high) => low <= length && length < high
  }
}

///|
/// Check if the length exceeded the max length
pub fn Nargs::is_exceeded(self : Nargs, length : UInt) -> Bool {
  match self {
    Any => false
    One => length > 1
    Fixed(value) => length > value
    AtLeast(_) => false
    AtMost(value) => length > value
    Range(_, high) => length >= high
  }
}