#!/usr/bin/env nu

# Create Distribution Packages Script
# Creates platform-specific distribution packages with nushell binary, plugins, and installers

use lib/common_lib.nu [
    log_info, log_error, log_success, log_warn, log_debug,
    validate_nushell_version, get_current_platform, get_supported_platforms,
    get_binary_extension, get_archive_extension, ensure_dir, remove_dir,
    copy_file, create_checksums, get_workspace_version, create_manifest
]

def main [
    --platform (-p): string = ""        # Target platform (e.g., linux-x86_64, darwin-arm64)
    --output (-o): string = "bin_archives" # Output directory for packages
    --force (-f)                         # Force overwrite existing packages
    --list (-l)                         # List what would be packaged
    --all-platforms                      # Create packages for all platforms
    --checksums                         # Generate checksums for packages
    --bootstrap                         # Include bootstrap installers
    --include-docs                      # Include documentation
    --version (-v): string = ""         # Version override
] {
    # Validate nushell version consistency first
    validate_nushell_version

    # Get version
    let version = if ($version | str length) > 0 {
        $version
    } else {
        get_workspace_version
    }

    if $list {
        list_packageable_components $platform $version
        return
    }

    if $all_platforms {
        create_all_platform_packages $output $force $checksums $bootstrap $include_docs $version
        return
    }

    # Create package for specific or current platform
    let target_platform = if ($platform | str length) > 0 {
        $platform
    } else {
        get_current_platform
    }

    create_platform_package $target_platform $output $force $checksums $bootstrap $include_docs $version
}

# List components that would be packaged
def list_packageable_components [platform: string, version: string] {
    let target_platform = if ($platform | str length) > 0 {
        $platform
    } else {
        get_current_platform
    }

    log_info $"📋 Components for platform: ($target_platform)"
    log_info "================================================"

    # Check nushell binary
    let nushell_components = get_nushell_components $target_platform $version
    log_info "🚀 Nushell Components:"
    $nushell_components.binaries | each {|binary|
        let status = if ($binary.path | path exists) { "✅" } else { "❌" }
        log_info $"  ($status) ($binary.name) - ($binary.path)"
    }

    # Check plugins
    let plugin_components = get_plugin_components $target_platform $version
    log_info ""
    log_info "🔌 Plugin Components:"
    $plugin_components.binaries | each {|binary|
        let status = if ($binary.path | path exists) { "✅" } else { "❌" }
        log_info $"  ($status) ($binary.name) - ($binary.path)"
    }

    # Check configuration templates
    let config_components = get_config_components $version
    log_info ""
    log_info "⚙️  Configuration Components:"
    $config_components | each {|config|
        let status = if ($config.path | path exists) { "✅" } else { "❌" }
        log_info $"  ($status) ($config.name) - ($config.path)"
    }

    # Check installers
    let installer_components = get_installer_components $target_platform
    log_info ""
    log_info "📥 Installer Components:"
    $installer_components | each {|installer|
        let status = if ($installer.path | path exists) { "✅" } else { "❌" }
        log_info $"  ($status) ($installer.name) - ($installer.path)"
    }
}

# Create packages for all supported platforms
def create_all_platform_packages [
    output: string,
    force: bool,
    checksums: bool,
    bootstrap: bool,
    include_docs: bool,
    version: string
] {
    log_info "🌍 Creating packages for all supported platforms..."

    let platforms = get_supported_platforms
    mut successful_packages = []
    mut failed_packages = []

    for platform in $platforms {
        log_info $""
        log_info $"📦 Processing platform: ($platform)"
        log_info "================================"

        let result = try {
            create_platform_package $platform $output $force $checksums $bootstrap $include_docs $version
            "success"
        } catch {|err|
            log_error $"Failed to create package for ($platform): ($err.msg)"
            "failed"
        }

        if $result == "success" {
            $successful_packages = ($successful_packages | append $platform)
        } else {
            $failed_packages = ($failed_packages | append $platform)
        }
    }

    # Summary
    log_info ""
    log_info "📊 Package Creation Summary"
    log_info "========================="
    log_success $"✅ Successful: ($successful_packages | length) platforms"
    if ($successful_packages | length) > 0 {
        $successful_packages | each {|platform| log_success $"  - ($platform)"}
    }

    if ($failed_packages | length) > 0 {
        log_error $"❌ Failed: ($failed_packages | length) platforms"
        $failed_packages | each {|platform| log_error $"  - ($platform)"}
    }

    # Create cross-platform checksums if requested
    if $checksums != null {
        create_cross_platform_checksums $output
    }
}

