# Module: Command Dispatcher
# Purpose: Main command router: dispatches CLI commands to appropriate handlers (infra, tools, workspace, etc.).
# Dependencies: All command modules

# Command Dispatcher
# Central routing logic for all provisioning commands

# Command module imports are lazy — loaded inside wrapper functions at dispatch time.
# Only load lib_provisioning helpers required for routing logic in dispatch_command itself.
use ../lib_provisioning/utils/undefined.nu [invalid_task]
use ../lib_provisioning/workspace/enforcement.nu *
use ../lib_provisioning/commands/traits.nu *
use ./metadata_handler.nu *
use ../lib_provisioning/utils/command-registry.nu *
use ../lib_provisioning/utils/nickel_processor.nu [ncl-eval-soft, default-ncl-paths]

# Helper to run module commands
def run_module [
  args: string
  module: string
  option?: string
  --exec
] {
  let use_debug = if ($env.PROVISIONING_DEBUG? | default false) { "-x" } else { "" }

  if $exec {
    exec $"($env.PROVISIONING_NAME)" $use_debug -mod $module ($option | default "") $args
  } else {
    ^$"($env.PROVISIONING_NAME)" $use_debug -mod $module ($option | default "") $args
  }
}

# Lazy dispatch wrappers — each module is loaded only when its domain is actually invoked.
def _dispatch_infrastructure [cmd: string, ops: string, flags: record] {
    use commands/infrastructure.nu *
    handle_infrastructure_command $cmd $ops $flags
}
def _dispatch_orchestration [cmd: string, ops: string, flags: record] {
    use commands/orchestration.nu *
    handle_orchestration_command $cmd $ops $flags
}
def _dispatch_development [cmd: string, ops: string, flags: record] {
    use commands/development.nu *
    handle_development_command $cmd $ops $flags
}
def _dispatch_workspace [cmd: string, ops: string, flags: record] {
    use commands/workspace.nu *
    handle_workspace_command $cmd $ops $flags
}
def _dispatch_config [cmd: string, ops: string, flags: record] {
    use commands/configuration.nu *
    handle_config_command $cmd $ops $flags
}
def _dispatch_utilities [cmd: string, ops: string, flags: record] {
    use commands/utilities/mod.nu *
    handle_utility_command $cmd $ops $flags
}
def _dispatch_generation [cmd: string, ops: string, flags: record] {
    use commands/generation.nu *
    handle_generation_command $cmd $ops $flags
}
def _dispatch_guides [cmd: string, ops: string, flags: record] {
    use commands/guides.nu *
    handle_guide_command $cmd $ops $flags
}
def _dispatch_authentication [cmd: string, ops: string, flags: record] {
    use commands/authentication.nu *
    handle_authentication_command $cmd $ops $flags
}
def _dispatch_diagnostics [cmd: string, ops: string, flags: record] {
    use commands/diagnostics.nu *
    handle_diagnostics_command $cmd $ops $flags
}
def _dispatch_vm [cmd: string, ops: string, flags: record] {
    use commands/vm_domain.nu *
    handle_vm_command $cmd $ops $flags
}
def _dispatch_platform [cmd: string, ops: string, flags: record] {
    use commands/platform.nu *
    handle_platform_command $cmd $ops $flags
}
def _dispatch_secretumvault [cmd: string, ops: string, flags: record] {
    use commands/secretumvault.nu *
    handle_secretumvault_command $cmd $ops $flags
}
def _dispatch_build [cmd: string, ops: string, flags: record] {
    use commands/build.nu *
    handle_build_command $cmd $ops $flags
}
def _dispatch_state [cmd: string, ops: string, flags: record] {
    use commands/state.nu *
    handle_state_command $cmd $ops $flags
}

# Command registry with shortcuts and aliases
# Maps short forms and aliases to their canonical command domain
export def get_command_registry [] {
  # Read commands registry from Nickel configuration
  let registry_file = ($env.PROVISIONING | path join "core/nulib/commands-registry.ncl")

  let prov = ($env.PROVISIONING? | default "/usr/local/provisioning")
  let registry_data = (ncl-eval-soft $registry_file (default-ncl-paths "") {})
  if ($registry_data | is-empty) or ($registry_data == {}) {
    print "Error loading command registry"
    return {}
  }
  let commands = $registry_data.commands

  # Build registry record mapping commands and aliases to "category command" format
  let entries = (
    $commands | each {|cmd|
      let help_cat = $cmd.help_category
      let cmd_name = $cmd.command
      let cmd_value = $"($help_cat) ($cmd_name)"

      # Create entries for command and all its aliases
      let command_entry = {($cmd_name): $cmd_value}
      let alias_entries = ($cmd.aliases | each {|alias| {($alias): $cmd_value}})

      # Merge all entries
      [$command_entry] | append $alias_entries | reduce {|it, acc| $acc | merge $it}
    }
    | reduce {|it, acc| $acc | merge $it}
  )

  $entries
}

