use typst_library::diag::SourceResult;
use typst_library::engine::Engine;
use typst_library::foundations::Resolve;
use typst_library::layout::grid::resolve::Repeatable;
use typst_library::layout::{Abs, Axes, Frame, Point, Region, Regions, Size, Sizing};
use typst_utils::MaybeReverseIter;

use super::layouter::{in_last_with_offset, points, Row, RowPiece};
use super::{layout_cell, Cell, GridLayouter};

/// All information needed to layout a single rowspan.
pub struct Rowspan {
    /// First column of this rowspan.
    pub x: usize,
    /// First row of this rowspan.
    pub y: usize,
    /// The disambiguator for laying out the cells.
    pub disambiguator: usize,
    /// Amount of rows spanned by the cell at (x, y).
    pub rowspan: usize,
    /// Whether all rows of the rowspan are part of an unbreakable row group.
    /// This is true e.g. in headers and footers, regardless of what the user
    /// specified for the parent cell's `breakable` field.
    pub is_effectively_unbreakable: bool,
    /// The horizontal offset of this rowspan in all regions.
    pub dx: Abs,
    /// The vertical offset of this rowspan in the first region.
    pub dy: Abs,
    /// The index of the first region this rowspan appears in.
    pub first_region: usize,
    /// The full height in the first region this rowspan appears in, for
    /// relative sizing.
    pub region_full: Abs,
    /// The vertical space available for this rowspan in each region.
    pub heights: Vec<Abs>,
    /// The index of the largest resolved spanned row so far.
    /// Once a spanned row is resolved and its height added to `heights`, this
    /// number is increased. Older rows, even if repeated through e.g. a
    /// header, will no longer contribute height to this rowspan.
    ///
    /// This is `None` if no spanned rows were resolved in `finish_region` yet.
    pub max_resolved_row: Option<usize>,
}

/// The output of the simulation of an unbreakable row group.
#[derive(Default)]
pub struct UnbreakableRowGroup {
    /// The rows in this group of unbreakable rows.
    /// Includes their indices and their predicted heights.
    pub rows: Vec<(usize, Abs)>,
    /// The total height of this row group.
    pub height: Abs,
}

/// Data used to measure a cell in an auto row.
pub struct CellMeasurementData<'layouter> {
    /// The available width for the cell across all regions.
    pub width: Abs,
    /// The available height for the cell in its first region.
    /// Infinite when the auto row is unbreakable.
    pub height: Abs,
    /// The backlog of heights available for the cell in later regions.
    ///
    /// When this is `None`, the `custom_backlog` field should be used instead.
    /// That's because, otherwise, this field would have to contain a reference
    /// to the `custom_backlog` field, which isn't possible in Rust without
    /// resorting to unsafe hacks.
    pub backlog: Option<&'layouter [Abs]>,
    /// If the backlog needs to be built from scratch instead of reusing the
    /// one at the current region, which is the case of a multi-region rowspan
    /// (needs to join its backlog of already laid out heights with the current
    /// backlog), then this vector will store the new backlog.
    pub custom_backlog: Vec<Abs>,
    /// The full height of the first region of the cell.
    /// Infinite when the auto row is unbreakable.
    pub full: Abs,
    /// The height of the last repeated region to use in the measurement pod,
    /// if any.
    pub last: Option<Abs>,
    /// The total height of previous rows spanned by the cell in the current
    /// region (so far).
    pub height_in_this_region: Abs,
    /// The amount of previous regions spanned by the cell.
    /// They are skipped for measurement purposes.
    pub frames_in_previous_regions: usize,
}

