functions/Core/Write-Log.ps1

function Write-Log {
    <#
    .SYNOPSIS
    Centralized logging function with CSV format and sensitive data masking.

    .DESCRIPTION
    Writes structured log entries to CSV file with automatic daily rotation and sensitive data masking.
    Logs are stored in logs/log_YYYY-MM-DD.csv with automatic 7-day retention.

    .PARAMETER Message
    Log message content.

    .PARAMETER Level
    Log level: Error, Warning, Info, Debug, Verbose (hierarchical order).

    .PARAMETER Caller
    Optional caller identifier (auto-populated from call stack if not provided).

    .EXAMPLE
    Write-Log -Message "Operation completed successfully" -Level Info

    .EXAMPLE
    Write-Log -Message "Failed to connect to server" -Level Error

    .NOTES
    Requires helper functions: _CleanupOldLogs, _MaskSensitiveData, _TestLogLevel
    CSV daily rotation with 7-day retention policy
    Encoding: UTF8 with proper CSV escaping for special characters
    Supports -WhatIf for dry-run logging verification
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Message,

        [Parameter(Mandatory = $false)]
        [ValidateSet('Error', 'Warning', 'Info', 'Debug', 'Verbose')]
        [string]
        $Level = 'Info',

        [Parameter(Mandatory = $false)]
        [string]
        $Caller
    )

    $ErrorActionPreference = 'Stop'

    try {
        # Determine log directory - handle both module and direct script contexts
        $logDir = $null

        if ($PSScriptRoot) {
            # Calculate project root: functions/Core -> ../../ = project root
            $projectRoot = Split-Path -Path $PSScriptRoot -Parent | Split-Path -Parent
            $logDir = Join-Path -Path $projectRoot -ChildPath 'logs'

            # Verify we got a reasonable path (should contain 'WinHarden' or 'logs')
            if (-not ($logDir -like '*logs')) {
                $logDir = $null
            }
        }

        # Fallback: use current directory
        if (-not $logDir) {
            $logDir = Join-Path -Path (Get-Location) -ChildPath 'logs'
        }

        # Create log directory if needed
        if (-not (Test-Path -Path $logDir -PathType Container)) {
            $null = New-Item -ItemType Directory -Path $logDir -Force -ErrorAction SilentlyContinue
        }

        # Log file with daily rotation
        $dateString = (Get-Date -Format 'yyyy-MM-dd')
        $logFile = Join-Path -Path $logDir -ChildPath "log_$dateString.csv"

        # Cleanup old logs (7-day retention)
        _CleanupOldLogs -LogDir $logDir

        # Determine caller info from call stack if not provided
        if (-not $Caller) {
            $callStack = Get-PSCallStack
            if ($callStack.Count -gt 1) {
                $Caller = $callStack[1].FunctionName
            }
            else {
                $Caller = 'Unknown'
            }
        }

        # Mask sensitive data in message
        $maskedMessage = _MaskSensitiveData -InputString $Message

        # Get Function and LineNumber from call stack for logging context
        $callStack = Get-PSCallStack
        $callerFunction = if ($callStack.Count -gt 1) {
            $callStack[1].FunctionName
        }
        else {
            'Unknown'
        }
        $callerLineNumber = if ($callStack.Count -gt 1) {
            $callStack[1].ScriptLineNumber
        }
        else {
            0
        }

        # Prepare CSV entry (6 columns per ADR-005)
        $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'
        $csvEntry = [ordered]@{
            'Timestamp' = $timestamp
            'Level' = $Level
            'Caller' = $Caller
            'Function' = $callerFunction
            'LineNumber' = $callerLineNumber
            'Message' = $maskedMessage
        }

        # Handle -WhatIf parameter (gracefully handle in both Interactive and NonInteractive modes)
        if ($WhatIfPreference) {
            Write-Verbose "WhatIf: Would log '$Message' (level: $Level) to $logFile"
            return
        }

        # Write header if file doesn't exist
        if (-not (Test-Path -Path $logFile -PathType Leaf)) {
            try {
                $header = $csvEntry.Keys -join ','
                Add-Content -Path $logFile -Value $header -Encoding UTF8 -ErrorAction Stop
            }
            catch {
                Write-Error -Message "Failed to write log header: $_" -ErrorAction Continue
            }
        }

        # Append CSV entry
        $values = $csvEntry.Values | ForEach-Object {
            # Escape CSV values
            if ($_ -match '[",\r\n]') {
                '"' + ($_ -replace '"', '""') + '"'
            }
            else {
                $_
            }
        }
        $csvRow = $values -join ','

        try {
            Add-Content -Path $logFile -Value $csvRow -Encoding UTF8 -ErrorAction Stop
        }
        catch {
            Write-Error -Message "Failed to append log entry: $_" -ErrorAction Continue
        }

        # Also output to PowerShell stream based on level
        if (_TestLogLevel -Level $Level) {
            switch ($Level) {
                'Error' {
                    Write-Error -Message $maskedMessage -ErrorAction Continue
                }
                'Warning' {
                    Write-Warning -Message $maskedMessage
                }
                'Debug' {
                    Write-Debug -Message $maskedMessage
                }
                'Verbose' {
                    Write-Verbose -Message $maskedMessage
                }
                'Info' {
                    Write-Verbose -Message $maskedMessage
                }
            }
        }

    }
    catch {
        # Fallback: write to stderr if log file fails
        Write-Error -Message "Logging failed: $($_.Exception.Message)"
    }
}