use typst_library::diag::SourceResult;
use typst_library::engine::Engine;
use typst_library::introspection::{ManualPageCounter, Tag};
use typst_library::layout::{Frame, FrameItem, Point};

use super::LayoutedPage;
use crate::Page;

/// Piece together the inner page frame and the marginals. We can only do this
/// at the very end because inside/outside margins require knowledge of the
/// physical page number, which is unknown during parallel layout.
pub fn finalize(
    engine: &mut Engine,
    counter: &mut ManualPageCounter,
    tags: &mut Vec<Tag>,
    LayoutedPage {
        inner,
        mut margin,
        margin_two_sided,
        mut bleed,
        bleed_two_sided,
        binding,
        header,
        footer,
        background,
        foreground,
        fill,
        numbering,
        supplement,
    }: LayoutedPage,
) -> SourceResult<Page> {
    // If two sided, left becomes inside and right becomes outside.
    // Thus, for left-bound pages, we want to swap on even pages and
    // for right-bound pages, we want to swap on odd pages.
    let swap = binding.swap(counter.physical());
    if margin_two_sided && swap {
        std::mem::swap(&mut margin.left, &mut margin.right);
    }
    if bleed_two_sided && swap {
        std::mem::swap(&mut bleed.left, &mut bleed.right);
    }

    // Create a frame for the full page.
    let mut frame = Frame::hard(inner.size() + margin.sum_by_axis());

    // Add tags.
    for tag in tags.drain(..) {
        frame.push(Point::zero(), FrameItem::Tag(tag));
    }

    let bleed_origin = Point::new(-bleed.left, -bleed.top);

    // Add the "before" marginals. The order in which we push things here is
    // important as it affects the relative ordering of introspectable elements
    // and thus how counters resolve.
    if let Some(background) = background {
        frame.push_frame(bleed_origin, background);
    }
    if let Some(header) = header {
        frame.push_frame(Point::with_x(margin.left), header);
    }

    // Add the inner contents.
    frame.push_frame(Point::new(margin.left, margin.top), inner);

    // Add the "after" marginals.
    if let Some(footer) = footer {
        let y = frame.height() - footer.height();
        frame.push_frame(Point::new(margin.left, y), footer);
    }
    if let Some(foreground) = foreground {
        frame.push_frame(bleed_origin, foreground);
    }

    // Apply counter updates from within the page to the manual page counter.
    counter.visit(engine, &frame)?;

    // Get this page's number and then bump the counter for the next page.
    let number = counter.logical();
    counter.step();

    Ok(Page { frame, bleed, fill, numbering, supplement, number })
}
