Public/Monitor.ps1

# ---------------------------------------------------------------------------
# Monitor - background scanning for dead sessions
# ---------------------------------------------------------------------------

function Start-TTMonitor {
    <#
    .SYNOPSIS
        Start the background session monitor.
    .DESCRIPTION
        Starts a polling loop that periodically checks tracked sessions, archives
        dead ones, updates working directories via PEB, discovers new terminals,
        and triggers sync if configured.
    .PARAMETER AsJob
        Run the monitor as a background PowerShell job (recommended).
    .EXAMPLE
        Start-TTMonitor -AsJob
        Starts the monitor as a background job.
    .EXAMPLE
        Start-TTMonitor
        Runs the monitor in the foreground (Ctrl+C to stop).
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [switch]$AsJob
    )
    Initialize-TTDataStore

    if ($AsJob) {
        # Check for existing running monitor
        $existing = Get-Job -Name 'TerminalTracker-Monitor' -ErrorAction SilentlyContinue |
            Where-Object { $_.State -eq 'Running' }
        if ($existing) {
            Write-Verbose "Monitor already running (Job ID: $($existing.Id))."
            return $existing
        }
        # Clean up dead monitor jobs
        Get-Job -Name 'TerminalTracker-Monitor' -ErrorAction SilentlyContinue |
            Where-Object { $_.State -ne 'Running' } |
            Remove-Job -Force -ErrorAction SilentlyContinue

        # Capture module path as a string BEFORE creating the job
        # $PSScriptRoot is not reliable inside a job scriptblock
        $modulePath = (Get-Module TerminalTracker).Path
        if (-not $modulePath) {
            $modulePath = Join-Path (Split-Path $PSScriptRoot -Parent) 'TerminalTracker.psm1'
        }

        $jobScript = {
            param($ModulePath)
            Import-Module $ModulePath -Force -DisableNameChecking
            while ($true) {
                try { Invoke-TTMonitorCycle } catch { Write-Verbose "Monitor cycle error: $_" }
                $cfg = Get-TTConfig
                Start-Sleep -Seconds $cfg.MonitorIntervalSeconds
            }
        }
        $job = Start-Job -ScriptBlock $jobScript -ArgumentList $modulePath -Name 'TerminalTracker-Monitor'
        Write-Verbose "Monitor started as background job (ID: $($job.Id))."
        return $job
    }
    else {
        Write-Verbose "Running monitor in foreground (Ctrl+C to stop)..."
        while ($true) {
            Invoke-TTMonitorCycle
            $cfg = Get-TTConfig
            Start-Sleep -Seconds $cfg.MonitorIntervalSeconds
        }
    }
}

function Stop-TTMonitor {
    <#
    .SYNOPSIS
        Stop the background session monitor.
    .DESCRIPTION
        Stops the background TerminalTracker-Monitor job if one is running.
        The job is stopped and removed.
    .EXAMPLE
        Stop-TTMonitor
        Stops the background monitor job.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param()
    $jobs = Get-Job -Name 'TerminalTracker-Monitor' -ErrorAction SilentlyContinue
    if ($jobs) {
        $jobs | Stop-Job -PassThru | Remove-Job -Force
        Write-Verbose "Monitor stopped."
    }
    else {
        Write-Verbose "No monitor job found."
    }
}

function Invoke-TTMonitorCycle {
    <#
    .SYNOPSIS
        Run one monitor cycle: clean dead sessions, discover new terminals, update CWDs.
    .DESCRIPTION
        Executes a single iteration of the monitor loop. Archives dead sessions,
        updates live session working directories via PEB, discovers new interactive
        terminals, and exports state to sync path if configured.
    .EXAMPLE
        Invoke-TTMonitorCycle
        Runs one monitor cycle manually.
    #>

    [CmdletBinding()]
    param()
    Initialize-TTDataStore
    $sessions = @(Read-JsonFile $script:SessionsFile)
    $changed = $false

    # Phase 1: Check existing sessions - archive dead ones, update live ones
    $alive = @()
    foreach ($s in $sessions) {
        $running = try { $null -ne (Get-Process -Id $s.Pid -ErrorAction Stop); $true } catch { $false }
        if ($running) {
            # Update CWD from PEB (more reliable than prompt hook for auto-discovered sessions)
            $cwd = Get-ProcessCwd -ProcessId $s.Pid
            if ($cwd -and $cwd -ne $s.WorkingDirectory -and $cwd -ne '(unknown)') {
                # Record directory change in history
                if ($s.WorkingDirectory -and $s.WorkingDirectory -ne '(unknown)') {
                    if (-not $s.DirectoryHistory) {
                        $s | Add-Member -NotePropertyName DirectoryHistory -NotePropertyValue @() -Force
                    }
                    $histEntry = [PSCustomObject]@{ Directory = $s.WorkingDirectory; Timestamp = $s.LastUpdated }
                    $history = @($s.DirectoryHistory) + @($histEntry)
                    if ($history.Count -gt 50) { $history = $history[-50..-1] }
                    $s.DirectoryHistory = $history
                }
                $s.WorkingDirectory = $cwd
                $changed = $true
            }
            $s.LastSeen = [datetime]::UtcNow.ToString('o')
            $alive += $s
        }
        else {
            Add-TTArchiveEntry $s 'closed'
            $changed = $true
        }
    }

    if ($changed) {
        Write-JsonFile $script:SessionsFile $alive
    }

    # Phase 2: Discover new interactive terminals
    $trackedPids = @($alive | ForEach-Object { $_.Pid })
    $discovered = Get-InteractiveTerminal

    # Backfill WTHost on existing sessions that lack it (one-time migration)
    $discoveredByPid = @{}
    foreach ($d in $discovered) { $discoveredByPid[$d.Pid] = $d }
    foreach ($s in $alive) {
        if ($s.AutoDiscovered -and -not $s.WTHost -and $discoveredByPid.ContainsKey($s.Pid)) {
            $wtHost = $discoveredByPid[$s.Pid].WTHost
            if ($wtHost) {
                $s | Add-Member -NotePropertyName WTHost -NotePropertyValue $wtHost -Force
                $changed = $true
            }
        }
    }
    if ($changed) {
        Write-JsonFile $script:SessionsFile $alive
    }

    $newSessions = @()

    foreach ($term in $discovered) {
        if ($term.Pid -in $trackedPids) { continue }
        if ($term.Pid -eq $PID) { continue }

        $newSession = [PSCustomObject]@{
            Id               = [guid]::NewGuid().ToString('N').Substring(0, 8)
            Pid              = $term.Pid
            ProcessName      = $term.ProcessName
            Shell            = $term.Shell
            WindowTitle      = $term.WindowTitle
            WorkingDirectory = $term.WorkingDirectory
            StartTime        = $term.StartTime
            LastUpdated      = [datetime]::UtcNow.ToString('o')
            LastSeen         = [datetime]::UtcNow.ToString('o')
            Notes            = ''
            Hidden           = $false
            Tags             = @()
            DirectoryHistory = @()
            AutoDiscovered   = $true
            WTHost           = $term.WTHost
        }
        $newSessions += $newSession
    }

    if ($newSessions.Count -gt 0) {
        $sessions = @(Read-JsonFile $script:SessionsFile)
        $sessions = @($sessions) + @($newSessions)
        Write-JsonFile $script:SessionsFile $sessions
    }

    # Sync if configured
    $cfg = Get-TTConfig
    if ($cfg.SyncPath) {
        Export-TTState -Path $cfg.SyncPath -Quiet
    }
}