// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
fn assign_positionals(
  matches : Matches,
  positionals : Array[Arg],
  values : Array[String],
) -> Unit raise ArgParseError {
  let cursor = for idx, arg in positionals; cursor = 0 {
    let remaining = values.length() - cursor
    if arg.multiple {
      let (min, max) = arg_min_max(arg)
      let reserve = remaining_positional_min(positionals, idx + 1)
      let mut take = remaining - reserve
      if take < 0 {
        take = 0
      }
      match max {
        Some(max_count) if take > max_count => take = max_count
        _ => ()
      }
      if take < min {
        take = min
      }
      if take > remaining {
        take = remaining
      }
      let mut taken = 0
      while taken < take {
        add_value(matches, arg.name, values[cursor + taken], arg, Argv)
        taken += 1
      }
      continue cursor + taken
    }
    if remaining > 0 {
      add_value(matches, arg.name, values[cursor], arg, Argv)
      continue cursor + 1
    } else {
      continue cursor
    }
  } nobreak {
    cursor
  }
  if cursor < values.length() {
    let overflow_label = if !positionals.is_empty() {
      let last = positionals[positionals.length() - 1]
      if last.multiple {
        Some("<\{last.name}...>")
      } else {
        Some("<\{last.name}>")
      }
    } else {
      None
    }
    raise TooManyPositionals(values[cursor], overflow_label)
  }
}

///|
fn positional_min_required(arg : Arg) -> Int {
  let (min, _) = arg_min_max(arg)
  if min > 0 {
    min
  } else {
    0
  }
}

///|
fn remaining_positional_min(positionals : Array[Arg], start : Int) -> Int {
  for idx = start, total = 0; idx < positionals.length(); {
    continue idx + 1, total + positional_min_required(positionals[idx])
  } nobreak {
    total
  }
}

///|
fn add_value(
  matches : Matches,
  name : String,
  value : String,
  arg : Arg,
  source : ValueSource,
) -> Unit {
  if arg.multiple || arg.info is OptionInfo(action=Append, ..) {
    let arr = matches.values.get(name).unwrap_or([])
    arr.push(value)
    matches.values[name] = arr
    matches.value_sources[name] = source
  } else {
    matches.values[name] = [value]
    matches.value_sources[name] = source
  }
}

///|
fn assign_value(
  matches : Matches,
  arg : Arg,
  value : String,
  source : ValueSource,
) -> Unit raise ArgParseError {
  match arg.info {
    OptionInfo(action=Append, ..) =>
      add_value(matches, arg.name, value, arg, source)
    OptionInfo(action=Set, ..) | PositionalInfo(_) =>
      add_value(matches, arg.name, value, arg, source)
    FlagInfo(action=SetTrue, ..) => {
      let flag = parse_bool(value)
      matches.flags[arg.name] = flag
      matches.flag_sources[arg.name] = source
    }
    FlagInfo(action=SetFalse, ..) => {
      let flag = parse_bool(value)
      matches.flags[arg.name] = !flag
      matches.flag_sources[arg.name] = source
    }
    FlagInfo(action=Count, ..) => {
      let count = parse_count(value)
      matches.counts[arg.name] = count
      matches.flags[arg.name] = count > 0
      matches.flag_sources[arg.name] = source
    }
    FlagInfo(action=Help, ..) =>
      raise InvalidArgument("help action does not take values")
    FlagInfo(action=Version, ..) =>
      raise InvalidArgument("version action does not take values")
  }
}

///|
fn option_conflict_label(arg : Arg) -> String {
  match arg.info {
    FlagInfo(long=Some(name), ..) | OptionInfo(long=Some(name), ..) =>
      "--\{name}"
    FlagInfo(short=Some(short), ..) | OptionInfo(short=Some(short), ..) =>
      "-\{short}"
    _ => arg.name
  }
}

///|
fn check_duplicate_set_occurrence(
  matches : Matches,
  arg : Arg,
) -> Unit raise ArgParseError {
  guard arg.info is (OptionInfo(action=Set, ..) | PositionalInfo(_)) else {
    return
  }
  if matches.values.get(arg.name) is Some(values) && !values.is_empty() {
    raise InvalidArgument(
      "argument '\{option_conflict_label(arg)}' cannot be used multiple times",
    )
  }
}

