#!/usr/bin/env bash
# core/command_mixins.sh
# Mixin infrastructure — loads mixin files and provides command::mixin / flag::exclusive

# ============================================
# Active mixin tracking (per-process)
# ============================================

declare -a _ACTIVE_MIXINS=()

# ============================================
# Auto-load all mixin files
# ============================================

function command::_load_mixins() {
  local mixin_file
  local -a mixin_paths=(
    "${_FRAMEWORK_DIR}/mixins/"*.mixin.sh
  )
  for mixin_file in "${mixin_paths[@]:-}"; do
    [[ -f "$mixin_file" ]] && source "$mixin_file"
  done
}

# ============================================
# command::mixin <name>
# Called from cmd::<name>::on_load to opt into a mixin
# ============================================

function command::mixin() {
  local name="${1:-}"
  [[ -z "$name" ]] && log::error "command::mixin: missing name" && return 1

  local register_fn="command::mixin::${name}::register"
  if declare -f "$register_fn" >/dev/null 2>&1; then
    # Track for reset/process — avoid duplicates
    local m already=false
    for m in "${_ACTIVE_MIXINS[@]:-}"; do
      [[ "$m" == "$name" ]] && already=true && break
    done
    $already || _ACTIVE_MIXINS+=("$name")
    "$register_fn"
  else
    log::error "Unknown mixin: ${name} (no command::mixin::${name}::register found)"
    return 1
  fi
}

# ============================================
# command::_reset_mixins
# Called by command::run before each invocation
# ============================================

function command::_reset_mixin_state() {
  # Reset values but keep _ACTIVE_MIXINS populated
  local m
  for m in "${_ACTIVE_MIXINS[@]:-}"; do
    local reset_fn="command::mixin::${m}::reset"
    declare -f "$reset_fn" >/dev/null 2>&1 && "$reset_fn"
  done
}

function command::_reset_mixins() {
  _ACTIVE_MIXINS=()
  local reset_fn mixin_file
  # Reset all known mixins regardless of active state
  for mixin_file in \
    "${WGCTL_DIR}/core/mixins/"*.mixin.sh \
    "${WGCTL_DIR}/commands/mixins/"*.mixin.sh; do
    [[ -f "$mixin_file" ]] || continue
    local mixin_name
    mixin_name=$(basename "$mixin_file" .mixin.sh)
    reset_fn="command::mixin::${mixin_name}::reset"
    declare -f "$reset_fn" >/dev/null 2>&1 && "$reset_fn"
  done
}

# ============================================
# command::_preprocess_flags <args_nameref>
# Called by command::run — strips mixin flags from args
# ============================================

function command::_preprocess_flags() {
  local -n _args_ref="$1"
  local -a _filtered=()

  if [[ ${#_args_ref[@]} -eq 0 ]]; then
    return 0  # nothing to process
  fi

  for _arg in "${_args_ref[@]}"; do
    local _consumed=false
    for _mixin in "${_ACTIVE_MIXINS[@]:-}"; do
      local _process_fn="command::mixin::${_mixin}::process"
      if declare -f "$_process_fn" >/dev/null 2>&1; then
        if "$_process_fn" "$_arg"; then
          _consumed=true
          break
        fi
      fi
    done
    $_consumed || _filtered+=("$_arg")
  done

  if [[ ${#_filtered[@]} -gt 0 ]]; then
    _args_ref=("${_filtered[@]}")
  else
    _args_ref=()
  fi
}

# command::_resolve_conflicts <defaults_nameref> <user_nameref> <groups_string>
# Removes conflicting defaults when user provides a member of an exclusive group
function command::_resolve_conflicts() {
  local -n _def_ref="$1"
  local -n _usr_ref="$2"
  local groups="$3"

  [[ -z "$groups" ]] && return 0
  [[ ${#_def_ref[@]} -eq 0 ]] && return 0

  # Work on a copy — progressively filter across all groups
  local -a working=("${_def_ref[@]}")

  local group
  while IFS= read -r group; do
    [[ -z "$group" ]] && continue

    local -a members=()
    IFS=',' read -ra members <<< "$group"

    # Find which member (if any) the user passed from this group
    local user_member=""
    local member user_arg
    for member in "${members[@]}"; do
      for user_arg in "${_usr_ref[@]:-}"; do
        if [[ "$user_arg" == "$member" ]]; then
          user_member="$member"
          break 2
        fi
      done
    done

    # No user member in this group — don't touch defaults
    [[ -z "$user_member" ]] && continue

    # User passed a member — remove all OTHER members from defaults
    # (keep the same flag if it was already in defaults)
    local -a new_working=()
    local def_arg
    for def_arg in "${working[@]:-}"; do
      local is_other_member=false
      for member in "${members[@]}"; do
        # It's another member if it's in the group AND not the same as user's choice
        if [[ "$def_arg" == "$member" && "$def_arg" != "$user_member" ]]; then
          is_other_member=true
          break
        fi
      done
      $is_other_member || new_working+=("$def_arg")
    done
    working=("${new_working[@]:-}")
  done < <(echo "$groups" | tr '|' '\n')

  # Write back
  if [[ ${#working[@]} -gt 0 ]]; then
    _def_ref=("${working[@]}")
  else
    _def_ref=()
  fi
}

# ============================================
# Flag Exclusive
# ============================================

declare -gA _FLAG_EXCLUSIVE_GROUPS=()

# flag::exclusive <flag1> <flag2> ...
# Called from on_load — registers mutually exclusive flags for current command
function flag::exclusive() {
  local cmd="${_CURRENT_LOADING_CMD:-}"
  [[ -z "$cmd" ]] && return 0
  cmd="${cmd%%::*}"
 
  # Join flags with comma as one group
  local group
  group=$(IFS=','; echo "$*")
 
  if [[ -n "${_FLAG_EXCLUSIVE_GROUPS[$cmd]:-}" ]]; then
    _FLAG_EXCLUSIVE_GROUPS["$cmd"]+="${group}|"
  else
    _FLAG_EXCLUSIVE_GROUPS["$cmd"]="${group}|"
  fi
}