impl GridLayouter<'_> {
    /// Layout a rowspan over the already finished regions, plus the current
    /// region's frame and resolved rows, if it wasn't finished yet (because
    /// we're being called from `finish_region`, but note that this function is
    /// also called once after all regions are finished, in which case
    /// `current_region_data` is `None`).
    ///
    /// We need to do this only once we already know the heights of all
    /// spanned rows, which is only possible after laying out the last row
    /// spanned by the rowspan (or some row immediately after the last one).
    pub fn layout_rowspan(
        &mut self,
        rowspan_data: Rowspan,
        current_region_data: Option<(&mut Frame, &[RowPiece])>,
        engine: &mut Engine,
    ) -> SourceResult<()> {
        let Rowspan {
            x,
            y,
            disambiguator,
            rowspan,
            is_effectively_unbreakable,
            dx,
            dy,
            first_region,
            region_full,
            heights,
            ..
        } = rowspan_data;
        let [first_height, backlog @ ..] = heights.as_slice() else {
            // Nothing to layout.
            return Ok(());
        };
        let first_column = self.rcols[x];
        let cell = self.grid.cell(x, y).unwrap();
        let width = self.cell_spanned_width(cell, x);
        let dx = if self.is_rtl { dx - width + first_column } else { dx };

        // Prepare regions.
        let size = Size::new(width, *first_height);
        let mut pod: Regions = Region::new(size, Axes::splat(true)).into();
        pod.backlog = backlog;

        if !is_effectively_unbreakable
            && self.grid.rows[y..][..rowspan]
                .iter()
                .any(|spanned_row| spanned_row == &Sizing::Auto)
        {
            // If the rowspan spans an auto row and is breakable, it will see
            // '100%' as the full page height, at least at its first region.
            // This is consistent with how it is measured, and with how
            // non-rowspan cells behave in auto rows.
            pod.full = region_full;
        }

        // Push the layouted frames directly into the finished frames.
        let fragment = layout_cell(cell, engine, disambiguator, self.styles, pod)?;
        let (current_region, current_rrows) = current_region_data.unzip();
        for ((i, finished), frame) in self
            .finished
            .iter_mut()
            .chain(current_region.into_iter())
            .skip(first_region)
            .enumerate()
            .zip(fragment)
        {
            let dy = if i == 0 {
                // At first, we draw the rowspan starting at its expected
                // vertical offset in the first region.
                dy
            } else {
                // The rowspan continuation starts after the header (thus,
                // at a position after the sum of the laid out header
                // rows).
                if let Some(Repeatable::Repeated(header)) = &self.grid.header {
                    let header_rows = self
                        .rrows
                        .get(i)
                        .map(Vec::as_slice)
                        .or(current_rrows)
                        .unwrap_or(&[])
                        .iter()
                        .take_while(|row| row.y < header.end);

                    header_rows.map(|row| row.height).sum()
                } else {
                    // Without a header, start at the very top of the region.
                    Abs::zero()
                }
            };

            finished.push_frame(Point::new(dx, dy), frame);
        }

        Ok(())
    }

    /// Checks if a row contains the beginning of one or more rowspan cells.
    /// If so, adds them to the rowspans vector.
    pub fn check_for_rowspans(&mut self, disambiguator: usize, y: usize) {
        // We will compute the horizontal offset of each rowspan in advance.
        // For that reason, we must reverse the column order when using RTL.
        let offsets = points(self.rcols.iter().copied().rev_if(self.is_rtl));
        for (x, dx) in (0..self.rcols.len()).rev_if(self.is_rtl).zip(offsets) {
            let Some(cell) = self.grid.cell(x, y) else {
                continue;
            };
            let rowspan = self.grid.effective_rowspan_of_cell(cell);
            if rowspan > 1 {
                // Rowspan detected. We will lay it out later.
                self.rowspans.push(Rowspan {
                    x,
                    y,
                    disambiguator,
                    rowspan,
                    // The field below will be updated in
                    // 'check_for_unbreakable_rows'.
                    is_effectively_unbreakable: !cell.breakable,
                    dx,
                    // The four fields below will be updated in 'finish_region'.
                    dy: Abs::zero(),
                    first_region: usize::MAX,
                    region_full: Abs::zero(),
                    heights: vec![],
                    max_resolved_row: None,
                });
            }
        }
    }

    /// Checks if the upcoming rows will be grouped together under an
    /// unbreakable row group, and, if so, advances regions until there is
    /// enough space for them. This can be needed, for example, if there's an
    /// unbreakable rowspan crossing those rows.
    pub fn check_for_unbreakable_rows(
        &mut self,
        current_row: usize,
        engine: &mut Engine,
    ) -> SourceResult<()> {
        if self.unbreakable_rows_left == 0 {
            // By default, the amount of unbreakable rows starting at the
            // current row is dynamic and depends on the amount of upcoming
            // unbreakable cells (with or without a rowspan setting).
            let mut amount_unbreakable_rows = None;
            if let Some(Repeatable::NotRepeated(header)) = &self.grid.header {
                if current_row < header.end {
                    // Non-repeated header, so keep it unbreakable.
                    amount_unbreakable_rows = Some(header.end);
                }
            }
            if let Some(Repeatable::NotRepeated(footer)) = &self.grid.footer {
                if current_row >= footer.start {
                    // Non-repeated footer, so keep it unbreakable.
                    amount_unbreakable_rows = Some(self.grid.rows.len() - footer.start);
                }
            }

            let row_group = self.simulate_unbreakable_row_group(
                current_row,
                amount_unbreakable_rows,
                &self.regions,
                engine,
                0,
            )?;

            // Skip to fitting region.
            while !self.regions.size.y.fits(row_group.height)
                && !in_last_with_offset(
                    self.regions,
                    self.header_height + self.footer_height,
                )
            {
                self.finish_region(engine, false)?;
            }

            // Update unbreakable rows left.
            self.unbreakable_rows_left = row_group.rows.len();
        }

        if self.unbreakable_rows_left > 1 {
            // Mark rowspans as effectively unbreakable where applicable
            // (if all of their spanned rows would be in the same unbreakable
            // row group).
            // Not needed if only one unbreakable row is left, since, then,
            // no rowspan will be effectively unbreakable, at least necessarily.
            // Note that this function is called after 'check_for_rowspans' and
            // potentially updates the amount of remaining unbreakable rows, so
            // it wouldn't be accurate to only check for this condition in that
            // function. We need to check here instead.
            for rowspan_data in
                self.rowspans.iter_mut().filter(|rowspan| rowspan.y == current_row)
            {
                rowspan_data.is_effectively_unbreakable |=
                    self.unbreakable_rows_left >= rowspan_data.rowspan;
            }
        }

        Ok(())
    }

    /// Simulates a group of unbreakable rows, starting with the index of the
    /// first row in the group. If `amount_unbreakable_rows` is `None`, keeps
    /// adding rows to the group until none have unbreakable cells in common.
    /// Otherwise, adds specifically the given amount of rows to the group.
    ///
    /// This is used to figure out how much height the next unbreakable row
    /// group (if any) needs.
    pub fn simulate_unbreakable_row_group(
        &self,
        first_row: usize,
        amount_unbreakable_rows: Option<usize>,
        regions: &Regions<'_>,
        engine: &mut Engine,
        disambiguator: usize,
    ) -> SourceResult<UnbreakableRowGroup> {
        let mut row_group = UnbreakableRowGroup::default();
        let mut unbreakable_rows_left = amount_unbreakable_rows.unwrap_or(0);
        for (y, row) in self.grid.rows.iter().enumerate().skip(first_row) {
            if amount_unbreakable_rows.is_none() {
                // When we don't set a fixed amount of unbreakable rows,
                // determine the amount based on the rowspan of unbreakable
                // cells in rows.
                let additional_unbreakable_rows = self.check_for_unbreakable_cells(y);
                unbreakable_rows_left =
                    unbreakable_rows_left.max(additional_unbreakable_rows);
            }
            if unbreakable_rows_left == 0 {
                // This check is in case the first row does not have any
                // unbreakable cells. Therefore, no unbreakable row group
                // is formed.
                break;
            }
            let height = match row {
                Sizing::Rel(v) => v.resolve(self.styles).relative_to(regions.base().y),

                // No need to pass the regions to the auto row, since
                // unbreakable auto rows are always measured with infinite
                // height, ignore backlog, and do not invoke the rowspan
                // simulation procedure at all.
                Sizing::Auto => self
                    .measure_auto_row(
                        engine,
                        disambiguator,
                        y,
                        false,
                        unbreakable_rows_left,
                        Some(&row_group),
                    )?
                    .unwrap()
                    .first()
                    .copied()
                    .unwrap_or_else(Abs::zero),
                // Fractional rows don't matter when calculating the space
                // needed for unbreakable rows
                Sizing::Fr(_) => Abs::zero(),
            };
            row_group.height += height;
            row_group.rows.push((y, height));
            unbreakable_rows_left -= 1;
            if unbreakable_rows_left == 0 {
                // This second check is necessary so we can tell distinct
                // but consecutive unbreakable row groups apart. If the
                // unbreakable row group ended at this row, we stop before
                // checking the next one.
                break;
            }
        }

        Ok(row_group)
    }

    /// Checks if one or more of the cells at the given row are unbreakable.
    /// If so, returns the largest rowspan among the unbreakable cells;
    /// the spanned rows must, as a result, be laid out in the same region.
    pub fn check_for_unbreakable_cells(&self, y: usize) -> usize {
        (0..self.grid.cols.len())
            .filter_map(|x| self.grid.cell(x, y))
            .filter(|cell| !cell.breakable)
            .map(|cell| self.grid.effective_rowspan_of_cell(cell))
            .max()
            .unwrap_or(0)
    }

    /// Used by `measure_auto_row` to gather data needed to measure the cell.
    pub fn prepare_auto_row_cell_measurement(
        &self,
        parent: Axes<usize>,
        cell: &Cell,
        breakable: bool,
        row_group_data: Option<&UnbreakableRowGroup>,
    ) -> CellMeasurementData<'_> {
        let rowspan = self.grid.effective_rowspan_of_cell(cell);

        // This variable is used to construct a custom backlog if the cell
        // is a rowspan, or if headers or footers are used. When measuring, we
        // join the heights from previous regions to the current backlog to
        // form a rowspan's expected backlog. We also subtract the header's
        // and footer's heights from all regions.
        let mut custom_backlog: Vec<Abs> = vec![];

        // This function is used to subtract the expected header and footer
        // height from each upcoming region size in the current backlog and
        // last region.
        let mut subtract_header_footer_height_from_regions = || {
            // Only breakable auto rows need to update their backlogs based
            // on the presence of a header or footer, given that unbreakable
            // auto rows don't depend on the backlog, as they only span one
            // region.
            if breakable
                && (matches!(self.grid.header, Some(Repeatable::Repeated(_)))
                    || matches!(self.grid.footer, Some(Repeatable::Repeated(_))))
            {
                // Subtract header and footer height from all upcoming regions
                // when measuring the cell, including the last repeated region.
                //
                // This will update the 'custom_backlog' vector with the
                // updated heights of the upcoming regions.
                let mapped_regions = self.regions.map(&mut custom_backlog, |size| {
                    Size::new(size.x, size.y - self.header_height - self.footer_height)
                });

                // Callees must use the custom backlog instead of the current
                // backlog, so we return 'None'.
                return (None, mapped_regions.last);
            }

            // No need to change the backlog or last region.
            (Some(self.regions.backlog), self.regions.last)
        };

        // Each declaration, from top to bottom:
        // 1. The height available to the cell in the first region.
        // Usually, this will just be the size remaining in the current
        // region.
        // 2. The backlog of upcoming region heights to specify as
        // available to the cell.
        // 3. The full height of the first region of the cell.
        // 4. Height of the last repeated region to use in the measurement pod.
        // 5. The total height of the cell covered by previously spanned
        // rows in this region. This is used by rowspans to be able to tell
        // how much the auto row needs to expand.
        // 6. The amount of frames laid out by this cell in previous
        // regions. When the cell isn't a rowspan, this is always zero.
        // These frames are skipped after measuring.
        let height;
        let backlog;
        let full;
        let last;
        let height_in_this_region;
        let frames_in_previous_regions;

        if rowspan == 1 {
            // Not a rowspan, so the cell only occupies this row. Therefore:
            // 1. When we measure the cell below, use the available height
            // remaining in the region as the height it has available.
            // However, if the auto row is unbreakable, measure with infinite
            // height instead to see how much content expands.
            // 2. Use the region's backlog and last region when measuring,
            // however subtract the expected header and footer heights from
            // each upcoming size, if there is a header or footer.
            // 3. Use the same full region height.
            // 4. No height occupied by this cell in this region so far.
            // 5. Yes, this cell started in this region.
            height = if breakable { self.regions.size.y } else { Abs::inf() };
            (backlog, last) = subtract_header_footer_height_from_regions();
            full = if breakable { self.regions.full } else { Abs::inf() };
            height_in_this_region = Abs::zero();
            frames_in_previous_regions = 0;
        } else {
            // Height of the rowspan covered by spanned rows in the current
            // region.
            let laid_out_height: Abs = self
                .lrows
                .iter()
                .filter_map(|row| match row {
                    Row::Frame(frame, y, _)
                        if (parent.y..parent.y + rowspan).contains(y) =>
                    {
                        Some(frame.height())
                    }
                    // Either we have a row outside of the rowspan, or a
                    // fractional row, whose size we can't really guess.
                    _ => None,
                })
                .sum();

            // If we're currently simulating an unbreakable row group, also
            // consider the height of previously spanned rows which are in
            // the row group but not yet laid out.
            let unbreakable_height: Abs = row_group_data
                .into_iter()
                .flat_map(|row_group| &row_group.rows)
                .filter(|(y, _)| (parent.y..parent.y + rowspan).contains(y))
                .map(|(_, height)| height)
                .sum();

            height_in_this_region = laid_out_height + unbreakable_height;

            // Ensure we will measure the rowspan with the correct heights.
            // For that, we will gather the total height spanned by this
            // rowspan in previous regions.
            if let Some((rowspan_full, [rowspan_height, rowspan_other_heights @ ..])) =
                self.rowspans
                    .iter()
                    .find(|data| data.x == parent.x && data.y == parent.y)
                    .map(|data| (data.region_full, &*data.heights))
            {
                // The rowspan started in a previous region (as it already
                // has at least one region height).
                // Therefore, its initial height will be the height in its
                // first spanned region, and the backlog will be the
                // remaining heights, plus the current region's size, plus
                // the current backlog.
                frames_in_previous_regions = rowspan_other_heights.len() + 1;

                let heights_up_to_current_region = rowspan_other_heights
                    .iter()
                    .copied()
                    .chain(std::iter::once(if breakable {
                        self.initial.y - self.header_height - self.footer_height
                    } else {
                        // When measuring unbreakable auto rows, infinite
                        // height is available for content to expand.
                        Abs::inf()
                    }));

                custom_backlog = if breakable {
                    // This auto row is breakable. Therefore, join the
                    // rowspan's already laid out heights with the current
                    // region's height and current backlog to ensure a good
                    // level of accuracy in the measurements.
                    let backlog = self
                        .regions
                        .backlog
                        .iter()
                        .map(|&size| size - self.header_height - self.footer_height);

                    heights_up_to_current_region.chain(backlog).collect::<Vec<_>>()
                } else {
                    // No extra backlog if this is an unbreakable auto row.
                    // Ensure, when measuring, that the rowspan can be laid
                    // out through all spanned rows which were already laid
                    // out so far, but don't go further than this region.
                    heights_up_to_current_region.collect::<Vec<_>>()
                };

                height = *rowspan_height;
                backlog = None;
                full = rowspan_full;
                last = self
                    .regions
                    .last
                    .map(|size| size - self.header_height - self.footer_height);
            } else {
                // The rowspan started in the current region, as its vector
                // of heights in regions is currently empty.
                // Therefore, the initial height it has available will be
                // the current available size, plus the size spanned in
                // previous rows in this region (and/or unbreakable row
                // group, if it's being simulated).
                // The backlog and full will be that of the current region.
                // However, use infinite height instead if we're measuring an
                // unbreakable auto row.
                height = if breakable {
                    height_in_this_region + self.regions.size.y
                } else {
                    Abs::inf()
                };
                (backlog, last) = subtract_header_footer_height_from_regions();
                full = if breakable { self.regions.full } else { Abs::inf() };
                frames_in_previous_regions = 0;
            }
        }

        let width = self.cell_spanned_width(cell, parent.x);
        CellMeasurementData {
            width,
            height,
            backlog,
            custom_backlog,
            full,
            last,
            height_in_this_region,
            frames_in_previous_regions,
        }
    }

    /// Used in `measure_auto_row` to prepare a rowspan's `sizes` vector.
    /// Returns `true` if we'll need to run a simulation to more accurately
    /// expand the auto row based on the rowspan's demanded size, or `false`
    /// otherwise.
    #[allow(clippy::too_many_arguments)]
    pub fn prepare_rowspan_sizes(
        &self,
        auto_row_y: usize,
        sizes: &mut Vec<Abs>,
        cell: &Cell,
        parent_y: usize,
        rowspan: usize,
        unbreakable_rows_left: usize,
        measurement_data: &CellMeasurementData<'_>,
    ) -> bool {
        if sizes.len() <= 1
            && sizes.first().map_or(true, |&first_frame_size| {
                first_frame_size <= measurement_data.height_in_this_region
            })
        {
            // Ignore a rowspan fully covered by rows in previous
            // regions and/or in the current region.
            sizes.clear();
            return false;
        }
        if let Some(first_frame_size) = sizes.first_mut() {
            // Subtract already covered height from the size requested
            // by this rowspan to the auto row in the first region.
            *first_frame_size = (*first_frame_size
                - measurement_data.height_in_this_region)
                .max(Abs::zero());
        }

        let last_spanned_row = parent_y + rowspan - 1;

        // When the rowspan is unbreakable, or all of its upcoming
        // spanned rows are in the same unbreakable row group, its
        // spanned gutter will certainly be in the same region as all
        // of its other spanned rows, thus gutters won't be removed,
        // and we can safely reduce how much the auto row expands by
        // without using simulation.
        let is_effectively_unbreakable_rowspan =
            !cell.breakable || auto_row_y + unbreakable_rows_left > last_spanned_row;

        // If the rowspan doesn't end at this row and the grid has
        // gutter, we will need to run a simulation to find out how
        // much to expand this row by later. This is because gutters
        // spanned by this rowspan might be removed if they appear
        // around a pagebreak, so the auto row might have to expand a
        // bit more to compensate for the missing gutter height.
        // However, unbreakable rowspans aren't affected by that
        // problem.
        if auto_row_y != last_spanned_row
            && !sizes.is_empty()
            && self.grid.has_gutter
            && !is_effectively_unbreakable_rowspan
        {
            return true;
        }

        // We can only predict the resolved size of upcoming fixed-size
        // rows, but not fractional rows. In the future, we might be
        // able to simulate and circumvent the problem with fractional
        // rows. Relative rows are currently always measured relative
        // to the first region as well.
        // We can ignore auto rows since this is the last spanned auto
        // row.
        let will_be_covered_height: Abs = self
            .grid
            .rows
            .iter()
            .skip(auto_row_y + 1)
            .take(last_spanned_row - auto_row_y)
            .map(|row| match row {
                Sizing::Rel(v) => {
                    v.resolve(self.styles).relative_to(self.regions.base().y)
                }
                _ => Abs::zero(),
            })
            .sum();

        // Remove or reduce the sizes of the rowspan at the current or future
        // regions where it will already be covered by further rows spanned by
        // it.
        subtract_end_sizes(sizes, will_be_covered_height);

        // No need to run a simulation for this rowspan.
        false
    }

    /// Performs a simulation to predict by how much height the last spanned
    /// auto row will have to expand, given the current sizes of the auto row
    /// in each region and the pending rowspans' data (parent Y, rowspan amount
    /// and vector of requested sizes).
    #[allow(clippy::too_many_arguments)]
    pub fn simulate_and_measure_rowspans_in_auto_row(
        &self,
        y: usize,
        resolved: &mut Vec<Abs>,
        pending_rowspans: &[(usize, usize, Vec<Abs>)],
        unbreakable_rows_left: usize,
        row_group_data: Option<&UnbreakableRowGroup>,
        mut disambiguator: usize,
        engine: &mut Engine,
    ) -> SourceResult<()> {
        // To begin our simulation, we have to unify the sizes demanded by
        // each rowspan into one simple vector of sizes, as if they were
        // all a single rowspan. These sizes will be appended to
        // 'resolved' once we finish our simulation.
        let mut simulated_sizes: Vec<Abs> = vec![];
        let last_resolved_size = resolved.last().copied();
        let mut max_spanned_row = y;
        for (parent_y, rowspan, sizes) in pending_rowspans {
            let mut sizes = sizes.iter();
            for (target, size) in resolved.iter_mut().zip(&mut sizes) {
                // First, we update the already resolved sizes as required
                // by this rowspan. No need to simulate this since the auto row
                // will already expand throughout already resolved regions.
                // Our simulation, therefore, won't otherwise change already
                // resolved sizes, other than, perhaps, the last one (at the
                // last currently resolved region, at which we can expand).
                target.set_max(*size);
            }
            for (simulated_target, rowspan_size) in
                simulated_sizes.iter_mut().zip(&mut sizes)
            {
                // The remaining sizes are exclusive to rowspans, since
                // other cells in this row didn't require as many regions.
                // We will perform a simulation to see how much of these sizes
                // does the auto row actually need to expand by, and how much
                // is already covered by upcoming rows spanned by the rowspans.
                simulated_target.set_max(*rowspan_size);
            }
            simulated_sizes.extend(sizes);
            max_spanned_row = max_spanned_row.max(parent_y + rowspan - 1);
        }
        if simulated_sizes.is_empty() && resolved.last() == last_resolved_size.as_ref() {
            // The rowspans already fit in the already resolved sizes.
            // No need for simulation.
            return Ok(());
        }

        // We will be updating the last resolved size (expanding the auto
        // row) as needed. Therefore, consider it as part of the simulation.
        // At the end, we push it back.
        if let Some(modified_last_resolved_size) = resolved.pop() {
            simulated_sizes.insert(0, modified_last_resolved_size);
        }

        // Prepare regions for simulation.
        // If we're currently inside an unbreakable row group simulation,
        // subtract the current row group height from the available space
        // when simulating rowspans in said group.
        let mut simulated_regions = self.regions;
        simulated_regions.size.y -=
            row_group_data.map_or(Abs::zero(), |row_group| row_group.height);

        for _ in 0..resolved.len() {
            // Ensure we start at the region where we will expand the auto
            // row.
            // Note that we won't accidentally call '.next()' once more than
            // desired (we won't skip the last resolved frame, where we will
            // expand) because we popped the last resolved size from the
            // resolved vector, above.
            simulated_regions.next();
            disambiguator += 1;

            // Subtract the initial header and footer height, since that's the
            // height we used when subtracting from the region backlog's
            // heights while measuring cells.
            simulated_regions.size.y -= self.header_height + self.footer_height;
        }

        if let Some(original_last_resolved_size) = last_resolved_size {
            // We're now at the (current) last region of this auto row.
            // Consider resolved height as already taken space.
            simulated_regions.size.y -= original_last_resolved_size;
        }

        // Now we run the simulation to check how much the auto row needs to
        // grow to ensure that rowspans have the height they need.
        let simulations_stabilized = self.run_rowspan_simulation(
            y,
            max_spanned_row,
            simulated_regions,
            &mut simulated_sizes,
            engine,
            last_resolved_size,
            unbreakable_rows_left,
            disambiguator,
        )?;

        if !simulations_stabilized {
            // If the simulation didn't stabilize above, we will just pretend
            // all gutters were removed, as a best effort. That means the auto
            // row will expand more than it normally should, but there isn't
            // much we can do.
            let will_be_covered_height = self
                .grid
                .rows
                .iter()
                .enumerate()
                .skip(y + 1)
                .take(max_spanned_row - y)
                .filter(|(y, _)| !self.grid.is_gutter_track(*y))
                .map(|(_, row)| match row {
                    Sizing::Rel(v) => {
                        v.resolve(self.styles).relative_to(self.regions.base().y)
                    }
                    _ => Abs::zero(),
                })
                .sum();

            subtract_end_sizes(&mut simulated_sizes, will_be_covered_height);
        }

        resolved.extend(simulated_sizes);

        Ok(())
    }

    /// Performs a simulation of laying out multiple rowspans (consolidated
    /// into a single vector of simulated sizes) ending in a certain auto row
    /// in order to find out how much the auto row will need to expand to cover
    /// the rowspans' requested sizes, considering how much size has been
    /// covered by other rows and by gutter between rows.
    ///
    /// For example, for a rowspan cell containing a block of 8pt of height
    /// spanning rows (1pt, auto, 0.5pt, 0.5pt), with a gutter of 1pt between
    /// each row, we have that the rows it spans provide 1pt + 0.5pt + 0.5pt
    /// = 2pt of height, plus 1pt + 1pt + 1pt = 3pt of gutter, with a total of
    /// 2pt + 3pt = 5pt of height already covered by fixed-size rows and
    /// gutters. This means that the auto row must (under normal conditions)
    /// expand by 3pt (8pt - 5pt) so that the rowspan has enough height across
    /// rows to fully draw its contents.
    ///
    /// However, it's possible that the last row is sent to the next page to
    /// respect a pagebreak, and then the 1pt gutter before it disappears. This
    /// would lead to our rowspan having a height of 7pt available if we fail
    /// to predict this situation when measuring the auto row.
    ///
    /// The algorithm below will, thus, attempt to simulate the layout of each
    /// spanned row, considering the space available in the current page and in
    /// upcoming pages (through the region backlog), in order to predict which
    /// rows will be sent to a new page and thus have their preceding gutter
    /// spacing removed (meaning the auto row has to grow a bit more). After
    /// simulating, we subtract the total height spanned by upcoming rows and
    /// gutter from the total rowspan height - this will be how much our auto
    /// row has to expand. We then simulate again to check if, if the auto row
    /// expanded by that amount, that would prompt the auto row to need to
    /// expand even more, because expanding the auto row might cause some other
    /// larger gutter spacing to disappear (leading to the rowspan having less
    /// space available instead of more); if so, we update the amount to expand
    /// and run the simulation again. Otherwise (if it should expand by the
    /// same amount, meaning we predicted correctly, or by less, meaning the
    /// auto row will be a bit larger than it should be, but that's a
    /// compromise we're willing to accept), we conclude the simulation
    /// (consider it stabilized) and return the result.
    ///
    /// Tries up to 5 times. If two consecutive simulations stabilize, then
    /// we subtract the predicted expansion height ('amount_to_grow') from the
    /// total height requested by rowspans (the 'requested_rowspan_height') to
    /// obtain how much height is covered by upcoming rows, according to our
    /// simulation, and the result of that operation is used to reduce or
    /// remove heights from the end of the vector of simulated sizes, such that
    /// the remaining heights are exactly how much the auto row should expand
    /// by. Then, we return `true`.
    ///
    /// If the simulations don't stabilize (they return 5 different and
    /// successively larger values), aborts and returns `false`.
    #[allow(clippy::too_many_arguments)]
    fn run_rowspan_simulation(
        &self,
        y: usize,
        max_spanned_row: usize,
        mut simulated_regions: Regions<'_>,
        simulated_sizes: &mut Vec<Abs>,
        engine: &mut Engine,
        last_resolved_size: Option<Abs>,
        unbreakable_rows_left: usize,
        mut disambiguator: usize,
    ) -> SourceResult<bool> {
        // The max amount this row can expand will be the total size requested
        // by rowspans which was not yet resolved. It is worth noting that,
        // earlier, we pushed the last resolved size to 'simulated_sizes' as
        // row expansion starts with it, so it's possible a rowspan requested
        // to extend that size (we will see, through the simulation, if that's
        // needed); however, we must subtract that resolved size from the total
        // sum of sizes, as it was already resolved and thus the auto row will
        // already grow by at least that much in the last resolved region (we
        // would grow by the same size twice otherwise).
        let requested_rowspan_height =
            simulated_sizes.iter().sum::<Abs>() - last_resolved_size.unwrap_or_default();

        // The amount the row will effectively grow by, according to the latest
        // simulation.
        let mut amount_to_grow = Abs::zero();

        // Try to simulate up to 5 times. If it doesn't stabilize at a value
        // which, when used and combined with upcoming spanned rows, covers all
        // of the requested rowspan height, we give up.
        for _attempt in 0..5 {
            let rowspan_simulator = RowspanSimulator::new(
                disambiguator,
                simulated_regions,
                self.header_height,
                self.footer_height,
            );

            let total_spanned_height = rowspan_simulator.simulate_rowspan_layout(
                y,
                max_spanned_row,
                amount_to_grow,
                requested_rowspan_height,
                unbreakable_rows_left,
                self,
                engine,
            )?;

            // If the total height spanned by upcoming spanned rows plus the
            // current amount we predict the auto row will have to grow (from
            // the previous iteration) are larger than the size requested by
            // rowspans, this means the auto row will grow enough in order to
            // cover the requested rowspan height, so we stop the simulation.
            //
            // If that's not yet the case, we will simulate again and make the
            // auto row grow even more, and do so until either the auto row has
            // grown enough, or we tried to do so over 5 times.
            //
            // A flaw of this approach is that we consider rowspans' content to
            // be contiguous. That is, we treat rowspans' requested heights as
            // a simple number, instead of properly using the vector of
            // requested heights in each region. This can lead to some
            // weirdness when using multi-page rowspans with content that
            // reacts to the amount of space available, including paragraphs.
            // However, this is probably the best we can do for now.
            if (total_spanned_height + amount_to_grow).fits(requested_rowspan_height) {
                // Reduce sizes by the amount to be covered by upcoming spanned
                // rows, which is equivalent to the amount that we don't grow.
                // We reduce from the end as that's where the spanned rows will
                // cover. The remaining sizes will all be covered by the auto
                // row instead (which will grow by those sizes).
                subtract_end_sizes(
                    simulated_sizes,
                    requested_rowspan_height - amount_to_grow,
                );

                if let Some(last_resolved_size) = last_resolved_size {
                    // Ensure the first simulated size is at least as large as
                    // the last resolved size (its initial value). As it was
                    // already resolved before, we must not reduce below the
                    // resolved size to avoid problems with non-rowspan cells.
                    if let Some(first_simulated_size) = simulated_sizes.first_mut() {
                        first_simulated_size.set_max(last_resolved_size);
                    } else {
                        simulated_sizes.push(last_resolved_size);
                    }
                }

                return Ok(true);
            }

            // For the next simulation, we will test if the auto row can grow
            // by precisely how much rowspan height is not covered by upcoming
            // spanned rows, according to the current simulation.
            // We know that the new amount to grow is larger (and thus the
            // auto row only expands between each simulation), because we
            // checked above if
            // 'total_spanned_height + (now old_)amount_to_grow >= requested_rowspan_height',
            // which was false, so it holds that
            // 'total_spanned_height + old_amount_to_grow < requested_rowspan_height'
            // Thus,
            // 'old_amount_to_grow < requested_rowspan_height - total_spanned_height'
            // Therefore, by definition, 'old_amount_to_grow < amount_to_grow'.
            let old_amount_to_grow = std::mem::replace(
                &mut amount_to_grow,
                requested_rowspan_height - total_spanned_height,
            );

            // We advance the 'regions' variable accordingly, so that, in the
            // next simulation, we consider already grown space as final.
            // That is, we effectively simulate how rows would be placed if the
            // auto row grew by precisely the new value of 'amount_to_grow'.
            let mut extra_amount_to_grow = amount_to_grow - old_amount_to_grow;
            while extra_amount_to_grow > Abs::zero()
                && simulated_regions.size.y < extra_amount_to_grow
            {
                extra_amount_to_grow -= simulated_regions.size.y.max(Abs::zero());
                simulated_regions.next();
                simulated_regions.size.y -= self.header_height + self.footer_height;
                disambiguator += 1;
            }
            simulated_regions.size.y -= extra_amount_to_grow;
        }

        // Simulation didn't succeed in 5 attempts.
        Ok(false)
    }
}

