use std::collections::HashSet;

use crate::measure::MeasuredTable;
use crate::model::{Alignment, TableStyle};

use super::{
    layout_run, layout_text_line, line_alignment,
    LayoutImage, LayoutItem, LayoutLine, LayoutQr, LayoutRun, LayoutSvg,
    LayoutTable, LayoutTableCell, LayoutTableRow,
};
use super::helpers::align_block_x;

pub(super) fn layout_table(table: &MeasuredTable, x: f32, y: f32) -> LayoutTable {
    layout_table_with_break(table, x, y, false, false)
}

pub(super) fn layout_table_with_break(table: &MeasuredTable, x: f32, y: f32, is_first_fragment: bool, is_last_fragment: bool) -> LayoutTable {
    let width: f32 = table.column_widths.iter().sum();
    let column_widths = &table.column_widths;
    let mut current_y = y;
    let mut rows = Vec::new();
    let mut borders = Vec::new();

    let mut covered: HashSet<(usize, usize)> = HashSet::new();
    // Track edges explicitly removed by per-cell border config.
    // Adjacent cells WITHOUT border config should skip these edges.
    // Key: (x1, y1, x2, y2) of the removed border line
    let mut removed_edges: HashSet<(i32, i32, i32, i32)> = HashSet::new();
    // Track edges with custom border colors (from cells with border_color set).
    // These edges should never be skipped by colored_edges logic.
    let mut colored_edges: HashSet<(i32, i32, i32, i32)> = HashSet::new();

    // Pre-compute header bottom y for HeaderLineOnly style
    let header_bottom_y = if table.table_style == Some(TableStyle::HeaderLineOnly) {
        let header_count = table.header_rows.unwrap_or(1);
        let mut hy = y;
        for (i, row) in table.rows.iter().enumerate() {
            if i >= header_count { break; }
            hy += row.height;
        }
        Some(hy)
    } else {
        None
    };

    for (row_idx, row) in table.rows.iter().enumerate() {
        let mut current_x = x;
        let mut col = 0usize;
        let mut cells = Vec::new();

        let mut cell_iter = row.cells.iter();
        loop {
            // 跳过 covered 列时不消费数据 cell，仅推进列坐标
            while col < column_widths.len() && covered.contains(&(row_idx, col)) {
                current_x += column_widths[col];
                col += 1;
            }

            let cell = match cell_iter.next() {
                Some(c) => c,
                None => break,
            };

            let row_span = cell.row_span.max(1);
            let cell_height: f32 = if row_span > 1 {
                (row_idx..(row_idx + row_span).min(table.rows.len()))
                    .map(|i| table.rows[i].height)
                    .sum()
            } else {
                row.height
            };

            let content_width = (cell.width - cell.padding * 2.0).max(0.0);
            let runs = layout_table_cell_runs(cell, current_x + cell.padding, current_y + cell.padding, content_width);
            let items = layout_table_cell_items(cell, current_x + cell.padding, current_y + cell.padding, content_width);
            let raw_borders = cell_borders(current_x, current_y, cell.width, cell_height);
            let filtered_borders = filter_borders(&raw_borders, table.table_style, header_bottom_y);
            let cell_filtered_borders = filter_cell_borders(&filtered_borders, &cell.border, current_x, current_y, cell.width, cell_height);

            // Track edges removed by per-cell border config (for adjacent cell filtering)
            if cell.border.is_some() {
                let filtered_set: HashSet<_> = cell_filtered_borders.iter()
                    .map(|b| edge_key(b))
                    .collect();
                for b in &filtered_borders {
                    let key = edge_key(b);
                    if !filtered_set.contains(&key) {
                        removed_edges.insert(key);
                    }
                }
            }

            // Track edges with custom colors (these should never be skipped)
            if cell.style.border_color.is_some() {
                for b in &cell_filtered_borders {
                    colored_edges.insert(edge_key(b));
                }
            }

            cells.push(LayoutTableCell {
                x: current_x,
                y: current_y,
                width: cell.width,
                height: cell_height,
                padding: cell.padding,
                runs,
                items,
                borders: cell_filtered_borders.clone(),
                col_span: cell.col_span,
                row_span,
                fill_color: cell.style.fill_color.clone(),
                fill_opacity: cell.style.fill_opacity,
                border_colors: cell.style.border_color.clone(),
                overlay_pattern: cell.style.overlay_pattern.clone(),
                overlay_opacity: cell.style.overlay_opacity,
                border: cell.border.clone(),
            });
            borders.extend(cell_filtered_borders);

            for r in 1..row_span {
                if row_idx + r < table.rows.len() {
                    for c in 0..cell.col_span {
                        covered.insert((row_idx + r, col + c));
                    }
                }
            }

            col += cell.col_span;
            current_x += cell.width;
        }
        rows.push(LayoutTableRow { y: current_y, height: row.height, cells });
        current_y += row.height;
    }

    // Remove borders from adjacent cells WITHOUT border config on edges explicitly removed by configured cells
    if !removed_edges.is_empty() {
        for row in &mut rows {
            for cell in &mut row.cells {
                // Only filter cells that don't have their own border config
                if cell.border.is_none() {
                    cell.borders.retain(|b| !removed_edges.contains(&edge_key(b)));
                }
            }
        }
    }

    borders.dedup_by(|a, b| a.x1 == b.x1 && a.y1 == b.y1 && a.x2 == b.x2 && a.y2 == b.y2);

    // Filter borders based on h_line_when_broken
    if table.h_line_when_broken == Some(false) {
        let last_row_y = rows.last().map(|r| r.y + r.height).unwrap_or(y);
        let first_row_y = rows.first().map(|r| r.y).unwrap_or(y);

        borders.retain(|line| {
            let is_horizontal = (line.y1 - line.y2).abs() < 0.01;
            if !is_horizontal {
                return true;
            }
            // Remove bottom border of last row if this is NOT the last fragment (break point)
            if !is_last_fragment && (line.y1 - last_row_y).abs() < 0.01 {
                return false;
            }
            // Remove top border of first row if this is NOT the first fragment (break point)
            if !is_first_fragment && (line.y1 - first_row_y).abs() < 0.01 {
                return false;
            }
            true
        });

        // Also filter cell borders
        for row in &mut rows {
            for cell in &mut row.cells {
                cell.borders.retain(|line| {
                    let is_horizontal = (line.y1 - line.y2).abs() < 0.01;
                    if !is_horizontal {
                        return true;
                    }
                    if !is_last_fragment && (line.y1 - last_row_y).abs() < 0.01 {
                        return false;
                    }
                    if !is_first_fragment && (line.y1 - first_row_y).abs() < 0.01 {
                        return false;
                    }
                    true
                });
            }
        }
    }

    LayoutTable { x, y, width, height: table.height, rows, borders, layout_config: table.layout_config.clone(), h_line_when_broken: table.h_line_when_broken, colored_edges }
}

