#!/usr/bin/env bash

# ============================================
# Module Registry
# ============================================

declare -A _LOADED_MODULES=()

readonly _MODULE_AUTO_LOAD_HOOK="on_load"

# ============================================
# Helpers
# ============================================

function module::loaded()       { [[ -n "${_LOADED_MODULES["$1"]:-}" ]]; }

# Convert path-style name to namespace
# e.g. firewall/iptables -> fw::iptables
function module::to_namespace() { echo "${1//\//:}"; }

# Build fully qualified function name
# e.g. module::fn "firewall/iptables" "on_load" -> fw::iptables::on_load
function module::fn() {
  local namespace
  namespace=$(module::to_namespace "$1")
  echo "${namespace}::${2}"
}

function module::has_function() { declare -F "$(module::fn "$1" "$2")" >/dev/null 2>&1; }
function module::is_auto_load() { declare -F "$(module::fn "$1" on_load)" >/dev/null 2>&1; }

# ============================================
# Loader
# ============================================

function load_module() {
  local name="$1"

  # Wildcard: load all submodules in a directory
  if [[ "$name" == *"/*" ]]; then
    _load_module_dir "${name%/*}"
    return $?
  fi

  module::loaded "$name" && return 0

  local path
  path="$(ctx::modules)/${name}.module.sh"

  if [[ ! -f "$path" ]]; then
    log::error "Module not found: ${name} (${path})"
    return 1
  fi

  source "$path"
  _LOADED_MODULES["$name"]=1

  core::call_if_exists "$(module::fn "$name" on_load)"

  return 0
}

function _load_module_dir() {
  local dir="${1:-}"
  local module_dir
  module_dir="$(ctx::modules)/${dir}"

  if [[ ! -d "$module_dir" ]]; then
    log::error "Module directory not found: ${dir} (${module_dir})"
    return 1
  fi

  for file in "${module_dir}"/*.module.sh; do
    [[ -f "$file" ]] || continue
    local subname="${dir}/$(basename "${file%.module.sh}")"
    load_module "$subname"
  done
}