#!/usr/bin/env bash
# core/framework/flag.sh
# Optimized: pre-parsed constraints + per-command flag index

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

declare -gA _FLAG_REGISTRY=()    # "ctx:--flag" → "type|description"
declare -gA _FLAG_INDEX=()       # "ctx" → " --flag1 --flag2 ..." (space-separated)

# Pre-parsed constraints
declare -gA _FLAG_C_DEFAULT=()
declare -gA _FLAG_C_TYPE=()
declare -gA _FLAG_C_MIN=()
declare -gA _FLAG_C_MAX=()
declare -gA _FLAG_C_CHOICES=()
declare -gA _FLAG_C_REQUIRED=()
declare -gA _FLAG_C_LABEL=()
declare -gA _FLAG_C_SECTION=()

# Runtime values
declare -gA _FLAG_VALUES=()
declare -gA _FLAG_ARRAYS=()
declare -gA _FLAG_SET=()
declare -ga _FLAG_ARGS=()

# Completion registry
declare -gA _FLAG_COMPLETION=()

# ── Pure bash constraint extractor ────────────────────────────────────────

function flag::_extract_constraint() {
  local constraints="$1" lookup="$2"
  constraints="${constraints#[}"; constraints="${constraints%]}"
  local rest="$constraints"
  while [[ -n "$rest" ]]; do
    local pair="${rest%%,*}"
    pair="${pair# }"; pair="${pair% }"
    local k="${pair%%=*}"; local v="${pair#*=}"
    k="${k% }"; v="${v# }"; v="${v//\"/}"
    if [[ "$k" == "$lookup" ]]; then
      printf '%s' "$v"
      return 0
    fi
    [[ "$rest" == *,* ]] && rest="${rest#*,}" || break
  done
}

function flag::_parse_and_cache() {
  local key="$1" constraints="$2"
  [[ -z "$constraints" ]] && return 0
  # log::debug "parse_and_cache: key=$key"
  local v
  v=$(flag::_extract_constraint "$constraints" "default")
  [[ -n "$v" ]] && _FLAG_C_DEFAULT["$key"]="$v"
  v=$(flag::_extract_constraint "$constraints" "type")
  [[ -n "$v" ]] && _FLAG_C_TYPE["$key"]="$v"
  v=$(flag::_extract_constraint "$constraints" "min")
  [[ -n "$v" ]] && _FLAG_C_MIN["$key"]="$v"
  v=$(flag::_extract_constraint "$constraints" "max")
  [[ -n "$v" ]] && _FLAG_C_MAX["$key"]="$v"
  v=$(flag::_extract_constraint "$constraints" "choices")
  # log::debug "parse_and_cache: choices='$v'"
  [[ -n "$v" ]] && _FLAG_C_CHOICES["$key"]="$v"
  v=$(flag::_extract_constraint "$constraints" "required")
  [[ "$v" == "true" ]] && _FLAG_C_REQUIRED["$key"]="true"
  v=$(flag::_extract_constraint "$constraints" "label")
  [[ -n "$v" ]] && _FLAG_C_LABEL["$key"]="$v"
  v=$(flag::_extract_constraint "$constraints" "section")
  [[ -n "$v" ]] && _FLAG_C_SECTION["$key"]="$v"
}

# ── Constraints ────────────────────────────────────────

# flag::set_constraint <flag> <key> [value]
# Set a constraint on an already-defined flag (Option B syntax)
# Examples:
#   flag::set_constraint --mode choices "split|full"
#   flag::set_constraint --mode required
#   flag::set_constraint --mode section Options
function flag::set_constraint() {
  local flag="$1" k="$2" v="${3:-}"
  local ctx="${_CURRENT_COMMAND:-__global__}"
  local key="${ctx}:${flag}"
 
  [[ -z "${_FLAG_REGISTRY[$key]+x}" ]] && \
    log::error "flag::set_constraint: flag not defined: ${flag}" && return 1
 
  case "$k" in
    label)    _FLAG_C_LABEL["$key"]="$v"    ;;
    default)  _FLAG_C_DEFAULT["$key"]="$v"  ;;
    type)     _FLAG_C_TYPE["$key"]="$v"     ;;
    min)      _FLAG_C_MIN["$key"]="$v"      ;;
    max)      _FLAG_C_MAX["$key"]="$v"      ;;
    choices)  _FLAG_C_CHOICES["$key"]="$v"  ;;
    section)
      _FLAG_C_SECTION["$key"]="$v"
      declare -f help::_assign_flag_from_cache &>/dev/null && \
        help::_assign_flag_from_cache "$flag" "$v"
      ;;
    required) _FLAG_C_REQUIRED["$key"]="true" ;;
  esac
}

