Public/Import-TerminalPresentation.ps1

function Set-TerminalImportedSourceDirectory {
    param(
        [Parameter(Mandatory)][TerminalSlides.Schema.V1.TerminalPresentation]$Presentation,
        [Parameter(Mandatory)][string]$SourcePath
    )

    $basePath = Split-Path -Path $SourcePath -Parent
    foreach ($element in @($Presentation.Slides.Elements | Where-Object Kind -eq Image)) {
        if (-not [System.IO.Path]::IsPathRooted($element.Payload.Path)) {
            Set-TerminalMediaOrigin -Element $element -Directory $basePath
        }
    }
    return $Presentation
}

function Import-TerminalPowerShellDataSnapshot {
    param([Parameter(Mandatory)][byte[]]$Bytes)

    [void](ConvertFrom-TerminalUtf8Bytes -Bytes $Bytes -RemoveByteOrderMark)
    $snapshotPath = Join-Path ([IO.Path]::GetTempPath()) ('terminalslides-' + [guid]::NewGuid().ToString('N') + '.psd1')
    try {
        $stream = [IO.FileStream]::new(
            $snapshotPath,
            [IO.FileMode]::CreateNew,
            [IO.FileAccess]::Write,
            [IO.FileShare]::Read
        )
        try {
            $stream.Write($Bytes, 0, $Bytes.Length)
            $stream.Flush($true)
        }
        finally { $stream.Dispose() }
        return Microsoft.PowerShell.Utility\Import-PowerShellDataFile -LiteralPath $snapshotPath
    }
    finally {
        Remove-Item -LiteralPath $snapshotPath -Force -ErrorAction SilentlyContinue
    }
}