pub(super) fn layout_table_cell_runs(cell: &crate::measure::MeasuredTableCell, x: f32, y: f32, content_width: f32) -> Vec<LayoutRun> {
    let mut runs = Vec::new();

    // Calculate total content height
    let content_height: f32 = cell.lines.iter().map(|l| l.line_height).sum();

    // Calculate y offset based on vertical alignment
    let available_height = cell.height - cell.padding * 2.0;
    let y_offset = match cell.vertical_alignment {
        Some(crate::model::VerticalAlignment::Top) | None => 0.0,
        Some(crate::model::VerticalAlignment::Middle) => ((available_height - content_height) / 2.0).max(0.0),
        Some(crate::model::VerticalAlignment::Bottom) => (available_height - content_height).max(0.0),
    };

    let mut current_y = y + y_offset;
    let line_count = cell.lines.len();
    for (line_idx, line) in cell.lines.iter().enumerate() {
        let alignment = line_alignment(line);
        let is_last_line = line_idx == line_count - 1;
        let is_justify = alignment == Alignment::Justify && !is_last_line;

        let line_x = match alignment {
            Alignment::Left | Alignment::Justify => x,
            Alignment::Center => x + (content_width - line.width).max(0.0) / 2.0,
            Alignment::Right => x + (content_width - line.width).max(0.0),
        };
        let mut x_offset = 0.0;
        for run in &line.runs {
            let mut layout_run = layout_run(run, line_x + x_offset);
            layout_run.y_offset = current_y;
            layout_run.is_justify = is_justify;
            layout_run.available_width = content_width;
            runs.push(layout_run);
            x_offset += run.width;
        }
        current_y += line.line_height;
    }
    runs
}