# Create package for a specific platform
def create_platform_package [
    platform: string,
    output: string,
    force: bool,
    checksums: bool,
    bootstrap: bool,
    include_docs: bool,
    version: string
] {
    log_info $"📦 Creating package for platform: ($platform)"

    # Validate platform
    if not ($platform in (get_supported_platforms)) {
        log_error $"Unsupported platform: ($platform). Supported: (get_supported_platforms | str join ', ')"
        return
    }

    # Setup package directory structure
    # Archive name includes platform, but directory structure is platform-agnostic
    let archive_name = $"nushell-full-($version)-($platform)"
    let package_name = $"nushell-full-($version)"  # Directory name without platform suffix
    let package_dir = $"($output)/($package_name)"
    let archive_extension = get_archive_extension $platform

    # Check if package already exists
    let archive_path = $"($output)/($archive_name)($archive_extension)"
    if ($archive_path | path exists) and ($force == null) {
        log_warn $"Package already exists: ($archive_path). Use --force to overwrite."
        return
    }

    # Clean and create package directory
    ensure_dir $output
    remove_dir $package_dir
    ensure_dir $package_dir

    # Create package structure
    ensure_dir $"($package_dir)/bin"
    ensure_dir $"($package_dir)/config"
    ensure_dir $"($package_dir)/scripts"
    if $include_docs != null {
        ensure_dir $"($package_dir)/docs"
    }

    # Package components
    mut components = {}

    # Package nushell binary
    let nushell_components = package_nushell_components $platform $package_dir $version
    $components = ($components | merge {nushell: $nushell_components})

    # Package plugins
    let plugin_components = package_plugin_components $platform $package_dir $version
    $components = ($components | merge {plugins: $plugin_components})

    # Package configuration
    let config_components = package_config_components $package_dir $version
    $components = ($components | merge {configs: $config_components})

    # Package installers
    let installer_components = package_installer_components $platform $package_dir $bootstrap
    $components = ($components | merge {installers: $installer_components})

    # Package documentation if requested
    if $include_docs != null {
        let docs_components = package_docs_components $package_dir
        $components = ($components | merge {docs: $docs_components})
    }

    # Create manifest
    create_manifest $version $platform $components $"($package_dir)/manifest.json"

    # Create README for package
    create_package_readme $platform $package_dir $version

    # Create archive
    create_package_archive $package_dir $archive_path $platform

    # Create checksums if requested
    if $checksums != null {
        create_checksums [$archive_path] $output
    }

    # Clean up temporary directory
    remove_dir $package_dir

    log_success $"✅ Package created: ($archive_path)"
}

# Package nushell components
def package_nushell_components [platform: string, package_dir: string, version: string] {
    log_info "📦 Packaging nushell components..."

    let components = get_nushell_components $platform $version
    mut packaged = []

    for binary in $components.binaries {
        if ($binary.path | path exists) {
            let dest_path = $"($package_dir)/bin/($binary.name)"
            if (copy_file $binary.path $dest_path) {
                $packaged = ($packaged | append $binary.name)
            }
        } else {
            log_warn $"Nushell binary not found: ($binary.path)"
        }
    }

    {
        binaries: $packaged,
        version: $version,
        platform: $platform
    }
}

