// 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.

///|
priv struct ValidationCtx {
  inherited_global_names : @set.Set[String]
  seen_names : @set.Set[String]
  seen_long : @set.Set[String]
  seen_short : @set.Set[Char]
  args : Array[Arg]
}

///|
fn ValidationCtx::new(
  inherited_global_names? : @set.Set[String] = Set([]),
) -> ValidationCtx {
  {
    inherited_global_names: inherited_global_names.copy(),
    seen_names: Set([]),
    seen_long: Set([]),
    seen_short: Set([]),
    args: [],
  }
}

///|
fn ValidationCtx::record_arg(
  self : ValidationCtx,
  arg : Arg,
) -> Unit raise ArgBuildError {
  if !self.seen_names.add_and_check(arg.name) {
    raise Unsupported("duplicate arg name: \{arg.name}")
  }
  if !arg.global && self.inherited_global_names.contains(arg.name) {
    raise Unsupported(
      "arg '\{arg.name}' shadows an inherited global; rename the arg or mark it global",
    )
  }
  fn check_long(name) raise _ {
    if !self.seen_long.add_and_check(name) {
      raise ArgBuildError::Unsupported("duplicate long option: --\{name}")
    }
  }
  fn check_short(short) raise _ {
    if !self.seen_short.add_and_check(short) {
      raise ArgBuildError::Unsupported("duplicate short option: -\{short}")
    }
  }
  match arg.info {
    FlagInfo(long~, short~, negatable~, ..) => {
      if long is Some(name) {
        check_long(name)
        if negatable {
          check_long("no-\{name}")
        }
      }
      if short is Some(short) {
        check_short(short)
      }
    }
    OptionInfo(long~, short~, ..) => {
      if long is Some(name) {
        check_long(name)
      }
      if short is Some(short) {
        check_short(short)
      }
    }
    PositionalInfo(_) => ()
  }
  self.args.push(arg)
}

///|
fn ValidationCtx::finalize(self : ValidationCtx) -> Unit raise ArgBuildError {
  validate_requires_conflicts_targets(self.args, self.seen_names)
}

///|
fn validate_command(
  cmd : Command,
  args : Array[Arg],
  groups : Array[ArgGroup],
  inherited_globals : Array[Arg],
) -> Unit raise ArgBuildError {
  if cmd.build_error is Some(err) {
    raise err
  }
  validate_inherited_global_shadowing(args, inherited_globals)
  validate_group_defs(args, groups)
  validate_group_refs(args, groups)
  validate_subcommand_defs(cmd.subcommands)
  validate_subcommand_required_policy(cmd)
  validate_default_subcommand_policy(cmd)
  validate_help_subcommand(cmd)
  validate_version_actions(cmd)
  let child_inherited_globals = merge_inherited_globals(
    inherited_globals,
    collect_globals(args),
  )
  for sub in cmd.subcommands {
    validate_command(sub, sub.args, sub.groups, child_inherited_globals)
  }
}

///|
fn validate_inherited_global_shadowing(
  args : Array[Arg],
  inherited_globals : Array[Arg],
) -> Unit raise ArgBuildError {
  for arg in args {
    if inherited_globals.iter().find_first(g => g.name == arg.name)
      is Some(inherited_arg) {
      if !arg.global {
        raise Unsupported(
          "arg '\{arg.name}' shadows an inherited global; rename the arg or mark it global",
        )
      }
      if !global_override_compatible(inherited_arg, arg) {
        raise Unsupported(
          "global arg '\{arg.name}' is incompatible with inherited global definition",
        )
      }
    }
    match arg.info {
      FlagInfo(long~, short~, negatable~, ..) => {
        if long is Some(name) {
          validate_inherited_global_long_collision(arg, name, inherited_globals)
          if negatable {
            validate_inherited_global_long_collision(
              arg,
              "no-\{name}",
              inherited_globals,
            )
          }
        }
        if short is Some(value) {
          validate_inherited_global_short_collision(
            arg, value, inherited_globals,
          )
        }
      }
      OptionInfo(long~, short~, ..) => {
        if long is Some(name) {
          validate_inherited_global_long_collision(arg, name, inherited_globals)
        }
        if short is Some(value) {
          validate_inherited_global_short_collision(
            arg, value, inherited_globals,
          )
        }
      }
      PositionalInfo(_) => ()
    }
  }
}