# Commands that require arguments are defined in commands-registry.ncl (Nickel config file)
# Use utils/command-registry.nu module to query the registry via JSON export
# Note: This is loaded dynamically when needed, not at dispatcher load time

# Main command dispatcher
# Routes commands to appropriate domain handlers
export def dispatch_command [
  args: list
  flags: record
] {
  use flags.nu *

  # Find first non-flag argument as the task
  # (flags have already been parsed by main function, but reorder_args may have moved them)
  let matches = ($args | enumerate | where {|item|
    not ($item.item | str starts-with "-") and ($item.item | is-not-empty)
  })

  let task_result = if ($matches | length) > 0 {
    $matches | first
  } else {
    null
  }

  let task = if ($task_result | is-not-empty) {
    $task_result.item
  } else {
    ""
  }

  # DEBUG
  if ($env.PROVISIONING_DEBUG? | default false) {
    print $"DEBUG dispatcher: task = '($task)'" >&2
  }

  let task_index = if ($task_result | is-not-empty) {
    $task_result.index
  } else {
    0
  }

  # Get remaining args after task
  let ops_list = if $task_index < ($args | length) {
    ($args | skip ($task_index + 1))
  } else {
    []
  }
  let ops_str = ($ops_list | str join " ")

  # Handle empty command
  if ($task | is-empty) {
    print "Use 'provisioning help' for available commands"
    exit
  }

  # NOTE: Bash wrapper validates commands via command registry
  # Direct Nushell invocations will fail later with invalid_task if command is unknown

  # Handle "provisioning help <category>" - DON'T dispatch, let main script handle it
  # The main script has "main help" function that Nushell will automatically route to
  # Using exec here creates infinite loop (calls bash wrapper → calls Nushell → calls exec → repeat)
  if $task in ["help" "h"] {
    # Don't dispatch help - it's handled by "export def main help" in provisioning script
    # Just exit dispatcher and let Nushell's built-in command routing handle it
    return
  }

  # Intercept bi-directional help: "provisioning <cmd> help" → convert to "provisioning help <cmd>"
  # Then exit dispatcher so main script's "main help" function handles it
  let first_op = if ($ops_list | length) > 0 { ($ops_list | get 0) } else { "" }
  if $first_op in ["help" "h"] {
    # Bi-directional help detected: convert args and exit dispatcher
    # The main script will see "help <task>" and route to "main help"
    return
  }

  # Resolve command through registry
  let registry = get_command_registry
  let resolved = if ($task in ($registry | columns)) { $registry | get $task } else { $task }

  # Split into domain, command, and optional subcommand args
  let parts = ($resolved | split row " ")
  let domain = if ($parts | length) > 1 { ($parts | get 0) } else { "special" }
  let command = if ($parts | length) > 1 { ($parts | get 1) } else { $task }

  # Extract any additional parts as pre-filled ops (for compound shortcuts like "dt" → "discover taskservs")
  let extra_ops = if ($parts | length) > 2 {
    ($parts | skip 2 | str join " ")
  } else {
    ""
  }

  # Combine extra_ops with user-provided ops
  let final_ops = if ($extra_ops | is-not-empty) and ($ops_str | is-not-empty) {
    $"($extra_ops) ($ops_str)"
  } else if ($extra_ops | is-not-empty) {
    $extra_ops
  } else {
    $ops_str
  }

  # Handle workspace override from flags
  let workspace_context = (extract-workspace-infra-from-flags $flags)

  # Set temporary workspace context if specified
  if ($workspace_context.workspace | is-not-empty) {
    $env.TEMP_WORKSPACE = $workspace_context.workspace
  }

  # Update infra flag if parsed from workspace:infra notation
  let updated_flags = if ($workspace_context.infra | is-not-empty) {
    $flags | merge { infra: $workspace_context.infra }
  } else {
    $flags
  }

  # WORKSPACE ENFORCEMENT - Check workspace requirement before processing
  # This enforces that most commands require an active workspace
  let enforcement_allowed = (check-and-enforce $task $args)

  if not $enforcement_allowed {
    # Enforcement failed - error already displayed by check-and-enforce
    exit 1
  }

  # METADATA VALIDATION - Check command requirements and handle interactive forms
  # Build canonical command name for metadata lookup
  let canonical_name = if ($domain == "special") {
    $command
  } else if ($domain != "") {
    $"($domain) ($command)"
  } else {
    $command
  }

  # Validate command and prepare execution (handles interactive forms)
  let prep_result = (validate-and-prepare $canonical_name $updated_flags)

  if not $prep_result.proceed {
    # Validation failed - error already displayed by validate-and-prepare
    exit 1
  }

  # Set environment based on flags
  set_debug_env $updated_flags

  # Ensure PROVISIONING_INFRA is explicitly set if infra flag was provided
  # This ensures context-aware filtering works with --infra flag
  let infra_flag = ($updated_flags | get --optional infra)
  if ($infra_flag | is-not-empty) {
    $env.PROVISIONING_INFRA = $infra_flag
  }

  # Dispatch to domain handler
  if ($env.PROVISIONING_DEBUG? | default false) {
    print $"DEBUG: Dispatching to domain='($domain)' command='($command)' final_ops='($final_ops)'" >&2
  }

  # Handler registry - maps domain to handler closure
  # To add a new command category:
  # 1. Add to commands-registry.ncl with help_category
  # 2. Add handler closure here
  # 3. Create handle_CATEGORY_command function in commands/ module
  let handlers = {
    infrastructure: {|cmd, ops, flags| _dispatch_infrastructure $cmd $ops $flags}
    orchestration: {|cmd, ops, flags| _dispatch_orchestration $cmd $ops $flags}
    development: {|cmd, ops, flags| _dispatch_development $cmd $ops $flags}
    workspace: {|cmd, ops, flags| _dispatch_workspace $cmd $ops $flags}
    config: {|cmd, ops, flags| _dispatch_config $cmd $ops $flags}
    utils: {|cmd, ops, flags| _dispatch_utilities $cmd $ops $flags}
    generation: {|cmd, ops, flags| _dispatch_generation $cmd $ops $flags}
    guides: {|cmd, ops, flags| _dispatch_guides $cmd $ops $flags}
    authentication: {|cmd, ops, flags| _dispatch_authentication $cmd $ops $flags}
    secretumvault: {|cmd, ops, flags| _dispatch_secretumvault $cmd $ops $flags}
    diagnostics: {|cmd, ops, flags| _dispatch_diagnostics $cmd $ops $flags}
    integrations: {|cmd, ops, flags| handle_integrations_command $cmd $ops $flags}
    platform: {|cmd, ops, flags| _dispatch_platform $cmd $ops $flags}
    vm: {|cmd, ops, flags| _dispatch_vm $cmd $ops $flags}
    build: {|cmd, ops, flags| _dispatch_build $cmd $ops $flags}
    state: {|cmd, ops, flags| _dispatch_state $cmd $ops $flags}
    special: {|cmd, ops, flags| handle_special_command $cmd $ops $flags}
    test: {|cmd, ops, flags| handle_test_command $cmd $ops $flags}
    help: {|cmd, ops, flags| exec $"($env.PROVISIONING_NAME)" help $cmd --notitles}
  }

  # Dynamic dispatch based on domain
  if ($domain in ($handlers | columns)) {
    let handler = ($handlers | get $domain)
    do $handler $command $final_ops $updated_flags
  } else {
    print $"❌ Error: No handler registered for domain '($domain)'"
    print $"   Command: ($task)"
    print $"   Available handlers: ($handlers | columns | str join ', ')"
    print ""
    print "To fix: Add handler closure to dispatcher.nu handlers record"
    invalid_task "" $task --end
    exit 1
  }

  # Clean up temporary workspace context
  if ($workspace_context.workspace | is-not-empty) {
    hide-env TEMP_WORKSPACE
  }
}

