use textlane_core::hast::{Element, Node};
use textlane_core::mdast::AlignKind;
use crate::helpers::text::read_text;
use crate::helpers::value::read_value;
use crate::utils::converter::state::Converter;
use crate::utils::converter::store::{Converted, Id, Kind};
const MAX_ADDED_CELLS: usize = 1 << 19;
pub(crate) fn convert_table<'a, 'n>(
state: &mut Converter<'a, 'n, '_>,
element: &'n Element<'a>,
) -> Converted {
if state.in_table {
let value = state.alloc.alloc_str(&read_text(element));
return Converted::One(state.store.add_text(value, element.position));
}
state.in_table = true;
let (align, headless) = inspect(element);
let children = state.all(&element.children);
let mut rows = state.wrap_specific_content(children, Kind::TableRow);
if headless {
rows.insert(0, state.store.add(Kind::TableRow, vec![], None));
}
for row in &rows {
let cells = state.store.take_children(*row);
let cells = state.wrap_specific_content(cells, Kind::TableCell(None));
state.store.set_children(*row, cells);
}
let columns = pad_spans(state, &rows);
let mut align: Vec<AlignKind> = align
.into_iter()
.map(|align| align.unwrap_or(AlignKind::None))
.collect();
if align.len() < columns {
align.resize(columns, AlignKind::None);
}
state.in_table = false;
Converted::One(state.store.add(Kind::Table(align), rows, element.position))
}
fn pad_spans(state: &mut Converter<'_, '_, '_>, rows: &[Id]) -> usize {
let mut budget = MAX_ADDED_CELLS;
let mut pending: Vec<Vec<(usize, usize)>> = vec![vec![]; rows.len()];
let mut columns = 1;
for (row_index, row) in rows.iter().enumerate() {
let cells = state.store.take_children(*row);
let inserts = std::mem::take(&mut pending[row_index]);
let cells = replay_inserts(state, cells, &inserts);
let mut built = Vec::with_capacity(cells.len());
for cell in cells {
let column = built.len();
built.push(cell);
let Kind::TableCell(Some((row_span, column_span))) =
state.store.items[cell].kind
else {
continue;
};
if row_span <= 1 && column_span <= 1 {
continue;
}
let own = (column_span - 1).min(budget);
budget -= own;
for _ in 0..own {
built.push(state.store.add(Kind::TableCell(None), vec![], None));
}
let row_end = row_index.saturating_add(row_span);
for other in pending.iter_mut().take(row_end).skip(row_index + 1) {
let count = column_span.min(budget);
if count == 0 {
break;
}
budget -= count;
other.push((column, count));
}
}
columns = columns.max(built.len());
state.store.set_children(*row, built);
}
for row in rows {
let missing = columns.saturating_sub(state.store.items[*row].children.len());
let missing = missing.min(budget);
budget -= missing;
for _ in 0..missing {
let empty = state.store.add(Kind::TableCell(None), vec![], None);
state.store.items[*row].children.push(empty);
}
}
columns
}
fn replay_inserts(
state: &mut Converter<'_, '_, '_>,
cells: Vec<Id>,
inserts: &[(usize, usize)],
) -> Vec<Id> {
if inserts.is_empty() {
return cells;
}
let mut length = cells.len();
let mut clamped = Vec::with_capacity(inserts.len());
for (position, count) in inserts {
clamped.push(((*position).min(length), *count));
length += count;
}
let mut free = FreeSlots::new(length);
let mut empty = vec![false; length];
for (position, count) in clamped.iter().rev() {
for _ in 0..*count {
let slot = free.take(*position);
empty[slot] = true;
}
}
let mut cells = cells.into_iter();
let mut built = Vec::with_capacity(length);
for is_empty in empty {
let cell = match is_empty {
true => state.store.add(Kind::TableCell(None), vec![], None),
false => cells.next().unwrap_or_default(),
};
built.push(cell);
}
built
}
struct FreeSlots {
tree: Vec<usize>,
}
impl FreeSlots {
fn new(length: usize) -> Self {
let mut tree = vec![0; length + 1];
for index in 1..=length {
tree[index] += 1;
let parent = index + (index & index.wrapping_neg());
if parent <= length {
tree[parent] += tree[index];
}
}
Self { tree }
}
fn take(&mut self, rank: usize) -> usize {
let length = self.tree.len() - 1;
let mut position = 0;
let mut remaining = rank + 1;
let mut step = length.next_power_of_two();
while step > 0 {
let next = position + step;
if next <= length && self.tree[next] < remaining {
position = next;
remaining -= self.tree[next];
}
step /= 2;
}
let mut index = position + 1;
while index <= length {
self.tree[index] -= 1;
index += index & index.wrapping_neg();
}
position
}
}
fn inspect(table: &Element<'_>) -> (Vec<Option<AlignKind>>, bool) {
let mut align: Vec<Option<AlignKind>> = vec![None];
let mut headless = true;
let mut row_index = 0;
let mut cell_index = 0;
let mut stack: Vec<&Node<'_>> = table.children.iter().rev().collect();
while let Some(node) = stack.pop() {
let Node::Element(element) = node else {
continue;
};
match element.tag_name {
"table" => continue,
"th" | "td" => {
if align.get(cell_index).copied().flatten().is_none() {
let value = match &*read_value(element, "align") {
"" => Some(AlignKind::None),
"center" => Some(AlignKind::Center),
"left" => Some(AlignKind::Left),
"right" => Some(AlignKind::Right),
_ => None,
};
if let Some(value) = value {
if align.len() <= cell_index {
align.resize(cell_index + 1, None);
}
align[cell_index] =
Some(value).filter(|value| *value != AlignKind::None);
}
}
if headless && row_index < 2 && element.tag_name == "th" {
headless = false;
}
cell_index += 1;
}
"thead" => headless = false,
"tr" => {
row_index += 1;
cell_index = 0;
}
_ => {}
}
stack.extend(element.children.iter().rev());
}
(align, headless)
}