///|
fn merge_inherited_globals(
  inherited_globals : Array[Arg],
  globals_here : Array[Arg],
) -> Array[Arg] {
  let merged = inherited_globals.copy()
  for global in globals_here {
    match merged.search_by(arg => arg.name == global.name) {
      Some(idx) => merged[idx] = global
      None => merged.push(global)
    }
  }
  merged
}

///|
fn global_override_compatible(inherited_arg : Arg, arg : Arg) -> Bool {
  match inherited_arg.info {
    FlagInfo(action=inherited_action, negatable=inherited_negatable, ..) =>
      match arg.info {
        FlagInfo(action~, negatable~, ..) =>
          inherited_action == action && inherited_negatable == negatable
        _ => false
      }
    OptionInfo(action=inherited_action, ..) =>
      match arg.info {
        OptionInfo(action~, ..) => inherited_action == action
        _ => false
      }
    PositionalInfo(_) => false
  }
}

///|
fn validate_inherited_global_long_collision(
  arg : Arg,
  long : String,
  inherited_globals : Array[Arg],
) -> Unit raise ArgBuildError {
  if inherited_global_long_owner(arg.name, long, inherited_globals)
    is Some(owner) {
    raise Unsupported(
      "arg '\{arg.name}' long option --\{long} conflicts with inherited global '\{owner}'",
    )
  }
}

///|
fn validate_inherited_global_short_collision(
  arg : Arg,
  short : Char,
  inherited_globals : Array[Arg],
) -> Unit raise ArgBuildError {
  if inherited_global_short_owner(arg.name, short, inherited_globals)
    is Some(owner) {
    raise Unsupported(
      "arg '\{arg.name}' short option -\{short} conflicts with inherited global '\{owner}'",
    )
  }
}

///|
fn inherited_global_long_owner(
  current_name : String,
  long : String,
  inherited_globals : Array[Arg],
) -> String? {
  for inherited in inherited_globals {
    if inherited.name == current_name {
      continue
    }
    match inherited.info {
      FlagInfo(long=inherited_long, negatable~, ..) =>
        if inherited_long is Some(name) &&
          (name == long || (negatable && "no-\{name}" == long)) {
          return Some(inherited.name)
        }
      OptionInfo(long=inherited_long, ..) =>
        if inherited_long is Some(name) && name == long {
          return Some(inherited.name)
        }
      PositionalInfo(_) => ()
    }
  }
  None
}

///|
fn inherited_global_short_owner(
  current_name : String,
  short : Char,
  inherited_globals : Array[Arg],
) -> String? {
  for inherited in inherited_globals {
    if inherited.name == current_name {
      continue
    }
    match inherited.info {
      FlagInfo(short=inherited_short, ..)
      | OptionInfo(short=inherited_short, ..) =>
        if inherited_short is Some(value) && value == short {
          return Some(inherited.name)
        }
      PositionalInfo(_) => ()
    }
  }
  None
}

///|
fn validate_flag_arg(
  arg : Arg,
  ctx : ValidationCtx,
) -> Unit raise ArgBuildError {
  validate_named_option_arg(arg)
  guard! arg.info is FlagInfo(action~, negatable~, ..)
  if action is (Help | Version) {
    guard !negatable else {
      raise Unsupported("help/version actions do not support negatable")
    }
    guard arg.env is None else {
      raise Unsupported("help/version actions do not support env/defaults")
    }
    guard !arg.multiple else {
      raise Unsupported("help/version actions do not support multiple values")
    }
  }
  ctx.record_arg(arg)
}

///|
fn validate_option_arg(
  arg : Arg,
  ctx : ValidationCtx,
) -> Unit raise ArgBuildError {
  validate_named_option_arg(arg)
  validate_default_values(arg)
  ctx.record_arg(arg)
}

///|
fn validate_positional_arg(
  arg : Arg,
  ctx : ValidationCtx,
) -> Unit raise ArgBuildError {
  let (min, max) = arg_min_max_for_validate(arg)
  if (min > 1 || (max is Some(m) && m > 1)) && !arg.multiple {
    raise Unsupported(
      "multiple values require action=Append or num_args allowing >1",
    )
  }
  validate_default_values(arg)
  ctx.record_arg(arg)
}

///|
fn validate_named_option_arg(arg : Arg) -> Unit raise ArgBuildError {
  guard! arg.info
    is (FlagInfo(long~, short~, ..) | OptionInfo(long~, short~, ..))
  guard long is Some(_) || short is Some(_) || arg.env is Some(_) else {
    raise Unsupported("flag/option args require short/long/env")
  }
}