/// Auxiliary structure holding state during rowspan simulation.
struct RowspanSimulator<'a> {
    /// The number of finished regions.
    finished: usize,
    /// The state of regions during the simulation.
    regions: Regions<'a>,
    /// The height of the header in the currently simulated region.
    header_height: Abs,
    /// The height of the footer in the currently simulated region.
    footer_height: Abs,
    /// The total spanned height so far in the simulation.
    total_spanned_height: Abs,
    /// Height of the latest spanned gutter row in the simulation.
    /// Zero if it was removed.
    latest_spanned_gutter_height: Abs,
}

impl<'a> RowspanSimulator<'a> {
    /// Creates new rowspan simulation state with the given regions and initial
    /// header and footer heights. Other fields should always start as zero.
    fn new(
        finished: usize,
        regions: Regions<'a>,
        header_height: Abs,
        footer_height: Abs,
    ) -> Self {
        Self {
            finished,
            regions,
            header_height,
            footer_height,
            total_spanned_height: Abs::zero(),
            latest_spanned_gutter_height: Abs::zero(),
        }
    }

    /// Calculates the total spanned height of the rowspan.
    /// Stops calculating if, at any point in the simulation, the value of
    /// `total_spanned_height + amount_to_grow` becomes larger than
    /// `requested_rowspan_height`, as the results are not going to become any
    /// more useful after that point.
    #[allow(clippy::too_many_arguments)]
    fn simulate_rowspan_layout(
        mut self,
        y: usize,
        max_spanned_row: usize,
        amount_to_grow: Abs,
        requested_rowspan_height: Abs,
        mut unbreakable_rows_left: usize,
        layouter: &GridLayouter<'_>,
        engine: &mut Engine,
    ) -> SourceResult<Abs> {
        let spanned_rows = &layouter.grid.rows[y + 1..=max_spanned_row];
        for (offset, row) in spanned_rows.iter().enumerate() {
            if (self.total_spanned_height + amount_to_grow).fits(requested_rowspan_height)
            {
                // Stop the simulation, as the combination of upcoming
                // spanned rows (so far) and the current amount the auto
                // row expands by has already fully covered the height the
                // rowspans need.
                return Ok(self.total_spanned_height);
            }
            let spanned_y = y + 1 + offset;
            let is_gutter = layouter.grid.is_gutter_track(spanned_y);

            if unbreakable_rows_left == 0 {
                // Simulate unbreakable row groups, and skip regions until
                // they fit. There is no risk of infinite recursion, as
                // no auto rows participate in the simulation, so the
                // unbreakable row group simulator won't recursively call
                // 'measure_auto_row' or (consequently) this function.
                let row_group = layouter.simulate_unbreakable_row_group(
                    spanned_y,
                    None,
                    &self.regions,
                    engine,
                    0,
                )?;
                while !self.regions.size.y.fits(row_group.height)
                    && !in_last_with_offset(
                        self.regions,
                        self.header_height + self.footer_height,
                    )
                {
                    self.finish_region(layouter, engine)?;
                }

                unbreakable_rows_left = row_group.rows.len();
            }

            match row {
                // Fixed-size spanned rows are what we are interested in.
                // They contribute a fixed amount of height to our rowspan.
                Sizing::Rel(v) => {
                    let height =
                        v.resolve(layouter.styles).relative_to(self.regions.base().y);
                    self.total_spanned_height += height;
                    if is_gutter {
                        self.latest_spanned_gutter_height = height;
                    }

                    let mut skipped_region = false;
                    while unbreakable_rows_left == 0
                        && !self.regions.size.y.fits(height)
                        && !in_last_with_offset(
                            self.regions,
                            self.header_height + self.footer_height,
                        )
                    {
                        self.finish_region(layouter, engine)?;

                        skipped_region = true;
                    }

                    if !skipped_region || !is_gutter {
                        // No gutter at the top of a new region, so don't
                        // account for it if we just skipped a region.
                        self.regions.size.y -= height;
                    }
                }
                Sizing::Auto => {
                    // We only simulate for rowspans which end at the
                    // current auto row. Therefore, there won't be any
                    // further auto rows.
                    unreachable!();
                }
                // For now, we ignore fractional rows on simulation.
                Sizing::Fr(_) if is_gutter => {
                    self.latest_spanned_gutter_height = Abs::zero();
                }
                Sizing::Fr(_) => {}
            }

            unbreakable_rows_left = unbreakable_rows_left.saturating_sub(1);
        }

        Ok(self.total_spanned_height)
    }