///|
fn should_stop_option_value(
  value : String,
  arg : Arg,
  _long_index : Map[String, Arg],
  _short_index : Map[Char, Arg],
) -> Bool {
  if !value.has_prefix("-") || value == "-" {
    return false
  }
  if arg.info
    is (OptionInfo(allow_hyphen_values~, ..)
    | PositionalInfo(allow_hyphen_values~, ..)) &&
    allow_hyphen_values {
    // Rust clap parity:
    // - `clap_builder/src/parser/parser.rs`: `parse_long_arg` / `parse_short_arg`
    //   return `ParseResult::MaybeHyphenValue` when the pending arg in
    //   `ParseState::Opt` or `ParseState::Pos` has `allow_hyphen_values`.
    // - `clap_builder/src/builder/arg.rs` (`Arg::allow_hyphen_values` docs):
    //   prior args with this setting take precedence over known flags/options.
    // - `tests/builder/opts.rs` (`leading_hyphen_with_flag_after`):
    //   a pending option consumes `-f` as a value rather than parsing flag `-f`.
    // This also means `--` is consumed as a value while the option remains pending.
    return false
  }
  true
}

///|
fn apply_env(
  matches : Matches,
  args : Array[Arg],
  env : Map[String, String],
) -> Unit raise ArgParseError {
  for arg in args {
    let name = arg.name
    if matches_has_value_or_flag(matches, name) {
      continue
    }
    let env_name = match arg.env {
      Some(value) => value
      None => continue
    }
    let value = match env.get(env_name) {
      Some(v) => v
      None => continue
    }
    if arg.info is (OptionInfo(_) | PositionalInfo(_)) {
      assign_value(matches, arg, value, Env)
      continue
    }
    match arg.info {
      FlagInfo(action=Count, ..) => {
        let count = parse_count(value)
        matches.counts[name] = count
        matches.flags[name] = count > 0
        matches.flag_sources[name] = Env
      }
      FlagInfo(action=SetFalse, ..) => {
        let flag = parse_bool(value)
        matches.flags[name] = !flag
        matches.flag_sources[name] = Env
      }
      FlagInfo(action=SetTrue, ..) => {
        let flag = parse_bool(value)
        matches.flags[name] = flag
        matches.flag_sources[name] = Env
      }
      OptionInfo(_) | PositionalInfo(_) => ()
      FlagInfo(action=Help | Version, ..) => ()
    }
  }
}

///|
fn apply_defaults(matches : Matches, args : Array[Arg]) -> Unit {
  for arg in args {
    let default_values = match arg.info {
      FlagInfo(_) => continue
      OptionInfo(default_values~, ..) | PositionalInfo(default_values~, ..) =>
        default_values
    }
    if matches_has_value_or_flag(matches, arg.name) {
      continue
    }
    match default_values {
      Some(values) if !values.is_empty() =>
        for value in values {
          let _ = add_value(matches, arg.name, value, arg, Default)
        }
      _ => ()
    }
  }
}

///|
fn matches_has_value_or_flag(matches : Matches, name : String) -> Bool {
  matches.flags.contains(name) || matches.values.contains(name)
}

///|
fn apply_flag(matches : Matches, arg : Arg, source : ValueSource) -> Unit {
  match arg.info {
    FlagInfo(action=SetTrue, ..) => matches.flags[arg.name] = true
    FlagInfo(action=SetFalse, ..) => matches.flags[arg.name] = false
    FlagInfo(action=Count, ..) => {
      let current = matches.counts.get(arg.name).unwrap_or(0)
      matches.counts[arg.name] = current + 1
      matches.flags[arg.name] = true
    }
    FlagInfo(action=Help, ..) => ()
    FlagInfo(action=Version, ..) => ()
    _ => matches.flags[arg.name] = true
  }
  matches.flag_sources[arg.name] = source
}

///|
fn parse_bool(value : String) -> Bool raise ArgParseError {
  match value {
    "1" | "true" | "yes" | "on" => true
    "0" | "false" | "no" | "off" => false
    _ =>
      raise InvalidValue(
        "invalid value '\{value}' for boolean flag; expected one of: 1, 0, true, false, yes, no, on, off",
      )
  }
}

///|
fn parse_count(value : String) -> Int raise ArgParseError {
  try @string.parse_int(value) catch {
    _ =>
      raise InvalidValue(
        "invalid value '\{value}' for count; expected a non-negative integer",
      )
  } noraise {
    _..<0 =>
      raise InvalidValue(
        "invalid value '\{value}' for count; expected a non-negative integer",
      )
    v => v
  }
}