///|
fn validate_default_values(arg : Arg) -> Unit raise ArgBuildError {
  if arg.info
    is (OptionInfo(default_values~, ..) | PositionalInfo(default_values~, ..)) &&
    default_values is Some(values) &&
    values.length() > 1 &&
    !arg.multiple &&
    !(arg.info is OptionInfo(action=Append, ..)) {
    raise Unsupported(
      "default_values with multiple entries require action=Append",
    )
  }
}

///|
fn validate_group_defs(
  args : Array[Arg],
  groups : Array[ArgGroup],
) -> Unit raise ArgBuildError {
  let seen : @set.Set[String] = Set([])
  let arg_seen : @set.Set[String] = Set([])
  for arg in args {
    arg_seen.add(arg.name)
  }
  for group in groups {
    if !seen.add_and_check(group.name) {
      raise Unsupported("duplicate group: \{group.name}")
    }
  }
  for group in groups {
    for required in group.requires {
      if required == group.name {
        raise Unsupported("group cannot require itself: \{group.name}")
      }
      if !seen.contains(required) && !arg_seen.contains(required) {
        raise Unsupported(
          "unknown group requires target: \{group.name} -> \{required}",
        )
      }
    }
    for conflict in group.conflicts_with {
      if conflict == group.name {
        raise Unsupported("group cannot conflict with itself: \{group.name}")
      }
      if !seen.contains(conflict) && !arg_seen.contains(conflict) {
        raise Unsupported(
          "unknown group conflicts_with target: \{group.name} -> \{conflict}",
        )
      }
    }
  }
}

///|
fn validate_group_refs(
  args : Array[Arg],
  groups : Array[ArgGroup],
) -> Unit raise ArgBuildError {
  if groups.is_empty() {
    return
  }
  let arg_index : @set.Set[String] = Set([])
  for arg in args {
    arg_index.add(arg.name)
  }
  for group in groups {
    for name in group.args {
      if !arg_index.contains(name) {
        raise Unsupported("unknown group arg: \{group.name} -> \{name}")
      }
    }
  }
}

///|
fn validate_requires_conflicts_targets(
  args : Array[Arg],
  seen_names : @set.Set[String],
) -> Unit raise ArgBuildError {
  for arg in args {
    for required in arg.requires {
      if required == arg.name {
        raise Unsupported("arg cannot require itself: \{arg.name}")
      }
      if !seen_names.contains(required) {
        raise Unsupported("unknown requires target: \{arg.name} -> \{required}")
      }
    }
    for conflict in arg.conflicts_with {
      if conflict == arg.name {
        raise Unsupported("arg cannot conflict with itself: \{arg.name}")
      }
      if !seen_names.contains(conflict) {
        raise Unsupported(
          "unknown conflicts_with target: \{arg.name} -> \{conflict}",
        )
      }
    }
  }
}

///|
fn validate_subcommand_defs(subs : Array[Command]) -> Unit raise ArgBuildError {
  if subs.is_empty() {
    return
  }
  let seen : @set.Set[String] = Set([])
  for sub in subs {
    if !seen.add_and_check(sub.name) {
      raise Unsupported("duplicate subcommand: \{sub.name}")
    }
  }
}

///|
fn validate_subcommand_required_policy(
  cmd : Command,
) -> Unit raise ArgBuildError {
  if cmd.subcommand_required && cmd.subcommands.is_empty() {
    raise Unsupported("subcommand_required requires at least one subcommand")
  }
}

///|
fn validate_default_subcommand_policy(
  cmd : Command,
) -> Unit raise ArgBuildError {
  guard cmd.default_subcommand is Some(name) else { return }
  if cmd.subcommand_required {
    raise Unsupported(
      "default_subcommand cannot be used with subcommand_required",
    )
  }
  if cmd.arg_required_else_help {
    raise Unsupported(
      "default_subcommand cannot be used with arg_required_else_help",
    )
  }
  if cmd.subcommands.iter().find_first(sub => sub.name == name && !sub.hidden)
    is None {
    raise Unsupported(
      "default_subcommand must name a visible subcommand: \{name}",
    )
  }
  if !cmd.groups.is_empty() {
    raise Unsupported("default_subcommand does not support root groups")
  }
  for arg in cmd.args {
    if !(arg.global && arg.info is (FlagInfo(_) | OptionInfo(_))) {
      raise Unsupported(
        "default_subcommand only supports global root flags/options",
      )
    }
  }
}

