use crate::{
    measure::MeasuredBlock,
    model::{Alignment, Margins},
};
use super::{
    LayoutItem, LayoutPage, PageCustomProperties,
    layout_block, line_alignment, layout_text_line,
};

pub(super) fn snaking_block_height(block: &MeasuredBlock) -> f32 {
    match block {
        MeasuredBlock::Text(t) => t.height,
        MeasuredBlock::Table(t) => t.height,
        MeasuredBlock::Image(i) => i.height,
        MeasuredBlock::Qr(q) => q.height,
        MeasuredBlock::Svg(s) => s.height,
        MeasuredBlock::Columns(c) => c.height,
        MeasuredBlock::List(l) => l.height,
        MeasuredBlock::Section(s) => s.height,
        MeasuredBlock::Canvas(c) => c.height,
        MeasuredBlock::Absolute(_) => 0.0,
        MeasuredBlock::Toc(t) => t.height,
    }
}

fn snaking_finalize_page(
    layout_columns: &mut Vec<Vec<LayoutItem>>,
    pages: &mut Vec<LayoutPage>,
    cur_page_idx: usize,
    x: f32,
    available_width: f32,
    start_y: f32,
    col_y: f32,
) {
    let has_content = layout_columns.iter().any(|c| !c.is_empty());
    if !has_content {
        return;
    }
    let cols = std::mem::take(layout_columns);
    pages[cur_page_idx].items.push(LayoutItem::Columns(super::LayoutColumns {
        x,
        y: start_y,
        width: available_width,
        height: (col_y - start_y).max(0.0),
        columns: cols,
    }));
}

pub(super) fn layout_snaking_columns(
    cols: &crate::measure::MeasuredColumns,
    x: f32,
    y: &mut f32,
    available_width: f32,
    bottom_limit: f32,
    page_margins_top: f32,
    pages: &mut Vec<LayoutPage>,
) {
    let num_columns = cols.columns.len();
    if num_columns == 0 {
        return;
    }

    let column_gap = cols.column_gap;
    let column_width = if num_columns > 1 {
        (available_width - column_gap * (num_columns as f32 - 1.0)) / num_columns as f32
    } else {
        available_width
    };

    let all_blocks: Vec<&MeasuredBlock> = cols.columns.iter().flatten().collect();

    let num_cols_f = num_columns as f32;
    let available_h = bottom_limit - *y;
    let _target_per_col = available_h / num_cols_f;

    let mut cur_page_idx = pages.len() - 1;
    let mut layout_columns: Vec<Vec<LayoutItem>> = vec![Vec::new(); num_columns];
    let mut col_idx: usize = 0;
    let mut col_y = *y;
    let mut start_y = *y;

    for block in &all_blocks {
        match block {
            MeasuredBlock::Text(text) => {
                let margin = text.style.margin.unwrap_or(Margins::all(0.0));
                let layout_available_width = (column_width - margin.left - margin.right).max(0.0);
                let leading_indent = text.style.leading_indent;
                col_y += margin.top;

                for (line_idx, line) in text.lines.iter().enumerate() {
                    if col_y + line.line_height > bottom_limit {
                        if col_idx + 1 < num_columns {
                            col_idx += 1;
                            col_y = start_y;
                        } else {
                            snaking_finalize_page(&mut layout_columns, pages, cur_page_idx, x, available_width, start_y, col_y);
                            pages.push(LayoutPage {
                                items: Vec::new(),
                                background: None,
                                watermark: None,
                                header: None,
                                footer: None,
                                custom_properties: PageCustomProperties::default(),
                            });
                            cur_page_idx = pages.len() - 1;
                            layout_columns = vec![Vec::new(); num_columns];
                            col_idx = 0;
                            col_y = page_margins_top;
                            start_y = page_margins_top;
                        }
                    }

                    let layout_x = x + col_idx as f32 * (column_width + column_gap) + margin.left;
                    let alignment = line_alignment(line);
                    let is_last_line = line_idx == text.lines.len() - 1;
                    let is_justify = alignment == Alignment::Justify && !is_last_line;
                    let indent = if line_idx == 0 { leading_indent } else { 0.0 };

                    let line_x = match alignment {
                        Alignment::Left | Alignment::Justify => layout_x + indent,
                        Alignment::Center => layout_x + (layout_available_width - line.width) / 2.0,
                        Alignment::Right => layout_x + layout_available_width - line.width,
                    };

                    layout_columns[col_idx].push(LayoutItem::Text(layout_text_line(line, line_x, col_y, layout_available_width, is_justify, text.id.clone(), text.page_reference.clone())));
                    col_y += line.line_height;
                }

                col_y += margin.bottom;
            }
            _ => {
                let block_height = snaking_block_height(block);
                let col_height = (bottom_limit - start_y) / num_cols_f;
                let col_end = start_y + col_height * (col_idx as f32 + 1.0);

                if col_y + block_height > col_end {
                    if col_idx + 1 < num_columns {
                        col_idx += 1;
                        col_y = start_y;
                    }
                    let new_col_end = start_y + col_height * (col_idx as f32 + 1.0);
                    if col_y + block_height > new_col_end && col_y > start_y {
                        snaking_finalize_page(&mut layout_columns, pages, cur_page_idx, x, available_width, start_y, col_y);
                        pages.push(LayoutPage {
                            items: Vec::new(),
                            background: None,
                            watermark: None,
                            header: None,
                            footer: None,
                            custom_properties: PageCustomProperties::default(),
                        });
                        cur_page_idx = pages.len() - 1;
                        layout_columns = vec![Vec::new(); num_columns];
                        col_idx = 0;
                        col_y = page_margins_top;
                        start_y = page_margins_top;
                    }
                }

                let col_x = x + col_idx as f32 * (column_width + column_gap);
                let mut temp_pages = vec![LayoutPage {
                    items: Vec::new(),
                    background: None,
                    watermark: None,
                    header: None,
                    footer: None,
                    custom_properties: PageCustomProperties::default(),
                }];

                let col_start_y = col_y;
                layout_block(block, col_x, &mut col_y, column_width, bottom_limit, col_start_y, &mut temp_pages);

                if !temp_pages.is_empty() {
                    layout_columns[col_idx].extend(temp_pages[0].items.clone());
                }

                for extra_page in temp_pages.iter().skip(1) {
                    snaking_finalize_page(&mut layout_columns, pages, cur_page_idx, x, available_width, start_y, col_y);
                    pages.push(LayoutPage {
                        items: Vec::new(),
                        background: None,
                        watermark: None,
                        header: None,
                        footer: None,
                        custom_properties: PageCustomProperties::default(),
                    });
                    cur_page_idx = pages.len() - 1;
                    layout_columns = vec![Vec::new(); num_columns];
                    col_idx = 0;
                    col_y = page_margins_top;
                    start_y = page_margins_top;
                    layout_columns[0].extend(extra_page.items.clone());
                }
            }
        }
    }

    snaking_finalize_page(&mut layout_columns, pages, cur_page_idx, x, available_width, start_y, col_y);
    *y = col_y;
}
