<#
.SYNOPSIS
  Converts the Markdown manuscript into a submission-ready .docx using the installed Word application.
.DESCRIPTION
  Markdown is converted to styled HTML (tables become real Word tables), Word opens it, and the document
  is saved as .docx with the formatting Scientometrics asks for: plain 10-pt Times New Roman, no more
  than three heading levels, numbered pages, and continuous line numbering for reviewers.
  Requires Microsoft Word. No third-party converter is used.
.EXAMPLE
  .\ConvertTo-Docx.ps1 -Markdown ..\papers\ai-retractions\00-manuscript.md
#>
[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)][string]$Markdown,
    [string]$OutFile = '',
    [string]$FontName = 'Times New Roman',
    [int]$FontSize = 10,
    [switch]$SingleSpaced,
    [switch]$NoLineNumbers,
    [switch]$NoJustify
)
$ErrorActionPreference = 'Stop'
$src = (Resolve-Path $Markdown).Path
if (-not $OutFile) { $OutFile = [IO.Path]::ChangeExtension($src, '.docx') }
$html = [IO.Path]::ChangeExtension($src, '.htm')

# ---------------- Markdown -> HTML ----------------
function Convert-Inline {
    param([string]$S)
    $S = $S -replace '&', '&amp;' -replace '<', '&lt;' -replace '>', '&gt;'
    $S = [regex]::Replace($S, '\*\*(.+?)\*\*', '<b>$1</b>')
    $S = [regex]::Replace($S, '(?<![\*\w])\*([^\*]+?)\*(?!\*)', '<i>$1</i>')
    $S = [regex]::Replace($S, '`([^`]+?)`', '<span style="font-family:Consolas,monospace">$1</span>')
    $S = [regex]::Replace($S, '(?<!\()\b(https?://[^\s<)]+)', '$1')
    return $S
}

$lines = @(Get-Content -LiteralPath $src -Encoding UTF8)
$out = New-Object System.Text.StringBuilder
[void]$out.AppendLine('<html><head><meta charset="utf-8"><style>')
[void]$out.AppendLine("body{font-family:'$FontName',serif;font-size:${FontSize}pt;}")
[void]$out.AppendLine("h1{font-size:$([int]($FontSize+4))pt;font-weight:bold;}")
[void]$out.AppendLine("h2{font-size:$([int]($FontSize+2))pt;font-weight:bold;}")
[void]$out.AppendLine("h3{font-size:${FontSize}pt;font-weight:bold;font-style:italic;}")
[void]$out.AppendLine("table{border-collapse:collapse;font-size:$([int]($FontSize-1))pt;}")
[void]$out.AppendLine('td,th{border:0.5pt solid #000000;padding:3pt;vertical-align:top;}')
[void]$out.AppendLine('th{font-weight:bold;}')
[void]$out.AppendLine('</style></head><body>')

$inTable = $false
$tableRow = 0
$para = New-Object System.Text.StringBuilder

function Flush-Para {
    if ($script:para.Length -gt 0) {
        [void]$script:out.AppendLine('<p>' + $script:para.ToString().Trim() + '</p>')
        [void]$script:para.Clear()
    }
}
function Close-Table {
    if ($script:inTable) { [void]$script:out.AppendLine('</table>'); $script:inTable = $false; $script:tableRow = 0 }
}

