#!/usr/bin/env nu
# [command]
# name = "command metadata traits"
# group = "infrastructure"
# tags = ["metadata", "cache", "validation"]
# version = "1.0.0"
# requires = ["nickel:0.11.2"]
# note = "Runtime bridge between Nickel metadata schema and Nushell command dispatch"

# ============================================================================
# Command Metadata Cache System
# Version: 1.0.0
# Purpose: Load, cache, and validate command metadata from Nickel schema
# ============================================================================

# Get cache directory
def get-cache-dir [] : nothing -> string {
    if ($env.XDG_CACHE_HOME? | is-empty) {
        $"($env.HOME)/.cache/provisioning"
    } else {
        $"($env.XDG_CACHE_HOME)/provisioning"
    }
}

# Get cache file path
def get-cache-path [] : nothing -> string {
    $"(get-cache-dir)/command_metadata.json"
}

# Get Nickel commands file path
def get-nickel-path [] : nothing -> string {
    let proj = (
        if (($env.PROVISIONING_ROOT? | is-empty)) {
            $"($env.HOME)/project-provisioning"
        } else {
            $env.PROVISIONING_ROOT
        }
    )
    $"($proj)/provisioning/nickel/commands.ncl"
}

# Get file modification time (macOS / Linux)
def get-file-mtime [file_path: string] : nothing -> int {
    let result_macos = (do { ^stat -f%m $file_path } | complete)
    if ($result_macos.exit_code == 0) {
        ($result_macos.stdout | str trim | into int)
    } else {
        let result_linux = (do { ^stat -c%Y $file_path } | complete)
        if ($result_linux.exit_code == 0) {
            ($result_linux.stdout | str trim | into int)
        } else {
            0
        }
    }
}

# Check if cache is valid
def is-cache-valid [] : nothing -> bool {
    let cache_path = (get-cache-path)
    let schema_path = (get-nickel-path)

    if not (($cache_path | path exists)) {
        return false
    }

    let now = (date now | format date "%s" | into int)
    let cache_mtime = (get-file-mtime $cache_path)
    let schema_mtime = (get-file-mtime $schema_path)
    let ttl = 3600

    let cache_age = ($now - $cache_mtime)
    let not_expired = ($cache_age < $ttl)
    let schema_not_modified = ($cache_mtime > $schema_mtime)

    ($not_expired and $schema_not_modified)
}

# Load metadata from Nickel
def load-from-nickel [] : nothing -> record {
    # Nickel metadata loading is DISABLED due to Nickel hanging issues
    # All commands work with empty metadata (metadata is optional per metadata_handler.nu:28)
    # This ensures CLI stays responsive even if Nickel is misconfigured

    # To re-enable Nickel metadata loading in the future:
    # 1. Fix the Nickel command to not hang
    # 2. Add proper timeout support to Nushell 0.109
    # 3. Uncomment the code below and test thoroughly

    {
        commands: {}
        version: "1.0.0"
    }
}

# Original implementation (disabled due to Nickel hanging):
# def load-from-nickel [] : nothing -> record {
#     let schema_path = (get-nickel-path)
#     let result = (^nickel run $schema_path -S command_registry --format json | complete)
#     if ($result.exit_code == 0) {
#         $result.stdout | from json
#     } else {
#         {
#             error: $"Failed to load Nickel"
#             commands: {}
#             version: "1.0.0"
#         }
#     }
# }

# Save metadata to cache
export def cache-metadata [metadata: record] : nothing -> nothing {
    let dir = (get-cache-dir)
    let path = (get-cache-path)

    if not (($dir | path exists)) {
        ^mkdir -p $dir
    }

    # Save metadata to cache file
    $metadata | to json | save --force $path
}

# Load from cache file
def load-from-cache [] : nothing -> record {
    let path = (get-cache-path)

    if not (($path | path exists)) {
        return {}
    }

    (open $path --raw | from json)
}

