#!/usr/bin/env nu

# Core bundle tool - bundles Nushell core libraries and CLI for distribution
#
# Bundles:
# - Nushell provisioning CLI wrapper
# - Core Nushell libraries (lib_provisioning)
# - Configuration system
# - Template system
# - Extensions and plugins

use std log

def main [
    --output-dir: string = "dist/core"           # Output directory for core bundle
    --config-dir: string = "dist/config"        # Configuration directory
    --validate                                   # Validate Nushell syntax
    --compress                                   # Compress bundle with gzip
    --exclude-dev = true                         # Exclude development files
    --verbose                                   # Enable verbose logging
] {

    let repo_root = ($env.PWD | path dirname | path dirname | path dirname)
    let bundle_config = {
        output_dir: ($output_dir | path expand)
        config_dir: ($config_dir | path expand)
        validate: $validate
        compress: $compress
        exclude_dev: $exclude_dev
        verbose: $verbose
    }

    log info $"Starting core bundle creation with config: ($bundle_config)"

    # Ensure output directories exist
    mkdir ($bundle_config.output_dir)
    mkdir ($bundle_config.config_dir)

    # Define core components to bundle
    let core_components = [
        {
            name: "provisioning-cli"
            source: ($repo_root | path join "provisioning" "core" "nulib" "provisioning")
            target: ($bundle_config.output_dir | path join "bin" "provisioning")
            type: "executable"
        },
        {
            name: "core-libraries"
            source: ($repo_root | path join "provisioning" "core" "nulib" "lib_provisioning")
            target: ($bundle_config.output_dir | path join "lib" "lib_provisioning")
            type: "directory"
        },
        {
            name: "workflows"
            source: ($repo_root | path join "provisioning" "core" "nulib" "workflows")
            target: ($bundle_config.output_dir | path join "lib" "workflows")
            type: "directory"
        },
        {
            name: "servers"
            source: ($repo_root | path join "provisioning" "core" "nulib" "servers")
            target: ($bundle_config.output_dir | path join "lib" "servers")
            type: "directory"
        },
        {
            name: "extensions"
            source: ($repo_root | path join "provisioning" "extensions")
            target: ($bundle_config.output_dir | path join "extensions")
            type: "directory"
        }
    ]

    # Define configuration files
    let config_files = [
        {
            name: "default-config"
            source: ($repo_root | path join "provisioning" "config.defaults.toml")
            target: ($bundle_config.config_dir | path join "config.defaults.toml")
        },
        {
            name: "config-examples"
            source: ($repo_root | path join "provisioning" "config")
            target: ($bundle_config.config_dir | path join "examples")
        }
    ]

    # Bundle core components
    let component_results = $core_components | each {|component|
        bundle_component $component $bundle_config $repo_root
    }

    # Bundle configuration files
    let config_results = $config_files | each {|config|
        bundle_config_file $config $bundle_config
    }

    let results = ($component_results | append $config_results)

    # Validate Nushell syntax if requested
    let validation_result = if $bundle_config.validate {
        validate_nushell_syntax ($bundle_config.output_dir)
    } else {
        { status: "skipped", validated_files: 0, errors: [] }
    }

    # Create bundle metadata
    create_bundle_metadata $bundle_config $repo_root $results

    # Compress if requested
    let compression_result = if $bundle_config.compress {
        compress_bundle ($bundle_config.output_dir)
    } else {
        { status: "skipped" }
    }

    let summary = {
        total_components: ($results | length)
        successful: ($results | where status == "success" | length)
        failed: ($results | where status == "failed" | length)
        validation: $validation_result
        compression: $compression_result
        bundle_config: $bundle_config
        results: $results
    }

    if $summary.failed > 0 {
        log error $"Core bundle creation completed with ($summary.failed) failures"
        exit 1
    } else {
        log info $"Core bundle creation completed successfully"
    }

    return $summary
}

# Bundle a single core component
def bundle_component [
    component: record
    bundle_config: record
    repo_root: string
] {
    log info $"Bundling ($component.name)..."

    if not ($component.source | path exists) {
        log warning $"Source path does not exist: ($component.source)"
        return {
            component: $component.name
            status: "skipped"
            reason: "source not found"
            target: $component.target
        }
    }

    let result = (do {
        # Ensure target directory exists
        let target_parent = ($component.target | path dirname)
        mkdir $target_parent

        if $component.type == "executable" {
            # Copy executable file and make it executable
            cp $component.source $component.target
            chmod +x $component.target
        } else if $component.type == "directory" {
            # Copy directory recursively, excluding development files if requested
            if $bundle_config.exclude_dev {
                copy_directory_filtered $component.source $component.target
            } else {
                cp -r $component.source $component.target
            }
        }

        log info $"Successfully bundled ($component.name) -> ($component.target)"

        {
            component: $component.name
            status: "success"
            source: $component.source
            target: $component.target
            size: (get_directory_size $component.target)
        }
    } | complete)

    if $result.exit_code != 0 {
        log error $"Failed to bundle ($component.name): ($result.stderr)"
        {
            component: $component.name
            status: "failed"
            reason: $result.stderr
            target: $component.target
        }
    } else {
        $result.stdout
    }
}

