Public/SuspendResume.ps1

# ---------------------------------------------------------------------------
# Save-and-Close / Suspend / Resume
# ---------------------------------------------------------------------------

function Suspend-TTSession {
    <#
    .SYNOPSIS
        Save session state and close the terminal window. Ready to resume later.
    .DESCRIPTION
        Captures the session's working directory, notes, tags, and directory history,
        then closes the terminal process. The session can be restored later with
        Resume-TTSession.
    .PARAMETER Id
        Session ID (prefix match supported). Omit to suspend the current session.
    .PARAMETER Note
        Resume note to attach before suspending.
    .EXAMPLE
        Suspend-TTSession
        Suspends the current terminal session.
    .EXAMPLE
        Suspend-TTSession -Id 'abc12345' -Note 'Continue after lunch'
        Suspends a specific session with a resume note.
    #>

    [CmdletBinding()]
    param(
        [string]$Id,
        [string]$Note
    )
    Initialize-TTDataStore

    if (-not $Id) {
        # Suspend current session
        Update-TTSession
        $sessions = @(Read-JsonFile $script:SessionsFile)
        $target = $sessions | Where-Object { $_.Pid -eq $PID }
        if (-not $target) { Write-Warning "Current session not tracked. Run Update-TTSession first."; return }
        $target = @($target)[0]
    }
    else {
        $sessions = @(Read-JsonFile $script:SessionsFile)
        $target = $sessions | Where-Object { $_.Id -like "$Id*" }
        if (-not $target -or @($target).Count -ne 1) { Write-Warning "Session not found or ambiguous."; return }
        $target = @($target)[0]
    }

    if ($Note) { $target.Notes = $Note }

    # Save to suspended list
    $suspended = @(Read-JsonFile $script:SuspendedFile)
    $suspendEntry = [PSCustomObject]@{
        Id               = $target.Id
        Shell            = $target.Shell
        ProcessName      = $target.ProcessName
        WorkingDirectory = $target.WorkingDirectory
        WindowTitle      = $target.WindowTitle
        Notes            = $target.Notes
        Tags             = $target.Tags
        DirectoryHistory = $target.DirectoryHistory
        SuspendedTime    = [datetime]::UtcNow.ToString('o')
        OriginalStartTime = $target.StartTime
    }

    # Replace existing suspend for same ID, or add new
    $suspended = @($suspended | Where-Object { $_.Id -ne $target.Id }) + @($suspendEntry)
    Write-JsonFile $script:SuspendedFile $suspended

    # Archive and remove from active
    Add-TTArchiveEntry $target 'suspended'
    $sessions = @($sessions | Where-Object { $_.Id -ne $target.Id })
    Write-JsonFile $script:SessionsFile $sessions

    Write-Verbose "Session $($target.Id) suspended."
    Write-Verbose " Directory: $($target.WorkingDirectory)"
    Write-Verbose " Notes: $($target.Notes)"
    Write-Verbose "Use Resume-TTSession to restore it."

    # If suspending current session, close the window after a brief delay
    if (-not $Id -or $target.Pid -eq $PID) {
        Write-Warning "This window will close in 3 seconds..."
        Start-Sleep -Seconds 3
        Stop-Process -Id $PID -Force
    }
    else {
        # Try to close the other window gracefully
        $proc = Get-Process -Id $target.Pid -ErrorAction SilentlyContinue
        if ($proc) {
            $proc.CloseMainWindow() | Out-Null
        }
    }
}

function Get-TTSuspended {
    <#
    .SYNOPSIS
        List suspended sessions awaiting resume.
    .DESCRIPTION
        Returns all sessions that have been suspended via Suspend-TTSession and
        are waiting to be resumed. Each entry includes the saved working directory,
        notes, tags, and suspend timestamp.
    .EXAMPLE
        Get-TTSuspended
        Returns all suspended sessions.
    #>

    [CmdletBinding()]
    param()
    Initialize-TTDataStore
    $entries = @(Read-JsonFile $script:SuspendedFile)
    # Filter out corrupt/empty entries
    @($entries | Where-Object { $_.Id -and $_.WorkingDirectory })
}

