use std::collections::HashMap;
use textlane_core::hast::{Element, Node};
use crate::actions::p::convert::build_paragraph;
use crate::helpers::phrasing::is_phrasing;
use crate::helpers::value::{find_string, find_value, is_truthy};
use crate::utils::converter::state::Converter;
use crate::utils::converter::store::{Converted, Id, Kind};
const STACK_RED_ZONE: usize = 64 * 1024;
const STACK_GROWTH: usize = 1024 * 1024;
pub(crate) fn convert_li<'a, 'n>(
state: &mut Converter<'a, 'n, '_>,
element: &'n Element<'a>,
) -> Converted {
let checkbox = find_leading_checkbox(element);
let checked =
checkbox.map(|(checkbox, _)| is_truthy(find_value(checkbox, "checked")));
let spread = is_spread_out(&mut state.spread, element);
let children = match checkbox {
Some((_, depth)) => all_without_checkbox(state, &element.children, depth),
None => state.all(&element.children),
};
let children = state.wrap_flow(children);
let kind = Kind::ListItem { spread, checked };
Converted::One(state.store.add(kind, children, element.position))
}
fn find_leading_checkbox<'n, 'a>(
element: &'n Element<'a>,
) -> Option<(&'n Element<'a>, usize)> {
let mut parent = element;
let mut depth = 0;
loop {
match parent.children.first() {
Some(Node::Element(head))
if head.tag_name == "input"
&& matches!(
find_string(head, "type"),
Some("checkbox" | "radio")
) =>
{
return Some((head, depth));
}
Some(Node::Element(head)) if head.tag_name == "p" => {
parent = head;
depth += 1;
}
_ => return None,
}
}
}
fn all_without_checkbox<'a, 'n>(
state: &mut Converter<'a, 'n, '_>,
children: &'n [Node<'a>],
depth: usize,
) -> Vec<Id> {
let mut results = vec![];
if depth > 0
&& let Some(Node::Element(head)) = children.first()
&& find_string(head, "dataMdast") != Some("ignore")
{
let nested = stacker::maybe_grow(STACK_RED_ZONE, STACK_GROWTH, || {
all_without_checkbox(state, &head.children, depth - 1)
});
if let Converted::One(paragraph) = build_paragraph(state, head, nested) {
results.push(paragraph);
}
}
results.extend(state.all(children.get(1..).unwrap_or_default()));
results
}
fn is_spread_out(memo: &mut HashMap<usize, bool>, element: &Element<'_>) -> bool {
let key = element as *const Element<'_> as usize;
if let Some(spread) = memo.get(&key) {
return *spread;
}
let mut spread = false;
let mut seen_flow = false;
for child in &element.children {
let Node::Element(child_element) = child else {
continue;
};
if is_phrasing(child) {
continue;
}
if child_element.tag_name == "p"
|| seen_flow
|| stacker::maybe_grow(STACK_RED_ZONE, STACK_GROWTH, || {
is_spread_out(memo, child_element)
})
{
spread = true;
break;
}
seen_flow = true;
}
memo.insert(key, spread);
spread
}