pub(super) fn layout_table_cell_items(cell: &crate::measure::MeasuredTableCell, x: f32, y: f32, content_width: f32) -> Vec<LayoutItem> {
    let mut items = Vec::new();

    // Calculate total content height
    let content_height: f32 = cell.items.iter().map(|block| match block {
        crate::measure::MeasuredBlock::Text(t) => t.height,
        crate::measure::MeasuredBlock::Image(i) => i.height,
        crate::measure::MeasuredBlock::Svg(s) => s.height,
        crate::measure::MeasuredBlock::Qr(q) => q.height,
        crate::measure::MeasuredBlock::Table(t) => t.height,
        _ => 0.0,
    }).sum();

    // Calculate y offset based on vertical alignment
    let available_height = cell.height - cell.padding * 2.0;
    let y_offset = match cell.vertical_alignment {
        Some(crate::model::VerticalAlignment::Top) | None => 0.0,
        Some(crate::model::VerticalAlignment::Middle) => ((available_height - content_height) / 2.0).max(0.0),
        Some(crate::model::VerticalAlignment::Bottom) => (available_height - content_height).max(0.0),
    };

    let mut current_y = y + y_offset;
    for block in &cell.items {
        match block {
            crate::measure::MeasuredBlock::Text(text) => {
                for (line_idx, line) in text.lines.iter().enumerate() {
                    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 line_x = match alignment {
                        Alignment::Left | Alignment::Justify => x,
                        Alignment::Center => x + (content_width - line.width).max(0.0) / 2.0,
                        Alignment::Right => x + (content_width - line.width).max(0.0),
                    };
                    items.push(LayoutItem::Text(layout_text_line(line, line_x, current_y, content_width, is_justify, text.id.clone(), text.page_reference.clone())));
                    current_y += line.line_height;
                }
            }
            crate::measure::MeasuredBlock::Image(image) => {
                let img_x = align_block_x(x, content_width, image.width, image.alignment);
                items.push(LayoutItem::Image(LayoutImage {
                    x: img_x,
                    y: current_y,
                    width: image.width,
                    height: image.height,
                    data: image.data.clone(),
                    format: image.format,
                    intrinsic_width: image.intrinsic_width,
                    intrinsic_height: image.intrinsic_height,
                    cover: image.cover,
                    opacity: image.opacity,
                    link: image.link.clone(),
                    link_to_page: image.link_to_page,
                    link_to_destination: image.link_to_destination.clone(),
                }));
                current_y += image.height;
            }
            crate::measure::MeasuredBlock::Svg(svg) => {
                let svg_x = align_block_x(x, content_width, svg.width, svg.alignment);
                items.push(LayoutItem::Svg(LayoutSvg {
                    x: svg_x,
                    y: current_y,
                    width: svg.width,
                    height: svg.height,
                    view_width: svg.view_width,
                    view_height: svg.view_height,
                    shapes: svg.shapes.clone(),
                    link: svg.link.clone(),
                    link_to_page: svg.link_to_page,
                    link_to_destination: svg.link_to_destination.clone(),
                }));
                current_y += svg.height;
            }
            crate::measure::MeasuredBlock::Qr(qr) => {
                let qr_x = align_block_x(x, content_width, qr.width, qr.alignment);
                items.push(LayoutItem::Qr(LayoutQr {
                    x: qr_x,
                    y: current_y,
                    width: qr.width,
                    height: qr.height,
                    modules: qr.modules,
                    dark_modules: qr.dark_modules.clone(),
                    foreground: qr.foreground.clone(),
                    background: qr.background.clone(),
                    padding: qr.padding,
                }));
                current_y += qr.height;
            }
            crate::measure::MeasuredBlock::Table(table) => {
                items.push(LayoutItem::Table(layout_table(table, x, current_y)));
                current_y += table.height;
            }
            _ => {}
        }
    }
    items
}

pub fn cell_borders(x: f32, y: f32, width: f32, height: f32) -> Vec<LayoutLine> {
    vec![
        LayoutLine { x1: x, y1: y, x2: x + width, y2: y },
        LayoutLine { x1: x + width, y1: y, x2: x + width, y2: y + height },
        LayoutLine { x1: x + width, y1: y + height, x2: x, y2: y + height },
        LayoutLine { x1: x, y1: y + height, x2: x, y2: y },
    ]
}

/// Normalize a border line to a canonical key, so opposite directions of the
/// same edge produce the same key. Coordinates are multiplied by 10 and truncated to i32.
pub fn edge_key(b: &LayoutLine) -> (i32, i32, i32, i32) {
    let a = ((b.x1 * 10.0) as i32, (b.y1 * 10.0) as i32);
    let c = ((b.x2 * 10.0) as i32, (b.y2 * 10.0) as i32);
    if a <= c { (a.0, a.1, c.0, c.1) } else { (c.0, c.1, a.0, a.1) }
}