# Bundle a configuration file
def bundle_config_file [
    config: record
    bundle_config: record
] {
    log info $"Bundling config ($config.name)..."

    if not ($config.source | path exists) {
        log warning $"Config source does not exist: ($config.source)"
        return {
            component: $config.name
            status: "skipped"
            reason: "source not found"
            target: $config.target
        }
    }

    let result = (do {
        # Ensure target directory exists
        let target_parent = ($config.target | path dirname)
        mkdir $target_parent

        # Copy config file or directory
        cp -r $config.source $config.target

        log info $"Successfully bundled config ($config.name) -> ($config.target)"

        {
            component: $config.name
            status: "success"
            source: $config.source
            target: $config.target
        }
    } | complete)

    if $result.exit_code != 0 {
        log error $"Failed to bundle config ($config.name): ($result.stderr)"
        {
            component: $config.name
            status: "failed"
            reason: $result.stderr
            target: $config.target
        }
    } else {
        $result.stdout
    }
}

# Copy directory with filtering for development files
def copy_directory_filtered [source: string, target: string] {
    let exclude_patterns = [
        "*.tmp"
        "*.bak"
        "*~"
        "*.swp"
        ".git*"
        "test_*"
        "*_test.nu"
        "dev_*"
        "debug_*"
    ]

    # Create target directory
    mkdir $target

    # Get all files recursively, excluding patterns
    let files = (^find $source -type f | where {|file|
        let exclude = $exclude_patterns | any {|pattern|
            $file =~ $pattern
        }
        not $exclude
    })

    # Copy each file, preserving directory structure
    $files | each {|file|
        let relative_path = ($file | str replace $source "" | str trim --left --char "/")
        let target_path = ($target | path join $relative_path)
        let target_dir = ($target_path | path dirname)

        mkdir $target_dir
        cp $file $target_path
    }
}

# Validate Nushell syntax in bundled files
def validate_nushell_syntax [bundle_dir: string] {
    log info "Validating Nushell syntax..."

    let nu_files = (^find $bundle_dir -name "*.nu" -type f | lines)
    let validation_results = if ($nu_files | is-empty) {
        []
    } else {
        $nu_files | each {|file|
            # Use nu --ide-check to validate syntax (complete only works on external commands)
            let result = (^nu --ide-check 100 $file | complete)

            if $result.exit_code != 0 {
                log error $"Syntax error in ($file): ($result.stderr)"
                {
                    file: $file
                    status: "failed"
                    error: $result.stderr
                }
            } else {
                { file: $file, status: "success" }
            }
        }
    }

    let validation_errors = ($validation_results | where status == "failed")
    let validated_count = ($validation_results | where status == "success" | length)

    if ($validation_errors | length) > 0 {
        log error $"Found ($validation_errors | length) syntax errors"
        {
            status: "failed"
            validated_files: $validated_count
            total_files: ($nu_files | length)
            errors: $validation_errors
        }
    } else {
        log info $"All ($validated_count) Nushell files passed syntax validation"
        {
            status: "success"
            validated_files: $validated_count
            total_files: ($nu_files | length)
            errors: []
        }
    }
}

# Create bundle metadata
def create_bundle_metadata [
    bundle_config: record
    repo_root: string
    results: list
] {
    let metadata = {
        bundle_version: "2.1.0"
        created_at: (date now | format date "%Y-%m-%d %H:%M:%S")
        created_by: "provisioning-build-system"
        source_commit: (cd $repo_root; git rev-parse HEAD)
        source_branch: (cd $repo_root; git branch --show-current)
        bundle_config: $bundle_config
        components: $results
        total_size: (get_directory_size $bundle_config.output_dir)
    }

    let metadata_file = ($bundle_config.output_dir | path join "bundle-metadata.json")
    $metadata | to json | save $metadata_file

    log info $"Created bundle metadata: ($metadata_file)"
}

# Compress bundle directory
def compress_bundle [bundle_dir: string] {
    log info "Compressing bundle..."

    let bundle_name = ($bundle_dir | path basename)
    let parent_dir = ($bundle_dir | path dirname)
    let archive_name = $"($bundle_name).tar.gz"
    let archive_path = ($parent_dir | path join $archive_name)

    let result = (do {
        cd $parent_dir
        ^tar -czf $archive_name $bundle_name
        { status: "success" }
    } | complete)

    if $result.exit_code != 0 {
        log error $"Failed to compress bundle: ($result.stderr)"
        {
            status: "failed"
            reason: $result.stderr
        }
    } else {
        let original_size = (get_directory_size $bundle_dir)
        let compressed_size = (ls $archive_path | get 0.size)
        let compression_ratio = (($compressed_size | into float) / ($original_size | into float) * 100)

        let ratio_str = ($compression_ratio | math floor | into string) + "% of original"
        log info $"Bundle compressed: ($original_size) -> ($compressed_size) ($ratio_str)"

        {
            status: "success"
            archive_path: $archive_path
            original_size: $original_size
            compressed_size: $compressed_size
            compression_ratio: $compression_ratio
        }
    }
}

# Get directory size recursively
def get_directory_size [dir: string]: int -> int {
    if not ($dir | path exists) {
        return 0
    }

    if ($dir | path type) == "file" {
        return (ls $dir | get 0.size)
    }

    let total_size = (^find $dir -type f | each {|file|
        ls $file | get 0.size
    } | math sum)

    return ($total_size | if $in == null { 0 } else { $in })
}

# Show bundle info
def "main info" [bundle_dir: string = "dist/core"] {
    let bundle_dir = ($bundle_dir | path expand)

    if not ($bundle_dir | path exists) {
        log error $"Bundle directory does not exist: ($bundle_dir)"
        exit 1
    }

    let metadata_file = ($bundle_dir | path join "bundle-metadata.json")
    if ($metadata_file | path exists) {
        open $metadata_file
    } else {
        {
            directory: $bundle_dir
            size: (get_directory_size $bundle_dir)
            files: (^find $bundle_dir -type f | length)
            nu_files: (^find $bundle_dir -name "*.nu" -type f | length)
        }
    }
}
