// 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 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[:]))

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

  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 && not(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) { try! rest[:pos] } else { rest }
      let value = if eq is Some(pos) { Some(try! 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) {
          flags_map[base_resolved] = false
          continue
        }
      }
      let resolved = resolve_alias(name)
      if flag_set.contains(resolved) {
        // if it's a flag
        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_string()
      } 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_string())
          None => collections_map[resolved] = [value.to_string()]
        }
      } else {
        // Unknown option - treat as positional
        positional.push(arg)
      }
    } else if arg is [.. "-", .. rest] && rest.length() > 0 {
      // Handle short options
      loop rest {
        [] => ()
        [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)
          }
        }
        [ch, .. rest] => {
          let resolved = resolve_alias(ch.to_string())
          if flag_set.contains(resolved) {
            flags_map[resolved] = true
            continue rest
          } 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
            positional.push(arg)
            break
          }
        }
      }
    } else {
      // Everything else is positional
      positional.push(arg)
    }
  }
  {
    positional,
    flags: flags_map,
    options: options_map,
    collections: collections_map,
  }
}