#!/usr/bin/env bash
# core/framework/flag.sh
#
# Declarative flag registration and parsing framework.
#
# Registration (in on_load):
#   flag::define --name    value "Filter by peer name"
#   flag::define --fw      bool  "Firewall events only"
#   flag::define --limit   value "Max results"            [default=50, type=int, min=1]
#   flag::define --exclude[]     "Exclude service"        [label="service"]
#   flag::exclusive --fw --wg
#
# Parsing (in run):
#   flag::parse "$@" || return 1
#
# Accessors:
#   flag::bool  --fw          → exits 0/1
#   flag::value --name        → prints value or default
#   flag::array --exclude     → prints newline-separated values
#   flag::set   --name        → exits 0 if explicitly passed
#   flag::args                → prints remaining positional args

# ── Storage ────────────────────────────────────────────────────────────────

# Per-command flag registry — populated by flag::define during on_load
# Key: "cmd::subcmd:--flag"
# Value: "type|description|constraints"
declare -gA _FLAG_REGISTRY=()

# Runtime values — populated by flag::parse during run
declare -gA _FLAG_VALUES=()    # --flag → value (bool: "true"/"false")
declare -gA _FLAG_ARRAYS=()    # --flag → newline-separated values
declare -gA _FLAG_SET=()       # --flag → "1" if explicitly passed
declare -ga _FLAG_ARGS=()      # remaining positional args

# Defaults — populated at registration time
declare -gA _FLAG_DEFAULTS=()  # "cmd::subcmd:--flag" → default value

# ── Helpers ─────────────────────────────────────────────────────────────────

function flag::_context() {
  echo "${_CURRENT_COMMAND:-__global__}"
}

function flag::_key() {
  echo "$(flag::_context):${1}"
}

function flag::_parse_constraints() {
  # Parse "[key=val, key=val]" constraint string
  # Output: "key=val\nkey=val"
  local constraints="${1:-}"
  constraints="${constraints#[}"
  constraints="${constraints%]}"
  echo "$constraints" | tr ',' '\n' | sed 's/^ *//' | sed 's/ *$//'
}

function flag::_constraint_get() {
  local constraints="$1" key="$2"
  echo "$constraints" | while IFS='=' read -r k v; do
    k="${k// /}"
    if [[ "$k" == "$key" ]]; then
      echo "${v//\"/}"
      return 0
    fi
  done
}

# ── Registration ─────────────────────────────────────────────────────────────

# flag::define --flag [bool|value] "description" [constraints]
# flag::define --flag[]            "description" [constraints]  ← array flag
function flag::define() {
  local raw_flag="$1"
  local is_array=false

  # Detect array flag syntax: --flag[]
  if [[ "$raw_flag" == *"[]" ]]; then
    is_array=true
    raw_flag="${raw_flag%[]}"
  fi

  local type description constraints=""

  if $is_array; then
    type="array"
    description="${2:-}"
    constraints="${3:-}"
  else
    type="${2:-bool}"
    description="${3:-}"
    constraints="${4:-}"
  fi

  local key
  key=$(flag::_key "$raw_flag")

  _FLAG_REGISTRY["$key"]="${type}|${description}|${constraints}"

  # Extract and store default
  if [[ -n "$constraints" ]]; then
    local parsed_constraints
    parsed_constraints=$(flag::_parse_constraints "$constraints")
    local default_val
    default_val=$(flag::_constraint_get "$parsed_constraints" "default")
    if [[ -n "$default_val" ]]; then
      _FLAG_DEFAULTS["$key"]="$default_val"
    fi
  fi

  # Set bool default to false if not specified
  if [[ "$type" == "bool" && -z "${_FLAG_DEFAULTS[$key]:-}" ]]; then
    _FLAG_DEFAULTS["$key"]="false"
  fi

  # Register for shell completion (backward compat)
  flag::register "$raw_flag" 2>/dev/null || true

  help::_assign_flag_section "$raw_flag" "$constraints"
}

# flag::register remains for backward compat and completion-only registration
# Already defined in command.sh — this is a no-op if already defined
function flag::register() {
  : # handled by command.sh completion registry
}

# ── Parsing ──────────────────────────────────────────────────────────────────