function Resume-TTSession {
    <#
    .SYNOPSIS
        Restore a suspended session - opens a new terminal at the saved directory.
    .DESCRIPTION
        Opens a new terminal window at the saved working directory for a suspended
        session. Restores the shell type, window title, and displays any saved notes.
        The session is removed from the suspended list after resuming.
    .PARAMETER Id
        Suspended session ID (prefix match supported).
    .PARAMETER All
        Resume all suspended sessions at once.
    .EXAMPLE
        Resume-TTSession -Id 'abc12345'
        Resumes a specific suspended session.
    .EXAMPLE
        Resume-TTSession -All
        Resumes all suspended sessions.
    #>

    [CmdletBinding()]
    param(
        [string]$Id,
        [switch]$All
    )
    Initialize-TTDataStore
    $suspended = @(Read-JsonFile $script:SuspendedFile)
    if ($suspended.Count -eq 0) { Write-Verbose "No suspended sessions."; return }

    if ($All) {
        $toResume = $suspended
    }
    elseif ($Id) {
        $toResume = @($suspended | Where-Object { $_.Id -like "$Id*" })
        if ($toResume.Count -eq 0) { Write-Warning "Suspended session '$Id' not found."; return }
        if ($toResume.Count -gt 1) { Write-Warning "Ambiguous ID '$Id'."; return }
    }
    else {
        # Show list and let user pick, or resume all
        Write-Output "Suspended sessions:"
        $i = 1
        foreach ($s in $suspended) {
            Write-Output " [$i] $($s.Id) - $($s.WorkingDirectory)"
            if ($s.Notes) { Write-Output " Notes: $($s.Notes)" }
            $i++
        }
        Write-Output "`nUse Resume-TTSession -Id <id> or Resume-TTSession -All"
        return
    }

    foreach ($s in $toResume) {
        Open-TerminalAt -Directory $s.WorkingDirectory -Shell $s.ProcessName -Title $s.WindowTitle -Note $s.Notes
        Write-Verbose "Resumed: $($s.Id) at $($s.WorkingDirectory)"
        if ($s.Notes) { Write-Verbose " Notes: $($s.Notes)" }
    }

    # Remove resumed sessions from suspended list
    $resumedIds = $toResume | ForEach-Object { $_.Id }
    $suspended = @($suspended | Where-Object { $_.Id -notin $resumedIds })
    Write-JsonFile $script:SuspendedFile $suspended
}

function Open-TerminalAt {
    <#
    .SYNOPSIS
        Open a new terminal window at a specific directory.
    .DESCRIPTION
        Launches a new terminal window at the specified directory. Prefers Windows
        Terminal if available, falling back to direct shell launch. Supports
        PowerShell, CMD, and Bash shells.
    .PARAMETER Directory
        The directory to open the terminal in.
    .PARAMETER Shell
        The shell process name (pwsh, powershell, cmd, bash). Defaults to pwsh.
    .PARAMETER Title
        Window title to set on the new terminal.
    .PARAMETER Note
        Note text to display when the terminal opens.
    .EXAMPLE
        Open-TerminalAt -Directory 'C:\Projects\MyApp'
        Opens a new PowerShell terminal at the specified directory.
    .EXAMPLE
        Open-TerminalAt -Directory '~' -Shell 'cmd'
        Opens a Command Prompt at the home directory.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$Directory,
        [string]$Shell = 'pwsh',
        [string]$Title,
        [string]$Note
    )

    if (-not (Test-Path $Directory)) {
        Write-Warning "Directory '$Directory' does not exist. Opening in home directory."
        $Directory = $env:USERPROFILE
    }

    # Build startup script for -EncodedCommand (prevents command injection via Title/Note)
    $modulePath = (Get-Module TerminalTracker).Path
    $startupScript = @()
    if ($Title) { $startupScript += "`$Host.UI.RawUI.WindowTitle = '$($Title -replace "'", "''")'" }
    if ($Note) {
        $startupScript += "Write-Output '--- Session Resume Notes ---'"
        $startupScript += "Write-Output '$($Note -replace "'", "''")'"
        $startupScript += "Write-Output '----------------------------'"
    }
    $startupScript += "Set-Location -LiteralPath '$($Directory -replace "'", "''")'"
    if ($modulePath) {
        $startupScript += "Import-Module '$($modulePath -replace "'", "''")' -Force -DisableNameChecking"
    }
    $startupScript += "if (Get-Command Update-TTSession -ErrorAction SilentlyContinue) { Update-TTSession }"
    $scriptText = $startupScript -join "`r`n"
    $encoded = [System.Convert]::ToBase64String(
        [System.Text.Encoding]::Unicode.GetBytes($scriptText)
    )

    # Resolve shell executable
    $shellExe = switch -Wildcard ($Shell) {
        'pwsh*'        { 'pwsh.exe' }
        'powershell*'  { 'powershell.exe' }
        'cmd*'         { 'cmd.exe' }
        'bash*'        { 'bash.exe' }
        default        { 'pwsh.exe' }
    }

    # Prefer Windows Terminal if available
    $wt = Get-Command wt.exe -ErrorAction SilentlyContinue
    if ($wt) {
        if ($shellExe -eq 'cmd.exe') {
            Start-Process wt.exe -ArgumentList "new-tab -d `"$Directory`" cmd.exe"
        }
        elseif ($shellExe -eq 'bash.exe') {
            Start-Process wt.exe -ArgumentList "new-tab -d `"$Directory`" bash.exe"
        }
        else {
            Start-Process wt.exe -ArgumentList "new-tab -d `"$Directory`" $shellExe -NoExit -EncodedCommand $encoded"
        }
    }
    else {
        # Fallback: direct launch
        switch -Wildcard ($Shell) {
            'cmd*' {
                Start-Process cmd.exe -ArgumentList "/K cd /d `"$Directory`"" -WorkingDirectory $Directory
            }
            'bash*' {
                Start-Process bash.exe -ArgumentList "--login" -WorkingDirectory $Directory
            }
            default {
                Start-Process $shellExe -ArgumentList "-NoExit -EncodedCommand $encoded"
            }
        }
    }
}