function Import-TerminalPresentation {
    [CmdletBinding()]
    [OutputType([object])]
    param([Parameter(Mandatory)][string]$Path)

    try {
        $resolvedPath = if ([System.IO.Path]::IsPathRooted($Path)) { $Path } else { Join-Path (Get-Location) $Path }
        if (-not (Test-Path -LiteralPath $resolvedPath -PathType Leaf)) { throw "Path '$resolvedPath' was not found." }
        $extension = [System.IO.Path]::GetExtension($resolvedPath).ToLowerInvariant()
        switch ($extension) {
            '.psd1' {
                $data = Import-TerminalPowerShellDataSnapshot -Bytes ([IO.File]::ReadAllBytes($resolvedPath))
                if ($data.ContainsKey('TerminalSlidesEnvelope')) {
                    $presentation = New-PresentationFromData -Data (ConvertFrom-TerminalDataMarker -Marker $data.TerminalSlidesEnvelope)
                    return Set-TerminalImportedSourceDirectory -Presentation $presentation -SourcePath $resolvedPath
                }
                if ($data.ContainsKey('TerminalSlidesData')) {
                    $presentation = New-PresentationFromData -Data (ConvertFrom-TerminalDataMarker -Marker $data.TerminalSlidesData)
                    return Set-TerminalImportedSourceDirectory -Presentation $presentation -SourcePath $resolvedPath
                }
                if ($data.ContainsKey('Json')) {
                    $presentation = New-PresentationFromData -Data (ConvertFrom-TerminalWireJson -Json ([string]$data.Json))
                    return Set-TerminalImportedSourceDirectory -Presentation $presentation -SourcePath $resolvedPath
                }
                $presentation = New-PresentationFromData -Data $data
                return Set-TerminalImportedSourceDirectory -Presentation $presentation -SourcePath $resolvedPath
            }
            '.json' {
                $json = ConvertFrom-TerminalUtf8Bytes -Bytes ([IO.File]::ReadAllBytes($resolvedPath)) -RemoveByteOrderMark
                $presentation = New-PresentationFromData -Data (ConvertFrom-TerminalWireJson -Json $json)
                return Set-TerminalImportedSourceDirectory -Presentation $presentation -SourcePath $resolvedPath
            }
            '.md' { }
            '.markdown' { }
            default { throw "Unsupported presentation format '$extension'." }
        }
        $content = ConvertFrom-TerminalUtf8Bytes -Bytes ([IO.File]::ReadAllBytes($resolvedPath)) -RemoveByteOrderMark
        $canonicalMatch = [regex]::Match($content, '<!--\s*terminalslides:envelope\s+(?<data>[A-Za-z0-9+/=]+)\s*-->\s*\z')
        if ($canonicalMatch.Success) {
            $marker = ConvertFrom-TerminalDataMarker -Marker $canonicalMatch.Groups['data'].Value
            Assert-TerminalMarkdownEnvelope -Envelope $marker
            $visibleDocument = $content.Remove($canonicalMatch.Index, $canonicalMatch.Length)
            if ([uint64]$marker.MarkerVersion -eq 2) {
                $actualHash = Get-TerminalMarkdownProjectionHash -VisibleDocument $visibleDocument -PresentationData $marker.Presentation
                if ($actualHash -cne $marker.ProjectionHash) {
                    throw 'The visible Markdown was edited, or its embedded presentation no longer matches, after export.'
                }
            }
            $sourceThemeName = [string]$marker.Presentation.Presentation.Theme
            $presentation = New-PresentationFromData -Data $marker.Presentation
            if ([uint64]$marker.MarkerVersion -eq 1 -and -not
                (Test-TerminalMarkdownV1Projection -VisibleDocument $visibleDocument -Presentation $presentation -SourceThemeName $sourceThemeName)) {
                throw 'The visible Markdown was edited after export. Remove the terminalslides:envelope marker to import the visible Markdown dialect, or re-export the deck.'
            }
            return Set-TerminalImportedSourceDirectory -Presentation $presentation -SourcePath $resolvedPath
        }
        if ($content -match '<!--\s*terminalslides:envelope(?:\s|-->)') {
            throw 'The Markdown TerminalSlides envelope is malformed or is not the trailing document envelope.'
        }
        if ($content -match '<!--\s*terminalslides:data\s+') {
            throw 'Legacy Markdown data markers have no integrity binding. Remove the terminalslides:data marker to import the visible Markdown dialect.'
        }
        $title = 'Imported Presentation'
        $author = $null
        $theme = 'Midnight'
        $frontmatterMatch = [regex]::Match($content, '(?ms)^---\s*\r?\n(?<frontmatter>.*?)\r?\n---\s*(\r?\n)?')
        if ($frontmatterMatch.Success) {
            $frontmatter = $frontmatterMatch.Groups['frontmatter'].Value
            foreach ($line in ($frontmatter -split "`n")) {
                if ($line -match '^(title|author|theme):\s*(.+)$') {
                    $name = $matches[1]
                    $rawValue = $matches[2].Trim()
                    try { $value = ConvertFrom-TerminalJsonValue -Json $rawValue }
                    catch { $value = $rawValue }
                    switch ($name) {
                        'title' { $title = [string]$value }
                        'author' { $author = [string]$value }
                        'theme' { $theme = [string]$value }
                    }
                }
            }
            $content = $content.Substring($frontmatterMatch.Length).TrimStart()
        }
        $presentation = New-TerminalPresentation -Title $title -Author $author -Theme $theme
        $slideBuffer = [System.Collections.Generic.List[string]]::new()
        $slides = [System.Collections.Generic.List[string]]::new()
        $inFence = $false
        foreach ($line in ($content -split "\r?\n")) {
            if ($line.TrimStart() -match '^```') { $inFence = -not $inFence }
            if ($line.Trim() -eq '---' -and -not $inFence) {
                $candidate = ($slideBuffer -join "`n").Trim()
                if ($candidate) { $slides.Add($candidate) }
                $slideBuffer.Clear()
                continue
            }
            $slideBuffer.Add($line)
        }
        $candidate = ($slideBuffer -join "`n").Trim()
        if ($candidate) { $slides.Add($candidate) }
        foreach ($slideText in $slides) {
            $trimmed = $slideText.Trim()
            if (-not $trimmed) { continue }
            $lines = $trimmed -split "`n"
            $slideTitle = ($lines | Where-Object { $_ -match '^#\s+' } | Select-Object -First 1)
            $slideTitle = if ($slideTitle) { $slideTitle -replace '^#\s+', '' } else { 'Slide' }
            $presentation | Add-TerminalSlide -Title $slideTitle -Content {
                $inCode = $false
                $codeLang = 'text'
                $codeLines = [System.Collections.Generic.List[string]]::new()
                foreach ($line in $lines) {
                    $current = $line.TrimEnd("`r")
                    if ($current -match '^#\s+') { continue }
                    if ($current -match '^```(.*)$') {
                        if (-not $inCode) {
                            $inCode = $true
                            $codeLang = if ($matches[1]) { $matches[1].Trim() } else { 'text' }
                        }
                        else {
                            Add-SlideCode -Code ($codeLines -join "`n") -Language $codeLang
                            $codeLines.Clear()
                            $inCode = $false
                        }
                        continue
                    }
                    if ($inCode) { $codeLines.Add($current); continue }
                    if ($current -match '^##\s+') { Add-SlideTitle ($current -replace '^##\s+', ''); continue }
                    if ($current -match '^###\s+') { Add-SlideSubtitle ($current -replace '^###\s+', ''); continue }
                    if ($current -match '^-\s+') { Add-SlideBullet ($current -replace '^-\s+', ''); continue }
                    if ($current -match '^>\s+') { Add-SlideQuote -Text ($current -replace '^>\s+', ''); continue }
                    if ($current -match '^<!--\s*Notes:\s*(.*?)\s*-->$') { Add-SlideNotes $matches[1]; continue }
                    if ($current.Trim()) { Add-SlideText $current.Trim() }
                }
                if ($inCode -and $codeLines.Count -gt 0) {
                    Add-SlideCode -Code ($codeLines -join "`n") -Language $codeLang
                }
            } | Out-Null
        }
        return Set-TerminalImportedSourceDirectory -Presentation $presentation -SourcePath $resolvedPath
    }
    catch {
        throw
    }
}