# Package plugin components
def package_plugin_components [platform: string, package_dir: string, version: string] {
    log_info "📦 Packaging plugin components..."

    let components = get_plugin_components $platform $version
    mut packaged = []

    for binary in $components.binaries {
        if ($binary.path | path exists) {
            let dest_path = $"($package_dir)/bin/($binary.name)"
            if (copy_file $binary.path $dest_path) {
                $packaged = ($packaged | append $binary.name)
            }
        } else {
            log_warn $"Plugin binary not found: ($binary.path)"
        }
    }

    {
        binaries: $packaged,
        count: ($packaged | length)
    }
}

# Package configuration components
def package_config_components [package_dir: string, version: string] {
    log_info "📦 Packaging configuration components..."

    let components = get_config_components $version
    mut packaged = []

    for config in $components {
        if ($config.path | path exists) {
            let dest_path = $"($package_dir)/config/($config.name)"
            if (copy_file $config.path $dest_path) {
                $packaged = ($packaged | append $config.name)
            }
        } else {
            log_warn $"Configuration file not found: ($config.path)"
        }
    }

    # Create distribution-specific config
    create_distribution_config $package_dir $version
    $packaged = ($packaged | append "distribution_config.toml")

    {
        files: $packaged,
        count: ($packaged | length)
    }
}

# Package installer components
def package_installer_components [platform: string, package_dir: string, bootstrap: bool] {
    log_info "📦 Packaging installer components..."

    let components = get_installer_components $platform
    mut packaged = []

    for installer in $components {
        if ($installer.path | path exists) {
            let dest_path = $"($package_dir)/($installer.name)"
            if (copy_file $installer.path $dest_path) {
                # Make installer executable on Unix-like systems
                if not ($platform | str starts-with "windows") {
                    try { chmod +x $dest_path }
                }
                $packaged = ($packaged | append $installer.name)
            }
        } else {
            log_warn $"Installer not found: ($installer.path)"
        }
    }

    # Create uninstaller
    create_uninstaller $platform $package_dir
    $packaged = ($packaged | append "uninstall.sh")

    {
        files: $packaged,
        bootstrap: $bootstrap
    }
}

# Package documentation components
def package_docs_components [package_dir: string] {
    log_info "📦 Packaging documentation components..."

    let docs = [
        {name: "README.md", path: "./README.md"},
        {name: "LICENSE", path: "./LICENSE"}
    ]

    mut packaged = []

    for doc in $docs {
        if ($doc.path | path exists) {
            let dest_path = $"($package_dir)/docs/($doc.name)"
            if (copy_file $doc.path $dest_path) {
                $packaged = ($packaged | append $doc.name)
            }
        }
    }

    {
        files: $packaged
    }
}

# Get nushell components for platform
def get_nushell_components [platform: string, version: string] {
    let extension = get_binary_extension $platform
    let profile_dir = if ($platform | str contains "release") or true { "release" } else { "debug" }

    # Always use workspace binary (nushell/target/release) as source of truth
    # Never use distribution/ copy as it may be stale from previous builds
    let workspace_binary = $"./nushell/target/($profile_dir)/nu($extension)"

    let nushell_path = if ($workspace_binary | path exists) {
        $workspace_binary
    } else {
        # Fallback: if no workspace binary, try distribution (shouldn't normally happen)
        let dist_binary = $"./distribution/($platform)/nu($extension)"
        if ($dist_binary | path exists) {
            $dist_binary
        } else {
            $workspace_binary  # Will be reported as missing in validation
        }
    }

    {
        binaries: [
            {
                name: $"nu($extension)",
                path: $nushell_path,
                component: "nushell"
            }
        ]
    }
}