# ── Context ───────────────────────────────────────────────────────────────

function flag::_context() { printf '%s' "${_CURRENT_COMMAND:-__global__}"; }
function flag::_key()     { printf '%s:%s' "${_CURRENT_COMMAND:-__global__}" "$1"; }

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

# flag::define <flag> [bool|value] "description" [constraints...]
# flag::define <flag[]>            "description" [constraints...]
#
# Constraints (positional, after description):
#   label:name       Display label in help
#   default:50       Default value
#   type:int         Value type (int, string)
#   min:1            Minimum value (int)
#   max:100          Maximum value (int)
#   choices:a|b|c    Allowed values (pipe-separated)
#   section:Filters  Help section
#   required         Mark as required (no value needed)
#
# Examples:
#   flag::define --fw    bool  "Firewall only"    section:Filters
#   flag::define --limit value "Max results"      default:50 type:int min:1 section:Output
#   flag::define --mode  value "Tunnel mode"      required choices:split|full section:Options
#   flag::define --svc[] "Exclude service"        label:service section:Filters
function flag::define() {
  local raw_flag="$1"
  local is_array=false
 
  if [[ "$raw_flag" == *"[]" ]]; then
    is_array=true
    raw_flag="${raw_flag%[]}"
  fi
 
  local type description
  local -a constraints=()
 
  if $is_array; then
    type="array"
    description="${2:-}"
    shift 2
    constraints=("$@")
  else
    type="${2:-bool}"
    description="${3:-}"
    shift 3
    constraints=("$@")
  fi
 
  local ctx="${_CURRENT_COMMAND:-__global__}"
  local key="${ctx}:${raw_flag}"
 
  _FLAG_REGISTRY["$key"]="${type}|${description}"
  _FLAG_INDEX["$ctx"]+=" ${raw_flag}"
 
  # Parse constraints
  flag::_parse_constraints_from_args "$key" "${constraints[@]:-}"
 
  # Bool default
  if [[ "$type" == "bool" && -z "${_FLAG_C_DEFAULT[$key]:-}" ]]; then
    _FLAG_C_DEFAULT["$key"]="false"
  fi
 
  # Help section — constraint takes precedence, then active section
  local section="${_FLAG_C_SECTION[$key]:-${_CURRENT_HELP_SECTION:-}}"
  if [[ -n "$section" ]]; then
    _FLAG_C_SECTION["$key"]="$section"
    declare -f help::_assign_flag_from_cache &>/dev/null && \
      help::_assign_flag_from_cache "$raw_flag" "$section"
  fi
 
  _FLAG_COMPLETION["$raw_flag"]=1
}

function flag::register() { _FLAG_COMPLETION["${1:-}"]=1; }

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