# Integrations command handler (prov-ecosystem + provctl)
def handle_integrations_command [command: string, ops: string, flags: record] {
  use commands/integrations/mod.nu *
  let args_list = if ($ops | is-not-empty) {
    $ops | split row " " | where { |x| ($x | is-not-empty) }
  } else {
    []
  }

  let check_mode = if $flags.check_mode { "--check" } else { "" }

  # Parse command - could be "integrations integrations" or just the subcommand
  let subcommand = if $command == "integrations" {
    ($args_list | get 0?)
  } else {
    $command
  }

  # Get remaining args
  let remaining_args = if $command == "integrations" {
    ($args_list | skip 1)
  } else {
    $args_list
  }

  # Call the integrations handler with parsed arguments
  if ($subcommand == null) {
    cmd-integrations "help" $remaining_args --check=($check_mode | is-not-empty)
  } else {
    cmd-integrations $subcommand $remaining_args --check=($check_mode | is-not-empty)
  }
}

# Test command handler
def handle_test_command [command: string, ops: string, flags: record] {
  let args = if ($ops | is-not-empty) { $ops } else { "" }
  run_module $args "test" --exec
}

# Special command handler (create, delete, update, deploy, etc.)
def handle_special_command [command: string, ops: string, flags: record] {
  match $command {
    "create" | "c" => {
      let use_debug = if $flags.debug_mode or ($env.PROVISIONING_DEBUG? | default false) { "-x" } else { "" }
      let use_check = if $flags.check_mode { "--check " } else { "" }
      let str_infra = if ($flags.infra | is-not-empty) { $"--infra ($flags.infra) " } else { "" }
      let str_out = if ($flags.outfile | is-not-empty) { $"--outfile ($flags.outfile) " } else { "" }
      exec $"($env.PROVISIONING_NAME)" $use_debug "create" $ops $use_check $str_infra $str_out --notitles
    }

    "delete" | "d" => {
      let use_debug = if $flags.debug_mode { "-x" } else { "" }
      let use_check = if $flags.check_mode { "--check " } else { "" }
      let use_yes = if $flags.auto_confirm { "--yes " } else { "" }
      let use_keepstorage = if $flags.keep_storage { "--keepstorage " } else { "" }
      let str_infra = if ($flags.infra | is-not-empty) { $"--infra ($flags.infra) " } else { "" }
      exec $"($env.PROVISIONING_NAME)" "delete" $ops $use_check $use_yes $use_keepstorage $str_infra --notitles
    }

    "update" | "u" => {
      let use_debug = if $flags.debug_mode { "-x" } else { "" }
      let use_check = if $flags.check_mode { "--check " } else { "" }
      let str_infra = if ($flags.infra | is-not-empty) { $"--infra ($flags.infra) " } else { "" }
      exec $"($env.PROVISIONING_NAME)" "update" $ops $use_check $str_infra --notitles
    }

    "price" | "prices" | "cost" | "costs" => {
      use commands/infrastructure.nu *
      handle_price_command $ops $flags
    }

    "create-server-task" | "cst" | "csts" | "create-servers-tasks" => {
      use commands/infrastructure.nu *
      handle_create_server_task $ops $flags
    }

    "new" => {
      let str_new = ($flags.new_infra | default "")
      print $"\n (_ansi yellow)New Infra ($str_new)(_ansi reset)"
    }

    "ai" => {
      let str_infra = if ($flags.infra | is-not-empty) { $"--infra ($flags.infra) " } else { "" }
      let str_settings = if ($flags.settings | is-not-empty) { $"--settings ($flags.settings) " } else { "" }
      let str_out = if ($flags.output_format | is-not-empty) { $"--out ($flags.output_format) " } else { "" }
      run_module $"($ops) ($str_infra) ($str_settings) ($str_out)" "ai" --exec
    }

    "context" | "ctx" => {
      ^$"($env.PROVISIONING_NAME)" "context" $ops --notitles
      run_module $ops "" --exec
    }

    "setup" | "st" | "config" => {
      # Route to full setup command handler
      use commands/setup.nu *

      let args_list = if ($ops | is-not-empty) {
        $ops | split row " " | where { |x| ($x | is-not-empty) }
      } else {
        []
      }

      let command = if ($args_list | length) > 0 { ($args_list | get 0) } else { "help" }
      let remaining_args = if ($args_list | length) > 1 { ($args_list | skip 1) } else { [] }

      cmd-setup $command $remaining_args --check=$flags.check_mode --verbose=$flags.debug_mode --yes=$flags.auto_confirm
    }

    "control-center" => {
      run_module $ops "control-center" --exec
    }

    "mcp-server" => {
      run_module $ops "mcp-server" --exec
    }

    "volume" | "vol" => {
      use ../provisioning-volume.nu *
      let vol_args = if ($ops | is-not-empty) { $ops | split row " " | where { $in | is-not-empty } } else { [] }
      let subcmd   = ($vol_args | get 0? | default "list")
      let rest     = if ($vol_args | length) > 1 { $vol_args | skip 1 } else { [] }
      match $subcmd {
        "list"   | "l"  => { main list --infra $flags.infra }
        "create" | "c"  => { main create ($rest | get 0? | default "") --yes=$flags.auto_confirm }
        "attach" | "a"  => { main attach ($rest | get 0? | default "") --server ($rest | get 1? | default "") --yes=$flags.auto_confirm }
        "detach" | "d"  => { main detach ($rest | get 0? | default "") --yes=$flags.auto_confirm }
        "delete" | "rm" => { main delete ($rest | get 0? | default "") --yes=$flags.auto_confirm }
        _               => { main list --infra $flags.infra }
      }
    }

    _ => {
      print $"❌ Unknown command: ($command)"
      print "Use 'provisioning help' for available commands"
      exit 1
    }
  }
}