# Get plugin components for platform
def get_plugin_components [platform: string, version: string] {
    let extension = get_binary_extension $platform
    let excluded_plugins = (get_excluded_plugins_dist)

    # Get custom plugins from individual plugin target/release directories
    # (Never from distribution dir - that's the staging output, not source)
    let custom_plugin_binaries = (
        glob "nu_plugin_*"
        | where ($it | path type) == "dir"
        | each {|plugin_dir|
            let plugin_name = ($plugin_dir | path basename)
            # Skip excluded plugins
            if $plugin_name in $excluded_plugins {
                null
            } else {
                let binary_name = $"($plugin_name)($extension)"
                mut binary_path = $"($plugin_dir)/target/release/($binary_name)"

                if not ($binary_path | path exists) {
                    # Try debug build as fallback
                    $binary_path = $"($plugin_dir)/target/debug/($binary_name)"
                }

                # Only return if binary actually exists
                if ($binary_path | path exists) {
                    {
                        name: $binary_name,
                        path: $binary_path,
                        component: "plugin"
                    }
                }
            }
        }
        | compact  # Remove null/empty values
    )

    # Get workspace plugins from nushell/target/release
    let workspace_plugins = [
        "nu_plugin_custom_values"
        "nu_plugin_example"
        "nu_plugin_formats"
        "nu_plugin_gstat"
        "nu_plugin_inc"
        "nu_plugin_polars"
        "nu_plugin_query"
        "nu_plugin_stress_internals"
    ]

    let workspace_plugin_binaries = (
        $workspace_plugins
        | where { |p| $p not-in $excluded_plugins }  # Filter excluded plugins
        | each {|plugin_name|
            let binary_name = $"($plugin_name)($extension)"
            let binary_path = $"./nushell/target/release/($binary_name)"

            # Only return if binary actually exists
            if ($binary_path | path exists) {
                {
                    name: $binary_name,
                    path: $binary_path,
                    component: "plugin"
                }
            }
        }
        | compact  # Remove null/empty values
    )

    {
        binaries: ($custom_plugin_binaries | append $workspace_plugin_binaries)
    }
}

# Get configuration components
def get_config_components [version: string] {
    [
        {name: "default_config.nu", path: "./scripts/templates/default_config.nu"},
        {name: "default_env.nu", path: "./scripts/templates/default_env.nu"}
    ]
}

# Get installer components
def get_installer_components [platform: string] {
    if ($platform | str starts-with "windows") {
        [
            {name: "install.ps1", path: "./scripts/templates/install.ps1"}
        ]
    } else {
        [
            {name: "install.sh", path: "./scripts/templates/install.sh"}
        ]
    }
}

# Create distribution-specific configuration
def create_distribution_config [package_dir: string, version: string] {
    let config = {
        distribution: {
            version: $version,
            created_at: (date now | format date "%Y-%m-%d %H:%M:%S UTC"),
            full_distribution: true
        }
    }

    $config | to toml | save -f $"($package_dir)/config/distribution_config.toml"
}

# Create package README
def create_package_readme [platform: string, package_dir: string, version: string] {
    let readme_content = [
        $"# Nushell Full Distribution v($version)",
        "",
        "This package contains a complete Nushell distribution with all plugins and configurations.",
        "",
        "## Platform",
        $"- Target: ($platform)",
        $"- Created: (date now | format date '%Y-%m-%d %H:%M:%S UTC')",
        "",
        "## Contents",
        "",
        "### Binaries (bin/)",
        "- nu - Nushell shell binary",
        "- nu_plugin_* - Plugin binaries",
        "",
        "### Configuration (config/)",
        "- default_config.nu - Default nushell configuration",
        "- default_env.nu - Default environment setup",
        "- distribution_config.toml - Distribution metadata",
        "",
        "### Installation",
        "Run the installer for your platform:",
        "- Unix/Linux/macOS: ./install.sh",
        "- Windows: ./install.ps1",
        "",
        "### Manual Installation",
        "1. Copy binaries from bin/ to a directory in your PATH",
        "2. Copy configuration files from config/ to your nushell config directory",
        "3. Register plugins: nu -c 'plugin add path/to/plugin'",
        "",
        "### Verification",
        "After installation, verify with:",
        "nu --version",
        "",
        "### Uninstallation",
        "Run: ./uninstall.sh (or ./uninstall.ps1 on Windows)",
        "",
        "## Documentation",
        "See docs/ directory for additional documentation.",
        "",
        "## Support",
        "For issues and support, visit: https://github.com/nushell/nushell"
    ] | str join "\n"

    $readme_content | save -f $"($package_dir)/README.md"
}