function flag::parse() {
  local ctx="${_CURRENT_COMMAND:-__global__}"
  # log::debug "ctx=$ctx index='${_FLAG_INDEX[$ctx]:-EMPTY}'" >&2

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

  # Check for --help/-h first (fast path)
  local arg
  for arg in "$@"; do
    if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
      local cmd="${_CURRENT_COMMAND%%::*}"
      declare -f hook::fire &>/dev/null && \
        hook::fire "command:help:${cmd}" "$cmd" "${_CURRENT_COMMAND##*::}"
      return 1
    fi
  done

  # Initialize from per-command index (fast — no full registry scan)
  local flag key type default_val
  for flag in ${_FLAG_INDEX[$ctx]:-}; do
    key="${ctx}:${flag}"
    [[ -z "${_FLAG_REGISTRY[$key]+x}" ]] && continue
    type="${_FLAG_REGISTRY[$key]%%|*}"
    default_val="${_FLAG_C_DEFAULT[$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
    arg="$1"

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

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

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

    type="${_FLAG_REGISTRY[$key]%%|*}"

    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"
        local vtype="${_FLAG_C_TYPE[$key]:-}"
        if [[ "$vtype" == "int" ]]; then
          if ! [[ "$val" =~ ^[0-9]+$ ]]; then
            log::error "Flag ${arg} requires an integer, got: ${val}"; return 1
          fi
          local min="${_FLAG_C_MIN[$key]:-}" max="${_FLAG_C_MAX[$key]:-}"
          [[ -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="${_FLAG_C_CHOICES[$key]:-}"
        # log::debug "choices check: flag=$arg val=$val choices='${_FLAG_C_CHOICES[$key]:-EMPTY}'"
          if [[ -n "$choices" ]]; then
            local valid=false
            local choice
            local saved_ifs="$IFS"
            IFS=',' read -ra choice_list <<< "$choices"
            IFS="$saved_ifs"
            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
        _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
  for flag in ${_FLAG_INDEX[$ctx]:-}; do
    key="${ctx}:${flag}"
    [[ -z "${_FLAG_C_REQUIRED[$key]:-}" ]] && continue
    if [[ -z "${_FLAG_SET[$flag]+x}" && -z "${_FLAG_VALUES[$flag]:-}" ]]; then
      log::error "Flag ${flag} is required"; return 1
    fi
  done

  # Validate exclusive groups
  local cmd="${_CURRENT_COMMAND%%::*}"
  local groups=""
  [[ -n "$cmd" ]] && groups="${_FLAG_EXCLUSIVE_GROUPS[$cmd]:-}"
  
  if [[ -n "$groups" ]]; then
    local group
    while IFS= read -r group; do
      [[ -z "$group" ]] && continue
      local -a members=()
      local OLD_IFS="$IFS"; IFS=','; read -ra members <<< "$group"; IFS="$OLD_IFS"
      local found_count=0 found_flags="" member
      for member in "${members[@]}"; do
        flag::set "$member" && (( found_count++ )) && found_flags+=" $member"
      done
      if [[ $found_count -gt 1 ]]; then
        log::error "Flags${found_flags} are mutually exclusive"; return 1
      fi
    done < <(printf '%s' "$groups" | tr '|' '\n')
  fi
  return 0
}

# flag::_parse_constraints_from_args <key> <constraint_args...>
# Parse variadic constraint args: label:x required choices:a|b section:x
function flag::_parse_constraints_from_args() {
  local key="$1"
  shift
  local arg k v
 
  for arg in "$@"; do
    # Handle bare keywords (no colon) — e.g. "required"
    if [[ "$arg" != *:* ]]; then
      case "$arg" in
        required) _FLAG_C_REQUIRED["$key"]="true" ;;
        optional) ;;  # default, no-op
      esac
      continue
    fi
 
    k="${arg%%:*}"
    v="${arg#*:}"
 
    case "$k" in
      label)   _FLAG_C_LABEL["$key"]="$v"   ;;
      default) _FLAG_C_DEFAULT["$key"]="$v"  ;;
      type)    _FLAG_C_TYPE["$key"]="$v"     ;;
      min)     _FLAG_C_MIN["$key"]="$v"      ;;
      max)     _FLAG_C_MAX["$key"]="$v"      ;;
      choices) _FLAG_C_CHOICES["$key"]="$v"  ;;
      section) _FLAG_C_SECTION["$key"]="$v"  ;;
      required) _FLAG_C_REQUIRED["$key"]="$v" ;;
    esac
  done
}
 

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

function flag::bool()  { [[ "${_FLAG_VALUES[$1]:-false}" == "true" ]]; }
function flag::value() { printf '%s' "${_FLAG_VALUES[$1]:-}"; }
function flag::array() { printf '%s' "${_FLAG_ARRAYS[$1]:-}"; }
function flag::set()   { [[ -n "${_FLAG_SET[$1]+x}" ]]; }
function flag::args()  { printf '%s ' "${_FLAG_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() {
  local ctx="${1:-$(flag::_context)}"
  local key
  for key in "${!_FLAG_REGISTRY[@]}"; do
    [[ "$key" == "${ctx}:"* ]] || continue
    unset "_FLAG_REGISTRY[$key]" "_FLAG_C_DEFAULT[$key]" "_FLAG_C_TYPE[$key]"
    unset "_FLAG_C_MIN[$key]"    "_FLAG_C_MAX[$key]"     "_FLAG_C_CHOICES[$key]"
    unset "_FLAG_C_REQUIRED[$key]" "_FLAG_C_LABEL[$key]" "_FLAG_C_SECTION[$key]"
  done
  unset "_FLAG_INDEX[$ctx]"
}

# ── Help bridge ───────────────────────────────────────────────────────────

function help::_assign_flag_from_cache() {
  local flag="$1" section="$2"
  local ctx="${_CURRENT_COMMAND:-__global__}"
  [[ -z "$section" ]] && section="${_CURRENT_HELP_SECTION:-}"
  [[ -z "$section" ]] && return 0
  local skey="${ctx}:${section}"
  if [[ -z "${_HELP_SECTIONS[$skey]+x}" ]]; then
    _HELP_SECTIONS["$skey"]=$(( _HELP_SECTION_COUNT++ ))
  fi
  _HELP_FLAG_SECTION["${ctx}:${flag}"]="$section"
}