# Load command metadata with caching
export def load-command-metadata [] : nothing -> record {
    # Check if cache is valid before loading from Nickel
    if (is-cache-valid) {
        # Use cached metadata
        load-from-cache
    } else {
        # Load from Nickel and cache it
        let metadata = (load-from-nickel)
        # Cache it for next time
        cache-metadata $metadata
        $metadata
    }
}

# Invalidate cache
export def invalidate-cache [] : nothing -> record {
    let path = (get-cache-path)

    let _rm_result = (do {
        if (($path | path exists)) {
            ^rm $path
        }
    } | complete)

    load-from-nickel
}

# Get metadata for specific command
export def get-command-metadata [name: string] : nothing -> record {
    let metadata = (load-command-metadata)

    # Check if metadata has commands field
    if ($metadata | type) != "record" {
        return {found: false, command: $name, error: "Invalid metadata"}
    }

    # Get the commands record
    let commands = (if ($metadata.commands | type) == "record" {
        $metadata.commands
    } else {
        {}
    })

    # Get the specific command
    let cmd = ($commands | get -o $name)

    if ($cmd | is-empty) {
        return {found: false, command: $name}
    }

    {found: true, command: $name, metadata: $cmd}
}

# Check if command is interactive
export def is-interactive-command [name: string] : nothing -> bool {
    let result = (get-command-metadata $name)
    if not $result.found {false} else {
        $result.metadata.requirements.interactive
    }
}

# Check if command requires auth
export def requires-auth [name: string] : nothing -> bool {
    let result = (get-command-metadata $name)
    if not $result.found {false} else {
        $result.metadata.requirements.requires_auth
    }
}

# Get auth type
export def get-auth-type [name: string] : nothing -> string {
    let result = (get-command-metadata $name)
    if not $result.found {"none"} else {
        $result.metadata.requirements.auth_type
    }
}

# Check if command requires workspace
export def requires-workspace [name: string] : nothing -> bool {
    let result = (get-command-metadata $name)
    if not $result.found {true} else {
        $result.metadata.requirements.requires_workspace
    }
}

# Check if command has side effects
export def has-side-effects [name: string] : nothing -> bool {
    let result = (get-command-metadata $name)
    if not $result.found {false} else {
        $result.metadata.requirements.side_effects
    }
}

# Get side effect type
export def get-side-effect-type [name: string] : nothing -> string {
    let result = (get-command-metadata $name)
    if not $result.found {"none"} else {
        $result.metadata.requirements.side_effect_type
    }
}

# Check if confirmation required
export def requires-confirmation [name: string] : nothing -> bool {
    let result = (get-command-metadata $name)
    if not $result.found {false} else {
        $result.metadata.requirements.requires_confirmation
    }
}

# Get min permission
export def get-min-permission [name: string] : nothing -> string {
    let result = (get-command-metadata $name)
    if not $result.found {"read"} else {
        $result.metadata.requirements.min_permission
    }
}

# Check if slow operation
export def is-slow-operation [name: string] : nothing -> bool {
    let result = (get-command-metadata $name)
    if not $result.found {false} else {
        $result.metadata.requirements.slow_operation
    }
}

# Get estimated time
export def get-estimated-time [name: string] : nothing -> int {
    let result = (get-command-metadata $name)
    if not $result.found {1} else {
        $result.metadata.estimated_time
    }
}

# Get form path
export def get-form-path [name: string] : nothing -> string {
    let result = (get-command-metadata $name)
    if not $result.found {""} else {
        if (($result.metadata.form_path? | is-empty)) {""} else {
            $result.metadata.form_path
        }
    }
}

