src/Startup.ps1

# Set module root
$script:ModuleRoot = $PSScriptRoot
# Adjust ProjectRoot calculation based on location
if ((Split-Path $PSScriptRoot -Leaf) -eq 'bin') {
    # If running from bin/, we need to go up two levels to get to repo root
    $script:ProjectRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
} else {
    # If running from module root (AitherZero/), we need to go up one level
    $script:ProjectRoot = Split-Path $PSScriptRoot -Parent
}

# Set environment variables
$env:AITHERZERO_ROOT = $script:ProjectRoot
$env:AITHERZERO_MODULE_ROOT = $script:ModuleRoot
$env:AITHERZERO_INITIALIZED = "1"

# Module loading tracking
$script:LoadedModules = @()
$script:FailedModules = @()
$script:LoadedFunctions = @()
$script:FailedFunctions = @()

# Helper function for logging during module initialization
function Write-InitLog {
    param(
        [string]$Message,
        [ValidateSet('Information', 'Warning', 'Error')]
        [string]$Level = 'Information'
    )

    # Try Write-AitherLog first, fallback to Write-Host/Write-Warning/Write-Error
    if (Get-Command Write-AitherLog -ErrorAction SilentlyContinue) {
        Write-AitherLog -Message $Message -Level $Level -Source 'ModuleLoader'
    }
    elseif ($Level -eq 'Error') {
        Write-Error $Message -ErrorAction Continue
    }
    elseif ($Level -eq 'Warning') {
        Write-Warning $Message
    }
    else {
        # Information level - only show if verbose mode
        if ($VerbosePreference -eq 'Continue' -or $env:AITHERZERO_VERBOSE -eq '1') {
            Write-Information $Message -InformationAction Continue
        }
    }
}

# Performance optimization: Skip transcript in test mode or when disabled
$isTestOrCIMode = ($env:AITHERZERO_DISABLE_TRANSCRIPT -eq '1') -or
                   ($env:AITHERZERO_TEST_MODE) -or
                   ($env:CI)
$script:TranscriptEnabled = -not $isTestOrCIMode

# Start PowerShell transcription for complete activity logging (if enabled)
if ($script:TranscriptEnabled) {
    $transcriptPath = Join-Path $script:ProjectRoot 'AitherZero/library/logs' "transcript-$(Get-Date -Format 'yyyy-MM-dd').log"
    $logsDir = Split-Path $transcriptPath -Parent
    if (-not (Test-Path $logsDir)) {
        New-Item -ItemType Directory -Path $logsDir -Force | Out-Null
    }

    try {
        # Try to start transcript, stop any existing one first
        try {
            Stop-Transcript -ErrorAction Stop | Out-Null
        }
        catch {
            # No active transcript to stop - this is expected
        }
        Start-Transcript -Path $transcriptPath -Append -IncludeInvocationHeader | Out-Null
    } catch {
        # Transcript functionality not available or failed - continue without it
        Write-Verbose "Transcript logging unavailable: $($_.Exception.Message)"
    }
}

# region Package Manager Configuration
# Package Manager Configurations
$script:WindowsPackageManagers = @{
    'winget' = @{
        Command = 'winget'
        Priority = 1
        InstallArgs = @('install', '--id', '{0}', '--exact', '--source', 'winget', '--accept-source-agreements', '--accept-package-agreements', '--silent')
        CheckArgs = @('list', '--id', '{0}', '--exact')
    }
    'chocolatey' = @{
        Command = 'choco'
        Priority = 2
        InstallArgs = @('install', '{0}', '-y')
        CheckArgs = @('list', '{0}', '--exact', '--local-only')
    }
}

$script:LinuxPackageManagers = @{
    'apt' = @{
        Command = 'apt-get'
        Priority = 1
        InstallArgs = @('install', '-y', '{0}')
        CheckArgs = @('list', '--installed', '{0}')
        UpdateArgs = @('update')
    }
    'yum' = @{
        Command = 'yum'
        Priority = 2
        InstallArgs = @('install', '-y', '{0}')
        CheckArgs = @('list', 'installed', '{0}')
    }
    'dnf' = @{
        Command = 'dnf'
        Priority = 2
        InstallArgs = @('install', '-y', '{0}')
        CheckArgs = @('list', '--installed', '{0}')
    }
    'pacman' = @{
        Command = 'pacman'
        Priority = 3
        InstallArgs = @('-S', '--noconfirm', '{0}')
        CheckArgs = @('-Qi', '{0}')
    }
}

$script:MacPackageManagers = @{
    'brew' = @{
        Command = 'brew'
        Priority = 1
        InstallArgs = @('install', '{0}')
        CheckArgs = @('list', '{0}')
        CaskArgs = @('install', '--cask', '{0}')
    }
}

