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

///|
/// Represents parsed command line arguments
pub struct Args {
  /// Positional arguments (non-flag/option arguments)
  positional : Array[String]
  /// Boolean flags (e.g., --verbose, -v)
  flags : Map[String, Bool]
  /// String options with values (e.g., --output=file.txt, -o file.txt)
  options : Map[String, String]
  /// Array options that can appear multiple times (e.g., --include src --include lib)
  collections : Map[String, Array[String]]
}

///|
pub suberror ParseError {
  ParseError(String)
}

///|
/// Parse command line arguments with simple configuration
pub fn parse(
  args : ArrayView[String],
  flags? : ArrayView[String] = [],
  options? : ArrayView[String] = [],
  collections? : ArrayView[String] = [],
  aliases? : Map[String, String] = {},
  negatable? : ArrayView[String] = [],
  double_dash? : Bool = true,
  stop_early? : Bool = false,
) -> Args raise ParseError {
  let positional : Array[String] = []
  let flags_map : Map[String, Bool] = {}
  let options_map : Map[String, String] = {}
  let collections_map : Map[String, Array[String]] = {}

  // Create sets for O(1) lookup
  let flag_set = Set::from_array(flags.map(s => s[:]))
  let option_set = Set::from_array(options.map(s => s[:]))
  let collection_set = Set::from_array(collections.map(s => s[:]))
  let negatable_set = Set::from_array(negatable.map(s => s[:]))

  let mut names = options[:]
  while names is [name, .. rest] {
    names = rest
    if flag_set.contains(name) {
      raise ParseError("Argument \{name} cannot be both a flag and an option")
    }
  }
  names = collections[:]
  while names is [name, .. rest] {
    names = rest
    if flag_set.contains(name) {
      raise ParseError(
        "Argument \{name} cannot be both a flag and a collection",
      )
    }
    if option_set.contains(name) {
      raise ParseError(
        "Argument \{name} cannot be both an option and a collection",
      )
    }
  }
  names = negatable[:]
  while names is [name, .. rest] {
    names = rest
    if !flag_set.contains(name) {
      raise ParseError("Negatable argument \{name} must be declared as a flag")
    }
  }

  // Helper to resolve aliases
  fn resolve_alias(name : StringView) -> String {
    aliases.get_from_string(name).unwrap_or(name.to_owned())
  }

  let mut args = args[:]
  while args is [arg, .. rest] {
    args = rest
    // Handle double dash
    if arg is "--" && double_dash {
      // After --, everything is positional
      positional.push_iter(args.iter())
      break
    }

    // Stop early if requested and we hit a non-option
    if stop_early && !arg.has_prefix("-") {
      positional.push(arg)
      positional.push_iter(args.iter())
      break
    }
    if arg is [.. "--", .. rest] && rest.length() > 0 {
      // Handle long options (--name or --name=value)
      // Extract name and value
      let eq = rest.find("=")
      let name = if eq is Some(pos) { rest[:pos] } else { rest }
      let value = if eq is Some(pos) { Some(rest[pos + 1:]) } else { None }

      // Handle --no- prefix for negatable flags
      if name is [.. "no-", .. base_name] {
        let base_resolved = resolve_alias(base_name)
        if negatable_set.contains(base_resolved) {
          if value is Some(_) {
            raise ParseError("Flag --\{name} does not take a value")
          }
          flags_map[base_resolved] = false
          continue
        }
      }
      let resolved = resolve_alias(name)
      if flag_set.contains(resolved) {
        // if it's a flag
        if value is Some(_) {
          raise ParseError("Flag --\{name} does not take a value")
        }
        flags_map[resolved] = true
      } else if option_set.contains(resolved) {
        // if it's an option
        let value = match value {
          Some(v) => v
          None => {
            guard args is [next_arg, .. rest_args] else {
              raise ParseError("Option --\{name} requires a value")
            }
            args = rest_args
            next_arg
          }
        }
        options_map[resolved] = value.to_owned()
      } else if collection_set.contains(resolved) {
        // if it's a collection
        let value = match value {
          Some(v) => v
          None => {
            guard args is [next_arg, .. rest_args] else {
              raise ParseError("Collection --\{name} requires a value")
            }
            args = rest_args
            next_arg
          }
        }
        match collections_map.get_from_string(resolved) {
          Some(arr) => arr.push(value.to_owned())
          None => collections_map[resolved] = [value.to_owned()]
        }
      } else {
        // Unknown option - treat as positional
        positional.push(arg)
      }
    } else if arg is [.. "-", .. rest] && rest.length() > 0 {
      // Handle short options
      if rest is [ch] {
        let resolved = resolve_alias(ch.to_string())
        if flag_set.contains(resolved) {
          flags_map[resolved] = true
        } else if option_set.contains(resolved) {
          // Option requires a value
          guard args is [next_arg, .. rest_args] else {
            raise ParseError("Option -\{ch} requires a value")
          }
          options_map[resolved] = next_arg
          args = rest_args
        } else if collection_set.contains(resolved) {
          // Collection requires a value
          guard args is [next_arg, .. rest_args] else {
            raise ParseError("Collection -\{ch} requires a value")
          }
          let value = next_arg
          match collections_map.get_from_string(resolved) {
            Some(arr) => arr.push(value)
            None => collections_map[resolved] = [value]
          }
          args = rest_args
        } else {
          // Unknown flag - treat as positional
          positional.push(arg)
        }
      } else {
        let mut unknown = false
        let mut short = rest
        while short is [ch, .. remaining] {
          let resolved = resolve_alias(ch.to_string())
          if flag_set.contains(resolved) {
            short = remaining
          } else if option_set.contains(resolved) {
            // Option requires a value
            raise ParseError(
              "Option -\{ch} in combined short flags requires a value",
            )
          } else if collection_set.contains(resolved) {
            // Collection requires a value
            raise ParseError(
              "Collection -\{ch} in combined short flags requires a value",
            )
          } else {
            // Unknown flag - treat as positional
            unknown = true
            break
          }
        }
        if unknown {
          positional.push(arg)
        } else {
          short = rest
          while short is [ch, .. remaining] {
            let resolved = resolve_alias(ch.to_string())
            flags_map[resolved] = true
            short = remaining
          }
        }
      }
    } else {
      // Everything else is positional
      positional.push(arg)
    }
  }
  {
    positional,
    flags: flags_map,
    options: options_map,
    collections: collections_map,
  }
}