foreach ($raw in $lines) {
    $line = $raw.TrimEnd()
    if ($line -match '^\s*\|') {
        Flush-Para
        # Protect escaped pipes (\|) so a literal | inside a cell, for example in a regular
        # expression alternation, is not mistaken for a column separator.
        $protected = $line -replace '\\\|', "`u{FFFD}PIPE`u{FFFD}"
        $cells = @(($protected.Trim() -replace '^\|', '' -replace '\|$', '') -split '\|')
        $cells = $cells | ForEach-Object { $_ -replace "`u{FFFD}PIPE`u{FFFD}", '|' }
        if (($cells -join '') -match '^[\s\-:]+$') { continue }   # separator row
        if (-not $inTable) { [void]$out.AppendLine('<table>'); $script:inTable = $true; $script:tableRow = 0 }
        $tag = 'td'; if ($tableRow -eq 0) { $tag = 'th' }
        [void]$out.Append('<tr>')
        foreach ($c in $cells) { [void]$out.Append("<$tag>" + (Convert-Inline $c.Trim()) + "</$tag>") }
        [void]$out.AppendLine('</tr>')
        $script:tableRow++
        continue
    }
    Close-Table
    if ($line -match '^\s*$') { Flush-Para; continue }
    if ($line -match '^!\[[^\]]*\]\(([^)]+)\)\s*$') {
        Flush-Para
        $imgRel = $Matches[1]
        $imgAbs = if ([IO.Path]::IsPathRooted($imgRel)) { $imgRel } else { Join-Path (Split-Path $src -Parent) $imgRel }
        if (-not (Test-Path -LiteralPath $imgAbs)) { throw "Figure file not found: $imgAbs" }
        $imgAbs = (Resolve-Path -LiteralPath $imgAbs).Path
        [void]$out.AppendLine('<p>IMGTOKEN:' + $imgAbs + ':ENDIMG</p>')
        continue
    }
    if ($line -match '^---+\s*$') { Flush-Para; [void]$out.AppendLine('<p>&nbsp;</p>'); continue }
    if ($line -match '^###\s+(.*)$') { Flush-Para; [void]$out.AppendLine('<h3>' + (Convert-Inline $Matches[1]) + '</h3>'); continue }
    if ($line -match '^##\s+(.*)$') { Flush-Para; [void]$out.AppendLine('<h2>' + (Convert-Inline $Matches[1]) + '</h2>'); continue }
    if ($line -match '^#\s+(.*)$') { Flush-Para; [void]$out.AppendLine('<h1>' + (Convert-Inline $Matches[1]) + '</h1>'); continue }
    if ($line -match '^>\s?(.*)$') { Flush-Para; [void]$out.AppendLine('<p style="margin-left:24pt">' + (Convert-Inline $Matches[1]) + '</p>'); continue }
    if ($line -match '^\s*[\-\*]\s+(.*)$') { Flush-Para; [void]$out.AppendLine('<p style="margin-left:18pt">&#8226; ' + (Convert-Inline $Matches[1]) + '</p>'); continue }
    if ($line -match '^\s*(\d+)\.\s+(.*)$') { Flush-Para; [void]$out.AppendLine('<p style="margin-left:18pt">' + $Matches[1] + '. ' + (Convert-Inline $Matches[2]) + '</p>'); continue }
    if ($para.Length -gt 0) { [void]$para.Append(' ') }
    [void]$para.Append((Convert-Inline $line))
}
Flush-Para
Close-Table
[void]$out.AppendLine('</body></html>')
Set-Content -LiteralPath $html -Value $out.ToString() -Encoding UTF8
Write-Host "Wrote intermediate HTML: $html" -ForegroundColor DarkGray