pub fn filter_borders(borders: &[LayoutLine], style: Option<TableStyle>, header_bottom_y: Option<f32>) -> Vec<LayoutLine> {
    let Some(style) = style else {
        return borders.to_vec();
    };
    const EPS: f32 = 0.01;
    borders.iter().filter(|line| {
        let is_horizontal = (line.y1 - line.y2).abs() < EPS;
        let is_vertical = (line.x1 - line.x2).abs() < EPS;
        match style {
            TableStyle::NoBorders => false,
            TableStyle::HeaderLineOnly => {
                is_horizontal && header_bottom_y.map_or(false, |hy| (line.y1 - hy).abs() < EPS)
            }
            TableStyle::LightHorizontalLines => is_horizontal,
            TableStyle::LightVerticalLines => is_vertical,
            TableStyle::CrossLines => true,
        }
    }).cloned().collect()
}

/// Determine which side of a cell a border line represents.
/// Returns `Some(side)` where side is: 0=top, 1=right, 2=bottom, 3=left.
/// Returns `None` if the line doesn't match any cell side (shouldn't happen for cell_borders() output).
pub fn border_side(line: &LayoutLine, cell_x: f32, cell_y: f32, cell_w: f32, cell_h: f32) -> Option<usize> {
    const EPS: f32 = 0.5;
    let is_horizontal = (line.y1 - line.y2).abs() < EPS;
    let is_vertical = (line.x1 - line.x2).abs() < EPS;
    if is_horizontal {
        if (line.y1 - cell_y).abs() < EPS {
            Some(0) // top
        } else if (line.y1 - (cell_y + cell_h)).abs() < EPS {
            Some(2) // bottom
        } else {
            None
        }
    } else if is_vertical {
        if (line.x1 - (cell_x + cell_w)).abs() < EPS {
            Some(1) // right
        } else if (line.x1 - cell_x).abs() < EPS {
            Some(3) // left
        } else {
            None
        }
    } else {
        None
    }
}

/// Filter cell borders based on per-cell border configuration.
/// `cell_borders()` returns borders in order: [top, right, bottom, left].
/// `border` field is [left, top, right, bottom].
pub fn filter_cell_borders(borders: &[LayoutLine], border: &Option<Vec<bool>>, cell_x: f32, cell_y: f32, cell_w: f32, cell_h: f32) -> Vec<LayoutLine> {
    let Some(sides) = border else {
        return borders.to_vec();
    };
    // Map from cell_borders side to border index:
    // side 0 (top) → border[1]
    // side 1 (right) → border[2]
    // side 2 (bottom) → border[3]
    // side 3 (left) → border[0]
    let side_to_border = [1, 2, 3, 0];
    borders.iter().filter(|line| {
        if let Some(side) = border_side(line, cell_x, cell_y, cell_w, cell_h) {
            let border_idx = side_to_border[side];
            sides.get(border_idx).copied().unwrap_or(true)
        } else {
            true // keep borders we can't classify
        }
    }).cloned().collect()
}

pub(super) fn row_span_groups(table: &MeasuredTable) -> Vec<Vec<usize>> {
    let mut groups: Vec<Vec<usize>> = Vec::new();
    let mut pending: HashSet<usize> = HashSet::new();

    for row_idx in 0..table.rows.len() {
        for cell in &table.rows[row_idx].cells[..] {
            if cell.row_span > 1 {
                for r in 1..cell.row_span {
                    if row_idx + r < table.rows.len() {
                        pending.insert(row_idx + r);
                    }
                }
            }
        }

        if pending.contains(&row_idx) {
            groups.last_mut().unwrap().push(row_idx);
        } else {
            groups.push(vec![row_idx]);
        }
        pending.remove(&row_idx);
    }

    groups
}

pub(super) fn build_header_table(table: &MeasuredTable, data_groups: &[Vec<usize>]) -> MeasuredTable {
    let header_indices: Vec<usize> = (0..table.header_rows.unwrap_or(0)).collect();
    let mut full_rows = header_indices;
    for group in data_groups {
        full_rows.extend(group);
    }
    let height: f32 = full_rows.iter().map(|&idx| table.rows[idx].height).sum();
    MeasuredTable {
        column_widths: table.column_widths.clone(),
        rows: full_rows.iter().map(|&idx| table.rows[idx].clone()).collect(),
        style: table.style.clone(),
        height,
        header_rows: None,
        dont_break_rows: table.dont_break_rows,
        page_break: None,
        heights: table.heights.clone(),
        keep_with_header_rows: table.keep_with_header_rows,
        table_style: table.table_style.clone(),
        layout_config: table.layout_config.clone(),
        h_line_when_broken: table.h_line_when_broken,
        origin_node_idx: table.origin_node_idx,
    }
}