    fn simulate_header_footer_layout(
        &mut self,
        layouter: &GridLayouter<'_>,
        engine: &mut Engine,
    ) -> SourceResult<()> {
        // We can't just use the initial header/footer height on each region,
        // because header/footer height might vary depending on region size if
        // it contains rows with relative lengths. Therefore, we re-simulate
        // headers and footers on each new region.
        // It's true that, when measuring cells, we reduce each height in the
        // backlog to consider the initial header and footer heights; however,
        // our simulation checks what happens AFTER the auto row, so we can
        // just use the original backlog from `self.regions`.
        let disambiguator = self.finished;
        let header_height =
            if let Some(Repeatable::Repeated(header)) = &layouter.grid.header {
                layouter
                    .simulate_header(header, &self.regions, engine, disambiguator)?
                    .height
            } else {
                Abs::zero()
            };

        let footer_height =
            if let Some(Repeatable::Repeated(footer)) = &layouter.grid.footer {
                layouter
                    .simulate_footer(footer, &self.regions, engine, disambiguator)?
                    .height
            } else {
                Abs::zero()
            };

        let mut skipped_region = false;

        // Skip until we reach a fitting region for both header and footer.
        while !self.regions.size.y.fits(header_height + footer_height)
            && self.regions.may_progress()
        {
            self.regions.next();
            self.finished += 1;
            skipped_region = true;
        }

        if let Some(Repeatable::Repeated(header)) = &layouter.grid.header {
            self.header_height = if skipped_region {
                // Simulate headers again, at the new region, as
                // the full region height may change.
                layouter
                    .simulate_header(header, &self.regions, engine, disambiguator)?
                    .height
            } else {
                header_height
            };
        }

        if let Some(Repeatable::Repeated(footer)) = &layouter.grid.footer {
            self.footer_height = if skipped_region {
                // Simulate footers again, at the new region, as
                // the full region height may change.
                layouter
                    .simulate_footer(footer, &self.regions, engine, disambiguator)?
                    .height
            } else {
                footer_height
            };
        }

        // Consume the header's and footer's heights from the new region,
        // but don't consider them spanned. The rowspan does not go over the
        // header or footer (as an invariant, any rowspans spanning any header
        // or footer rows are fully contained within that header's or footer's rows).
        self.regions.size.y -= self.header_height + self.footer_height;

        Ok(())
    }

    fn finish_region(
        &mut self,
        layouter: &GridLayouter<'_>,
        engine: &mut Engine,
    ) -> SourceResult<()> {
        // If a row was pushed to the next region, the immediately
        // preceding gutter row is removed.
        self.total_spanned_height -= self.latest_spanned_gutter_height;
        self.latest_spanned_gutter_height = Abs::zero();
        self.regions.next();
        self.finished += 1;

        self.simulate_header_footer_layout(layouter, engine)
    }
}

/// Subtracts some size from the end of a vector of sizes.
/// For example, subtracting 5pt from \[2pt, 1pt, 3pt\] will result in \[1pt\].
fn subtract_end_sizes(sizes: &mut Vec<Abs>, mut subtract: Abs) {
    while subtract > Abs::zero() && sizes.last().is_some_and(|&size| size <= subtract) {
        subtract -= sizes.pop().unwrap();
    }
    if subtract > Abs::zero() {
        if let Some(last_size) = sizes.last_mut() {
            *last_size -= subtract;
        }
    }
}