# Validate command context
export def validate-command-context [
    name: string
    flags: record = {}
] : nothing -> record {
    let metadata_result = (get-command-metadata $name)

    if not $metadata_result.found {
        return {valid: false, issues: ["Command metadata not found"]}
    }

    let req = $metadata_result.metadata.requirements

    let issues = (
        []
        | if ($req.requires_workspace and (($env.PROVISIONING_WORKSPACE? | is-empty))) {
            append "Active workspace required"
        } else { . }
        | if ($req.requires_confirmation and not (($flags.yes? | default false) or ($flags.confirm? | default false))) {
            append "Confirmation required"
        } else { . }
        | if ($req.side_effects and (($req.side_effect_type | is-empty) or ($req.side_effect_type == "none"))) {
            append "Invalid side_effect_type"
        } else { . }
    )

    {
        valid: (($issues | length) == 0)
        command: $name
        issues: $issues
        metadata: $metadata_result.metadata
    }
}

# Print validation issues
export def print-validation-issues [validation: record] : nothing -> nothing {
    if (($validation.issues | is-empty)) {
        return
    }

    print $"(ansi red_bold)✗ Validation failed(ansi reset)"
    print ""

    $validation.issues | enumerate | each {|item|
        print $"  (ansi yellow)[$($item.index)]:(ansi reset) ($item.item)"
    }

    print ""
}

# List all commands
export def list-all-commands [] : nothing -> table {
    let metadata = (load-command-metadata)

    if (($metadata | has "error") and not (($metadata.error | is-empty))) {
        return []
    }

    if not (($metadata | has "commands")) {
        return []
    }

    $metadata.commands
        | keys | each {|cmd_name|
            let cmd = ($metadata.commands | get $cmd_name)
            let req = $cmd.requirements
            {
                name: $cmd_name
                domain: $cmd.domain
                description: $cmd.description
                interactive: $req.interactive
                requires_auth: $req.requires_auth
                auth_type: $req.auth_type
                requires_workspace: $req.requires_workspace
                side_effects: $req.side_effects
                side_effect_type: $req.side_effect_type
                requires_confirmation: $req.requires_confirmation
                min_permission: $req.min_permission
                slow_operation: $req.slow_operation
                estimated_time: $cmd.estimated_time
            }
        }
}

# Filter commands
export def filter-commands [criteria: record] : nothing -> table {
    let all = (list-all-commands)

    $all | where {|cmd|
        let domain_match = if (($criteria.domain? | is-empty)) {true} else {$cmd.domain == $criteria.domain}
        let interactive_match = if (($criteria.interactive? | is-empty)) {true} else {$cmd.interactive == $criteria.interactive}
        let side_effects_match = if (($criteria.side_effects? | is-empty)) {true} else {$cmd.side_effects == $criteria.side_effects}
        let auth_match = if (($criteria.requires_auth? | is-empty)) {true} else {$cmd.requires_auth == $criteria.requires_auth}
        let slow_match = if (($criteria.slow? | is-empty)) {true} else {$cmd.slow_operation == $criteria.slow}

        ($domain_match and $interactive_match and $side_effects_match and $auth_match and $slow_match)
    }
}

# Cache statistics
export def cache-stats [] : nothing -> record {
    let cache_path = (get-cache-path)
    let schema_path = (get-nickel-path)
    let now = (date now | format date "%s" | into int)

    let cache_mtime = (get-file-mtime $cache_path)
    let schema_mtime = (get-file-mtime $schema_path)
    let cache_age = (if ($cache_mtime > 0) {($now - $cache_mtime)} else {-1})
    let ttl_remain = (if ($cache_age >= 0) {(3600 - $cache_age)} else {0})

    {
        cache_path: $cache_path
        cache_exists: ($cache_path | path exists)
        cache_age_seconds: $cache_age
        cache_ttl_seconds: 3600
        cache_ttl_remaining: (if ($ttl_remain > 0) {$ttl_remain} else {0})
        cache_valid: (is-cache-valid)
        schema_path: $schema_path
        schema_exists: ($schema_path | path exists)
        schema_mtime_ago: (if ($schema_mtime > 0) {($now - $schema_mtime)} else {-1})
    }
}