///|
fn validate_help_subcommand(cmd : Command) -> Unit raise ArgBuildError {
  if help_subcommand_enabled(cmd) &&
    cmd.subcommands.any(cmd => cmd.name == "help") {
    raise Unsupported(
      "subcommand name reserved for built-in help: help (disable with disable_help_subcommand)",
    )
  }
}

///|
fn validate_version_actions(cmd : Command) -> Unit raise ArgBuildError {
  if cmd.version is None &&
    cmd.args.any(arg => arg.info is FlagInfo(action=Version, ..)) {
    raise Unsupported("version action requires command version text")
  }
}

///|
fn validate_command_policies(
  cmd : Command,
  matches : Matches,
) -> Unit raise ArgParseError {
  if cmd.subcommand_required &&
    !cmd.subcommands.is_empty() &&
    matches.parsed_subcommand is None {
    raise MissingRequired("subcommand", None)
  }
}

///|
fn validate_groups(
  args : Array[Arg],
  groups : Array[ArgGroup],
  matches : Matches,
) -> Unit raise ArgParseError {
  if groups.is_empty() {
    return
  }
  let group_presence = Map([])
  let group_seen : @set.Set[String] = Set([])
  let arg_seen : @set.Set[String] = Set([])
  for group in groups {
    group_seen.add(group.name)
  }
  for arg in args {
    arg_seen.add(arg.name)
  }
  for group in groups {
    let count = for arg in args; count = 0 {
      if !arg_in_group(arg, group) {
        continue count
      }
      if matches_has_value_or_flag(matches, arg.name) {
        continue count + 1
      } else {
        continue count
      }
    } nobreak {
      count
    }
    group_presence[group.name] = count
    if group.required && count == 0 {
      raise MissingGroup(group.name)
    }
    if !group.multiple && count > 1 {
      raise GroupConflict(group.name)
    }
  }
  for group in groups {
    let count = group_presence[group.name]
    if count == 0 {
      continue
    }
    for required in group.requires {
      if group_seen.contains(required) {
        if group_presence.get(required).unwrap_or(0) == 0 {
          raise MissingGroup(required)
        }
      } else if arg_seen.contains(required) {
        if !matches_has_value_or_flag(matches, required) {
          raise MissingRequired(required, None)
        }
      }
    }
    for conflict in group.conflicts_with {
      if group_seen.contains(conflict) {
        if group_presence.get(conflict).unwrap_or(0) > 0 {
          raise GroupConflict("\{group.name} conflicts with \{conflict}")
        }
      } else if arg_seen.contains(conflict) {
        if matches_has_value_or_flag(matches, conflict) {
          raise GroupConflict("\{group.name} conflicts with \{conflict}")
        }
      }
    }
  }
}

///|
fn arg_in_group(arg : Arg, group : ArgGroup) -> Bool {
  group.args.contains(arg.name)
}

///|
fn validate_values(
  args : Array[Arg],
  matches : Matches,
) -> Unit raise ArgParseError {
  for arg in args {
    let present = matches_has_value_or_flag(matches, arg.name)
    if arg.required && !present {
      raise MissingRequired(arg.name, None)
    }
    guard arg.info is (OptionInfo(_) | PositionalInfo(_)) else { continue }
    if !present {
      if arg.info is PositionalInfo(_) {
        let (min, _) = arg_min_max(arg)
        if min > 0 {
          raise TooFewValues(arg.name, 0, min)
        }
      }
      continue
    }
    let values = matches.values.get(arg.name).unwrap_or([])
    let count = values.length()
    let (min, max) = arg_min_max(arg)
    if count < min {
      raise TooFewValues(arg.name, count, min)
    }
    if !(arg.info is OptionInfo(action=Append, ..)) {
      match max {
        Some(max) if count > max => raise TooManyValues(arg.name, count, max)
        _ => ()
      }
    }
  }
}

///|
fn validate_relationships(
  matches : Matches,
  args : Array[Arg],
) -> Unit raise ArgParseError {
  for arg in args {
    if !matches_has_value_or_flag(matches, arg.name) {
      continue
    }
    for required in arg.requires {
      if !matches_has_value_or_flag(matches, required) {
        raise MissingRequired(required, Some(arg.name))
      }
    }
    for conflict in arg.conflicts_with {
      if matches_has_value_or_flag(matches, conflict) {
        raise InvalidArgument(
          "conflicting arguments: \{arg.name} and \{conflict}",
        )
      }
    }
  }
}