// 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 source_priority(source : ValueSource?) -> Int {
  match source {
    Some(Argv) => 3
    Some(Env) => 2
    Some(Default) => 1
    None => 0
  }
}

///|
fn prefer_child_source(
  parent_source : ValueSource?,
  child_source : ValueSource?,
) -> Bool {
  let parent_priority = source_priority(parent_source)
  let child_priority = source_priority(child_source)
  if child_priority > parent_priority {
    true
  } else if child_priority < parent_priority {
    false
  } else {
    child_source is Some(Argv)
  }
}

///|
fn strongest_source(
  parent_source : ValueSource?,
  child_source : ValueSource?,
) -> ValueSource? {
  if prefer_child_source(parent_source, child_source) {
    child_source
  } else if parent_source is Some(_) {
    parent_source
  } else {
    child_source
  }
}

///|
fn merge_global_value_from_child(
  parent : Matches,
  child : Matches,
  arg : Arg,
  name : String,
) -> Unit raise ArgParseError {
  let parent_vals = parent.values.get(name)
  let child_vals = child.values.get(name)
  let parent_source = parent.value_sources.get(name)
  let child_source = child.value_sources.get(name)
  let has_parent = parent_vals is Some(pv) && !pv.is_empty()
  let has_child = child_vals is Some(cv) && !cv.is_empty()
  if !has_parent && !has_child {
    return
  }
  if arg.multiple || arg.info is OptionInfo(action=Append, ..) {
    let both_argv = parent_source is Some(Argv) && child_source is Some(Argv)
    if both_argv {
      let merged = []
      if parent_vals is Some(pv) {
        for v in pv {
          merged.push(v)
        }
      }
      if child_vals is Some(cv) {
        for v in cv {
          merged.push(v)
        }
      }
      if !merged.is_empty() {
        parent.values[name] = merged
        parent.value_sources[name] = Argv
      }
    } else {
      let choose_child = has_child &&
        (!has_parent || prefer_child_source(parent_source, child_source))
      if choose_child {
        if child_vals is Some(cv) && !cv.is_empty() {
          parent.values[name] = cv.copy()
        }
        if child_source is Some(src) {
          parent.value_sources[name] = src
        }
      } else if parent_vals is Some(pv) && !pv.is_empty() {
        parent.values[name] = pv.copy()
        if parent_source is Some(src) {
          parent.value_sources[name] = src
        }
      }
    }
  } else {
    if has_parent &&
      has_child &&
      parent_source is Some(Argv) &&
      child_source is Some(Argv) &&
      arg.info is OptionInfo(action=Set, ..) {
      raise InvalidArgument(
        "argument '\{global_option_conflict_label(arg)}' cannot be used multiple times",
      )
    }
    let choose_child = has_child &&
      (!has_parent || prefer_child_source(parent_source, child_source))
    if choose_child {
      if child_vals is Some(cv) && !cv.is_empty() {
        parent.values[name] = cv.copy()
      }
      if child_source is Some(src) {
        parent.value_sources[name] = src
      }
    } else if parent_vals is Some(pv) && !pv.is_empty() {
      parent.values[name] = pv.copy()
      if parent_source is Some(src) {
        parent.value_sources[name] = src
      }
    }
  }
}