# Create uninstaller script
def create_uninstaller [platform: string, package_dir: string] {
    let uninstaller_path = if ($platform | str starts-with "windows") {
        "./scripts/templates/uninstall.ps1"
    } else {
        "./scripts/templates/uninstall.sh"
    }

    if ($uninstaller_path | path exists) {
        let dest = if ($platform | str starts-with "windows") {
            $"($package_dir)/uninstall.ps1"
        } else {
            $"($package_dir)/uninstall.sh"
        }
        copy_file $uninstaller_path $dest
    }
}

# Create package archive
def create_package_archive [package_dir: string, archive_path: string, platform: string] {
    log_info $"📦 Creating archive: ($archive_path)"

    let package_name = ($package_dir | path basename)
    let archive_name = ($archive_path | path basename)
    let work_dir = ($package_dir | path dirname)

    # Get absolute paths before changing directory
    let abs_archive_path = ($archive_path | path expand)
    let abs_work_dir = ($work_dir | path expand)

    if ($platform | str starts-with "windows") {
        # Create ZIP archive for Windows
        cd $abs_work_dir
        try {
            run-external "zip" "-r" $archive_name $package_name
        } catch {
            # Fallback to tar if zip not available
            run-external "tar" "-czf" $archive_name $package_name
        }
    } else {
        # Create tar.gz archive for Unix-like systems
        # On macOS: Use --xattrs to preserve extended attributes (code signatures, etc.)
        # On Linux: Standard tar works fine
        cd $abs_work_dir

        let is_macos = ((uname -s | str trim) == "Darwin")
        if $is_macos {
            # macOS: preserve extended attributes
            run-external "tar" "--xattrs" "-czf" $archive_name $package_name
        } else {
            # Linux and others: standard tar
            run-external "tar" "-czf" $archive_name $package_name
        }
    }

    # The archive is created in work_dir, so check there
    let temp_archive = $"($abs_work_dir)/($archive_name)"

    # Check if archive was successfully created
    if ($temp_archive | path exists) {
        # Move archive to final location if different
        if $temp_archive != $abs_archive_path {
            mv $temp_archive $abs_archive_path
        }

        # Verify final archive and report size
        if ($abs_archive_path | path exists) {
            let size = (ls $abs_archive_path | get 0.size)
            log_success $"✅ Archive created: ($archive_path) (($size))"
        } else {
            log_error $"❌ Failed to move archive to: ($archive_path)"
        }
    } else {
        log_error $"❌ Failed to create archive: ($archive_path)"
    }
}

# Create cross-platform checksums
def create_cross_platform_checksums [output: string] {
    log_info "🔐 Creating cross-platform checksums..."

    let archives = glob $"($output)/*.tar.gz" | append (glob $"($output)/*.zip")
    if ($archives | length) > 0 {
        create_checksums $archives $output
        log_success $"✅ Cross-platform checksums created in ($output)/checksums.txt"
    } else {
        log_warn "No archives found for checksum generation"
    }
}

# Load excluded plugins from plugin_registry.toml
def get_excluded_plugins_dist []: nothing -> list<string> {
    try {
        let registry_path = "./etc/plugin_registry.toml"
        if not ($registry_path | path exists) {
            return []
        }

        # Read and parse the registry file
        let registry_content = open $registry_path
        let excluded = try {
            $registry_content.distribution.excluded_plugins
        } catch {
            []
        }

        return $excluded
    } catch {
        # If there's any error reading the registry, return empty list
        # This ensures packaging continues to work even if registry is malformed
        return []
    }
}