# Software Package Mappings
$script:SoftwarePackages = @{
    'git' = @{
        winget = 'Git.Git'
        chocolatey = 'git'
        apt = 'git'
        yum = 'git'
        dnf = 'git'
        pacman = 'git'
        brew = 'git'
    }
    'nodejs' = @{
        winget = 'OpenJS.NodeJS'
        chocolatey = 'nodejs'
        apt = 'nodejs'
        yum = 'nodejs'
        dnf = 'nodejs'
        pacman = 'nodejs'
        brew = 'node'
    }
    'vscode' = @{
        winget = 'Microsoft.VisualStudioCode'
        chocolatey = 'vscode'
        apt = 'code'
        yum = 'code'
        dnf = 'code'
        pacman = 'code'
        brew = 'visual-studio-code'
        brew_cask = $true
    }
    'python' = @{
        winget = 'Python.Python.3.12'
        chocolatey = 'python'
        apt = 'python3'
        yum = 'python3'
        dnf = 'python3'
        pacman = 'python'
        brew = 'python3'
    }
    '7zip' = @{
        winget = '7zip.7zip'
        chocolatey = '7zip'
        apt = 'p7zip-full'
        yum = 'p7zip'
        dnf = 'p7zip'
        pacman = 'p7zip'
        brew = 'p7zip'
    }
    'azure-cli' = @{
        winget = 'Microsoft.AzureCLI'
        chocolatey = 'azure-cli'
        apt = 'azure-cli'
        yum = 'azure-cli'
        dnf = 'azure-cli'
        pacman = 'azure-cli'
        brew = 'azure-cli'
    }
    'docker' = @{
        winget = 'Docker.DockerDesktop'
        chocolatey = 'docker-desktop'
        apt = 'docker.io'
        yum = 'docker'
        dnf = 'docker'
        pacman = 'docker'
        brew = 'docker'
        brew_cask = $true
    }
    'golang' = @{
        winget = 'GoLang.Go'
        chocolatey = 'golang'
        apt = 'golang-go'
        yum = 'golang'
        dnf = 'golang'
        pacman = 'go'
        brew = 'go'
    }
    'powershell' = @{
        winget = 'Microsoft.PowerShell'
        chocolatey = 'powershell-core'
        apt = 'powershell'
        yum = 'powershell'
        dnf = 'powershell'
        pacman = 'powershell'
        brew = 'powershell'
        brew_cask = $true
    }
}
# endregion Package Manager Configuration

# region ProjectContext + Auto-Load Plugins

# Config cache for ProjectContext resolution
$script:AitherConfig = $null

# Plugin state: Use a .NET class to hold shared state that survives module scope splits.
# PowerShell's compiled PSM1 can create multiple session states, making $script: unreliable
# for variables set AFTER Export-ModuleMember. A static class is a reliable singleton.
if (-not ([System.Management.Automation.PSTypeName]'AitherPluginState').Type) {
    Add-Type -TypeDefinition @'
using System;
using System.Collections;

public static class AitherPluginState {
    private static Hashtable _plugins = new Hashtable(StringComparer.OrdinalIgnoreCase);
    private static System.Collections.Generic.List<string> _scriptPaths = new System.Collections.Generic.List<string>();
    private static System.Collections.Generic.List<string> _playbookPaths = new System.Collections.Generic.List<string>();
    private static Hashtable _config = null;

    public static Hashtable Plugins { get { return _plugins; } }
    public static System.Collections.Generic.List<string> ScriptPaths { get { return _scriptPaths; } }
    public static System.Collections.Generic.List<string> PlaybookPaths { get { return _playbookPaths; } }
    public static Hashtable Config { get { return _config; } set { _config = value; } }

    public static void Reset() {
        _plugins = new Hashtable(StringComparer.OrdinalIgnoreCase);
        _scriptPaths = new System.Collections.Generic.List<string>();
        _playbookPaths = new System.Collections.Generic.List<string>();
        _config = null;
    }
}
'@

} else {
    [AitherPluginState]::Reset()
}

# Backward-compat aliases (some code uses $script: directly)
$script:RegisteredPlugins = [AitherPluginState]::Plugins
$script:PluginScriptPaths = [AitherPluginState]::ScriptPaths
$script:PluginPlaybookPaths = [AitherPluginState]::PlaybookPaths

$_pluginsDir = Join-Path $script:ModuleRoot 'plugins'
if (Test-Path $_pluginsDir) {
    $pluginDirs = Get-ChildItem -Path $_pluginsDir -Directory | Where-Object { $_.Name -notlike '_*' }
    foreach ($pluginDir in $pluginDirs) {
        $manifestPath = Join-Path $pluginDir.FullName 'plugin.psd1'
        if (Test-Path $manifestPath) {
            # Defer actual registration until Register-AitherPlugin is available
            # Store paths for post-init loading
            if (-not $script:_PendingPluginPaths) {
                $script:_PendingPluginPaths = [System.Collections.Generic.List[string]]::new()
            }
            $script:_PendingPluginPaths.Add($pluginDir.FullName)
        }
    }
}
Remove-Variable -Name '_pluginsDir' -ErrorAction SilentlyContinue

# endregion Auto-Load Plugins