// SPDX-FileCopyrightText: 2026 Brent Daignault
// SPDX-License-Identifier: GPL-3.0-or-later

import CoreGraphics
import Foundation

/// Saddle-stitch imposition scatters two logical pages across each physical
/// sheet side, and the two halves of any one physical page are produced at
/// opposite ends of the content-generation order (e.g. the cover is logical
/// page 1, but it shares a physical sheet with the very last logical page).
/// Core Graphics' PDF writer is streaming — once a page is closed its
/// content is finalised — so it cannot be driven directly in that scattered
/// order the way libharu's retained-page model could.
///
/// The fix is a two-stage render:
///   1. Render every logical page into its own small, self-contained
///      single-page PDF, held in memory.
///   2. Walk physical sheets in order; for each one, composite the two
///      logical pages that belong there using `drawPDFPage`.
enum BookletRenderer {
    static func render(document: RosterDocument, map: PageMap, to outputPath: String) {
        let layout = Layout.booklet
        let n = map.totalPageCount
        let sheetCount = n / 4

        // ── Stage 1: one intermediate PDF per logical page ──────────────────
        var intermediates: [CGPDFDocument] = []
        intermediates.reserveCapacity(n)

        for lp in 1...n {
            let data = NSMutableData()
            guard let consumer = CGDataConsumer(data: data as CFMutableData) else { continue }

            var box = CGRect(x: 0, y: 0, width: layout.pageW, height: layout.pageH)
            guard let pageCtx = CGContext(consumer: consumer, mediaBox: &box, nil) else { continue }

            pageCtx.beginPDFPage(nil)
            document.draw(logicalPage: lp, map: map, layout: layout, into: pageCtx)
            pageCtx.endPDFPage()
            pageCtx.closePDF()

            guard let provider = CGDataProvider(data: data as CFData),
                  let doc = CGPDFDocument(provider) else { continue }
            intermediates.append(doc)
        }

        // ── Stage 2: composite pairs onto physical sheets ───────────────────
        let physW: CGFloat = 11.0 * 72.0   // letter landscape
        let physH: CGFloat = 8.5 * 72.0
        let url = URL(fileURLWithPath: outputPath) as CFURL
        var physBox = CGRect(x: 0, y: 0, width: physW, height: physH)

        guard let finalCtx = CGContext(url, mediaBox: &physBox, nil) else {
            FileHandle.standardError.write(
                "Failed to create PDF context for \(outputPath)\n".data(using: .utf8)!)
            return
        }

        func page(_ lp: Int) -> CGPDFPage? {
            guard lp >= 1, lp <= intermediates.count else { return nil }
            return intermediates[lp - 1].page(at: 1)
        }

        func place(_ lp: Int, atX x: CGFloat) {
            guard let pdfPage = page(lp) else { return }
            finalCtx.saveGState()
            finalCtx.translateBy(x: x, y: 0)
            finalCtx.drawPDFPage(pdfPage)
            finalCtx.restoreGState()
        }

        for s in 0..<sheetCount {
            for side in 0...1 {
                let slot = Imposition.slot(sheet: s, side: side, N: n)
                finalCtx.beginPDFPage(nil)
                place(slot.leftPage, atX: 0)
                place(slot.rightPage, atX: layout.pageW)
                finalCtx.endPDFPage()
            }
        }

        finalCtx.closePDF()
    }
}