///|
fn merge_global_flag_from_child(
  parent : Matches,
  child : Matches,
  arg : Arg,
  name : String,
) -> Unit {
  guard child.flags.get(name) is Some(v) else { return }
  if arg.info is FlagInfo(action=Count, ..) {
    let has_parent = parent.flags.get(name) is Some(_)
    let parent_source = parent.flag_sources.get(name)
    let child_source = child.flag_sources.get(name)
    let both_argv = parent_source is Some(Argv) && child_source is Some(Argv)
    if both_argv {
      let parent_count = parent.counts.get(name).unwrap_or(0)
      let child_count = child.counts.get(name).unwrap_or(0)
      if child_count == 0 && !v {
        // `--no-` after a subcommand should reset an earlier argv count.
        parent.counts[name] = 0
        parent.flags[name] = false
      } else {
        let total = parent_count + child_count
        parent.counts[name] = total
        parent.flags[name] = total > 0
      }
      if strongest_source(parent_source, child_source) is Some(src) {
        parent.flag_sources[name] = src
      }
    } else {
      let choose_child = !has_parent ||
        prefer_child_source(parent_source, child_source)
      if choose_child {
        let child_count = child.counts.get(name).unwrap_or(0)
        parent.counts[name] = child_count
        parent.flags[name] = child_count > 0
        if child_source is Some(src) {
          parent.flag_sources[name] = src
        }
      }
    }
  } else {
    let has_parent = parent.flags.get(name) is Some(_)
    let parent_source = parent.flag_sources.get(name)
    let child_source = child.flag_sources.get(name)
    let choose_child = !has_parent ||
      prefer_child_source(parent_source, child_source)
    if choose_child {
      parent.flags[name] = v
      if child_source is Some(src) {
        parent.flag_sources[name] = src
      }
    }
  }
}

///|
fn seed_child_globals_from_parent(
  parent : Matches,
  globals : Array[Arg],
  child_local_non_globals : @set.Set[String],
) -> Matches {
  let seed = new_matches_parse_state()
  let seen : @set.Set[String] = Set([])
  for arg in globals {
    let name = arg.name
    if child_local_non_globals.contains(name) || !seen.add_and_check(name) {
      continue
    }
    if arg.info is (OptionInfo(_) | PositionalInfo(_)) {
      if parent.values.get(name) is Some(_) &&
        parent.value_sources.get(name) is Some(Argv) {
        // Presence marker only: avoid duplicating parent argv values in child.
        seed.values[name] = []
        seed.value_sources[name] = Argv
      }
      continue
    }
    if parent.flags.get(name) is Some(v) &&
      parent.flag_sources.get(name) is Some(Argv) {
      seed.flags[name] = v
      seed.flag_sources[name] = Argv
      if arg.info is FlagInfo(action=Count, ..) {
        // Presence marker only: count is rebuilt from child argv tokens.
        seed.counts[name] = 0
      }
    }
  }
  seed
}

///|
fn merge_globals_from_child(
  parent : Matches,
  child : Matches,
  globals : Array[Arg],
  child_local_non_globals : @set.Set[String],
) -> Unit raise ArgParseError {
  for arg in globals {
    let name = arg.name
    if child_local_non_globals.contains(name) {
      continue
    }
    match arg.info {
      OptionInfo(_) | PositionalInfo(_) =>
        merge_global_value_from_child(parent, child, arg, name)
      FlagInfo(_) => merge_global_flag_from_child(parent, child, arg, name)
    }
  }
}

///|
fn global_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 propagate_globals_to_child(
  parent : Matches,
  child : Matches,
  globals : Array[Arg],
  child_local_non_globals : @set.Set[String],
) -> Unit {
  for arg in globals {
    let name = arg.name
    if child_local_non_globals.contains(name) {
      continue
    }
    if arg.info is (OptionInfo(_) | PositionalInfo(_)) {
      if parent.values.get(name) is Some(values) {
        child.values[name] = values.copy()
        if parent.value_sources.get(name) is Some(src) {
          child.value_sources[name] = src
        }
      }
    } else if parent.flags.get(name) is Some(v) {
      child.flags[name] = v
      if parent.flag_sources.get(name) is Some(src) {
        child.flag_sources[name] = src
      }
      if arg.info is FlagInfo(action=Count, ..) &&
        parent.counts.get(name) is Some(c) {
        child.counts[name] = c
      }
    }
  }
  if child.parsed_subcommand is Some((sub_name, sub_m)) {
    propagate_globals_to_child(parent, sub_m, globals, Set([]))
    child.parsed_subcommand = Some((sub_name, sub_m))
  }
}