# ---------------- HTML -> DOCX via Word ----------------
$word = $null; $doc = $null
try {
    $word = New-Object -ComObject Word.Application
    $word.Visible = $false
    $word.DisplayAlerts = 0
    $doc = $word.Documents.Open($html, [ref]$false, [ref]$false)

    # base font
    $doc.Content.Font.Name = $FontName
    $doc.Content.Font.Size = $FontSize

    # ---- alignment ------------------------------------------------------------------
    # Justify body text unless -NoJustify. Headings, table cells and figure paragraphs are
    # restored to their correct alignment afterwards, because blanket justification would
    # stretch short heading lines and un-centre the artwork.
    if (-not $NoJustify) {
        $doc.Content.ParagraphFormat.Alignment = 3      # wdAlignParagraphJustify
    }
    # widow and orphan control: never leave a single line stranded across a page break
    $doc.Content.ParagraphFormat.WidowControl = $true

    # ---- embed figures: replace IMGTOKEN paragraphs with pictures stored in the file ----
    $ps0 = $doc.PageSetup
    $textWidthPt = $ps0.PageWidth - $ps0.LeftMargin - $ps0.RightMargin
    $figCount = 0
    # iterate in reverse so inserting/removing does not shift indices ahead of us
    for ($p = $doc.Paragraphs.Count; $p -ge 1; $p--) {
        $para = $doc.Paragraphs.Item($p)
        $txt = $para.Range.Text
        if ($txt -match 'IMGTOKEN:(.+?):ENDIMG') {
            $path = $Matches[1]
            if (-not (Test-Path -LiteralPath $path)) { throw "Figure file not found at embed time: $path" }
            $rng = $para.Range
            # back off the paragraph mark so clearing the text does not merge this
            # paragraph into the caption that follows it
            [void]$rng.MoveEnd(1, -1)                        # wdCharacter
            $rng.Text = ''
            # LinkToFile=false, SaveWithDocument=true -> image bytes live inside the .docx
            $shape = $doc.InlineShapes.AddPicture($path, $false, $true, $rng)
            $ratio = $shape.Height / $shape.Width
            $shape.Width = $textWidthPt
            $shape.Height = $textWidthPt * $ratio
            $shape.Range.Paragraphs.Item(1).Alignment = 1     # centered image only
            $figCount++
        }
    }

    # paragraph spacing: double-spaced body for review, single inside tables
    if (-not $SingleSpaced) {
        $doc.Content.ParagraphFormat.LineSpacingRule = 2   # wdLineSpaceDouble
    }
    for ($i = 1; $i -le $doc.Tables.Count; $i++) {
        $t = $doc.Tables.Item($i)
        $t.Range.ParagraphFormat.LineSpacingRule = 0       # wdLineSpaceSingle
        $t.Range.ParagraphFormat.Alignment = 0             # left: never justify cell text
        $t.Range.Font.Size = $FontSize - 1
        $t.Rows.Item(1).HeadingFormat = $true
        $t.Rows.AllowBreakAcrossPages = $false             # keep a row intact across pages
        $t.PreferredWidthType = 2                          # wdPreferredWidthPercent
        $t.PreferredWidth = 100
    }

    # Headings left-aligned and kept with the text that follows them.
    foreach ($para in $doc.Paragraphs) {
        $sty = ""
        try { $sty = [string]$para.Style.NameLocal } catch { }
        if ($sty -like "Heading*") {
            $para.Alignment = 0                            # wdAlignParagraphLeft
            $para.KeepWithNext = $true
            $para.SpaceBefore = 12
            $para.SpaceAfter = 6
        }
    }

    # Figure captions and table captions: keep with their object, do not justify a caption
    # that is only a line or two long.
    foreach ($para in $doc.Paragraphs) {
        $txt = $para.Range.Text
        if ($txt -match '^(Fig\. \d+|Table \d+)\b') {
            $para.Alignment = 0
            $para.KeepWithNext = $true
        }
    }

    # page setup, line numbers, page numbers
    $ps = $doc.PageSetup
    $ps.TopMargin = 72; $ps.BottomMargin = 72; $ps.LeftMargin = 72; $ps.RightMargin = 72
    $ps.LineNumbering.Active = -not $NoLineNumbers
    if (-not $NoLineNumbers) {
        $ps.LineNumbering.CountBy = 1
        $ps.LineNumbering.RestartMode = 2                  # wdRestartContinuous
    }
    $footer = $doc.Sections.Item(1).Footers.Item(1)         # wdHeaderFooterPrimary
    $footer.Range.ParagraphFormat.Alignment = 1             # centred
    [void]$footer.Range.Fields.Add($footer.Range, 33)       # wdFieldPage

    $doc.SaveAs2($OutFile, 12)                             # wdFormatXMLDocument
    $words = $doc.ComputeStatistics(0)                     # wdStatisticWords
    $pages = $doc.ComputeStatistics(2)                     # wdStatisticPages
    $tables = $doc.Tables.Count
    $doc.Close($false)
    Write-Host ("Wrote {0}" -f $OutFile) -ForegroundColor Green
    Write-Host ("  words: {0} | pages: {1} | Word tables: {2} | embedded figures: {3} | font: {4} {5}pt | line numbers: on" -f $words, $pages, $tables, $figCount, $FontName, $FontSize) -ForegroundColor Yellow
}
finally {
    if ($word) { $word.Quit(); [void][Runtime.InteropServices.Marshal]::ReleaseComObject($word) }
    [GC]::Collect(); [GC]::WaitForPendingFinalizers()
    Remove-Item -LiteralPath $html -Force -ErrorAction SilentlyContinue
}