function flag::parse() {
  local ctx
  ctx=$(flag::_context)

  # Reset runtime state
  _FLAG_VALUES=()
  _FLAG_ARRAYS=()
  _FLAG_SET=()
  _FLAG_ARGS=()

  # Initialize bools to false, values to defaults
  local key
  for key in "${!_FLAG_REGISTRY[@]}"; do
    [[ "$key" != "${ctx}:"* ]] && continue
    local flag="${key#${ctx}:}"
    local reg="${_FLAG_REGISTRY[$key]}"
    local type="${reg%%|*}"
    local default_val="${_FLAG_DEFAULTS[$key]:-}"

    case "$type" in
      bool)  _FLAG_VALUES["$flag"]="${default_val:-false}" ;;
      value) [[ -n "$default_val" ]] && _FLAG_VALUES["$flag"]="$default_val" ;;
      array) _FLAG_ARRAYS["$flag"]="" ;;
    esac
  done

  # Parse args
  while [[ $# -gt 0 ]]; do
    local arg="$1"

    if [[ "$arg" == "--" ]]; then
      shift
      _FLAG_ARGS+=("$@")
      break
    fi

    if [[ "$arg" != --* ]]; then
      _FLAG_ARGS+=("$arg")
      shift
      continue
    fi

    local key
    key=$(flag::_key "$arg")

    if [[ -z "${_FLAG_REGISTRY[$key]+x}" ]]; then
      log::error "Unknown flag: ${arg}"
      return 1
    fi

    local reg="${_FLAG_REGISTRY[$key]}"
    local type="${reg%%|*}"
    local constraints
    constraints=$(echo "$reg" | cut -d'|' -f3)

    case "$type" in
      bool)
        _FLAG_VALUES["$arg"]="true"
        _FLAG_SET["$arg"]="1"
        shift
        ;;
      value)
        if [[ $# -lt 2 || "$2" == --* ]]; then
          log::error "Flag ${arg} requires a value"
          return 1
        fi
        local val="$2"
        # Type validation
        if [[ -n "$constraints" ]]; then
          local parsed
          parsed=$(flag::_parse_constraints "$constraints")
          local vtype
          vtype=$(flag::_constraint_get "$parsed" "type")
          if [[ "$vtype" == "int" ]]; then
            if ! [[ "$val" =~ ^[0-9]+$ ]]; then
              log::error "Flag ${arg} requires an integer, got: ${val}"
              return 1
            fi
            local min max
            min=$(flag::_constraint_get "$parsed" "min")
            max=$(flag::_constraint_get "$parsed" "max")
            [[ -n "$min" && "$val" -lt "$min" ]] && \
              log::error "Flag ${arg} minimum is ${min}, got: ${val}" && return 1
            [[ -n "$max" && "$val" -gt "$max" ]] && \
              log::error "Flag ${arg} maximum is ${max}, got: ${val}" && return 1
          fi
          local choices
          choices=$(flag::_constraint_get "$parsed" "choices")
          if [[ -n "$choices" ]]; then
            local valid=false
            local choice
            IFS='|' read -ra choice_list <<< "$choices"
            for choice in "${choice_list[@]}"; do
              [[ "$val" == "$choice" ]] && valid=true && break
            done
            if ! $valid; then
              log::error "Flag ${arg} must be one of: ${choices//|/, }, got: ${val}"
              return 1
            fi
          fi
        fi
        _FLAG_VALUES["$arg"]="$val"
        _FLAG_SET["$arg"]="1"
        shift 2
        ;;
      array)
        if [[ $# -lt 2 || "$2" == --* ]]; then
          log::error "Flag ${arg} requires a value"
          return 1
        fi
        if [[ -n "${_FLAG_ARRAYS[$arg]:-}" ]]; then
          _FLAG_ARRAYS["$arg"]+=$'\n'"$2"
        else
          _FLAG_ARRAYS["$arg"]="$2"
        fi
        _FLAG_SET["$arg"]="1"
        shift 2
        ;;
    esac
  done

  # Validate required flags
  for key in "${!_FLAG_REGISTRY[@]}"; do
    [[ "$key" != "${ctx}:"* ]] && continue
    local flag="${key#${ctx}:}"
    local reg="${_FLAG_REGISTRY[$key]}"
    local constraints
    constraints=$(echo "$reg" | cut -d'|' -f3)
    if [[ -n "$constraints" ]]; then
      local parsed
      parsed=$(flag::_parse_constraints "$constraints")
      local required
      required=$(flag::_constraint_get "$parsed" "required")
      if [[ "$required" == "true" && -z "${_FLAG_SET[$flag]+x}" ]]; then
        # Check if provided by defaults
        if [[ -z "${_FLAG_VALUES[$flag]:-}" ]]; then
          log::error "Flag ${flag} is required"
          return 1
        fi
      fi
    fi
  done

  return 0
}

# ── Accessors ─────────────────────────────────────────────────────────────────

# flag::bool --flag → exits 0 if true, 1 if false
function flag::bool() {
  [[ "${_FLAG_VALUES[$1]:-false}" == "true" ]]
}

# flag::value --flag → prints value or empty string
function flag::value() {
  echo "${_FLAG_VALUES[$1]:-}"
}

# flag::array --flag → prints newline-separated values
function flag::array() {
  echo "${_FLAG_ARRAYS[$1]:-}"
}

# flag::set --flag → exits 0 if explicitly passed by user
function flag::set() {
  [[ -n "${_FLAG_SET[$1]+x}" ]]
}

# flag::args → prints positional args array
function flag::args() {
  echo "${_FLAG_ARGS[@]:-}"
}

# flag::args_array → populates a nameref array with positional args
function flag::args_array() {
  local -n _arr_ref="$1"
  _arr_ref=("${_FLAG_ARGS[@]:-}")
}

# ── Reset ─────────────────────────────────────────────────────────────────────

function flag::_reset_runtime() {
  _FLAG_VALUES=()
  _FLAG_ARRAYS=()
  _FLAG_SET=()
  _FLAG_ARGS=()
}

function flag::_reset_registry() {
  # Clear registry entries for a specific command context
  local ctx="${1:-$(flag::_context)}"
  local key
  for key in "${!_FLAG_REGISTRY[@]}"; do
    [[ "$key" == "${ctx}:"* ]] && unset "_FLAG_REGISTRY[$key]"
  done
  for key in "${!_FLAG_DEFAULTS[@]}"; do
    [[ "$key" == "${ctx}:"* ]] && unset "_FLAG_DEFAULTS[$key]"
  done
}