use std::borrow::Cow;
// TODO: Add a check for unexpected keys in the properties
use std::collections::BTreeSet;
use std::collections::HashSet;

use super::elisp_fact::ElispFact;
use super::elisp_fact::GetElispFact;
use super::sexp::unquote;
use super::sexp::Token;
use super::util::compare_standard_properties;
use super::util::get_property;
use super::util::get_property_boolean;
use super::util::get_property_numeric;
use super::util::get_property_quoted_string;
use super::util::get_property_unquoted_atom;
use crate::types::AngleLink;
use crate::types::AstNode;
use crate::types::BabelCall;
use crate::types::Bold;
use crate::types::CenterBlock;
use crate::types::CheckboxType;
use crate::types::Citation;
use crate::types::CitationReference;
use crate::types::Clock;
use crate::types::Code;
use crate::types::Comment;
use crate::types::CommentBlock;
use crate::types::Date;
use crate::types::DayOfMonth;
use crate::types::DayOfMonthInner;
use crate::types::DiarySexp;
use crate::types::Document;
use crate::types::DocumentElement;
use crate::types::Drawer;
use crate::types::DynamicBlock;
use crate::types::Entity;
use crate::types::ExampleBlock;
use crate::types::ExportBlock;
use crate::types::ExportSnippet;
use crate::types::FixedWidthArea;
use crate::types::FootnoteDefinition;
use crate::types::FootnoteReference;
use crate::types::GetStandardProperties;
use crate::types::Heading;
use crate::types::HorizontalRule;
use crate::types::Hour;
use crate::types::HourInner;
use crate::types::InlineBabelCall;
use crate::types::InlineSourceBlock;
use crate::types::Italic;
use crate::types::Keyword;
use crate::types::LatexEnvironment;
use crate::types::LatexFragment;
use crate::types::LineBreak;
use crate::types::Minute;
use crate::types::MinuteInner;
use crate::types::Month;
use crate::types::MonthInner;
use crate::types::NodeProperty;
use crate::types::OrgMacro;
use crate::types::Paragraph;
use crate::types::PlainLink;
use crate::types::PlainList;
use crate::types::PlainListItem;
use crate::types::PlainListItemCounter;
use crate::types::PlainListItemPreBlank;
use crate::types::PlainListType;
use crate::types::PlainText;
use crate::types::Planning;
use crate::types::PriorityCookie;
use crate::types::PropertyDrawer;
use crate::types::QuoteBlock;
use crate::types::RadioLink;
use crate::types::RadioTarget;
use crate::types::RegularLink;
use crate::types::RepeaterType;
use crate::types::RepeaterWarningDelayValueType;
use crate::types::Section;
use crate::types::SpecialBlock;
use crate::types::SrcBlock;
use crate::types::StandardProperties;
use crate::types::StatisticsCookie;
use crate::types::StrikeThrough;
use crate::types::Subscript;
use crate::types::Superscript;
use crate::types::Table;
use crate::types::TableCell;
use crate::types::TableRow;
use crate::types::TableRowType;
use crate::types::Target;
use crate::types::Time;
use crate::types::TimeUnit;
use crate::types::Timestamp;
use crate::types::TimestampRangeType;
use crate::types::TimestampType;
use crate::types::TodoKeywordType;
use crate::types::Underline;
use crate::types::Verbatim;
use crate::types::VerseBlock;
use crate::types::WarningDelayType;
use crate::types::Year;
use crate::types::YearInner;

#[derive(Debug)]
pub enum DiffEntry<'b, 's> {
    DiffResult(DiffResult<'b, 's>),
    DiffLayer(DiffLayer<'b, 's>),
}

#[derive(Debug)]
pub struct DiffResult<'b, 's> {
    status: DiffStatus,
    name: Cow<'s, str>,
    message: Option<String>,
    children: Vec<DiffEntry<'b, 's>>,
    rust_source: &'s str,
    #[allow(dead_code)]
    emacs_token: &'b Token<'s>,
}

#[derive(Debug, PartialEq)]
enum DiffStatus {
    Good,
    Bad,
}

#[derive(Debug)]
pub struct DiffLayer<'b, 's> {
    name: Cow<'s, str>,
    children: Vec<DiffEntry<'b, 's>>,
}

impl<'b, 's> From<DiffResult<'b, 's>> for DiffEntry<'b, 's> {
    fn from(value: DiffResult<'b, 's>) -> Self {
        DiffEntry::DiffResult(value)
    }
}

impl<'b, 's> From<DiffLayer<'b, 's>> for DiffEntry<'b, 's> {
    fn from(value: DiffLayer<'b, 's>) -> Self {
        DiffEntry::DiffLayer(value)
    }
}

impl<'b, 's> DiffEntry<'b, 's> {
    fn has_bad_children(&self) -> bool {
        match self {
            DiffEntry::DiffResult(diff) => &diff.children,
            DiffEntry::DiffLayer(diff) => &diff.children,
        }
        .iter()
        .any(|child| child.is_immediately_bad() || child.has_bad_children())
    }

    fn is_immediately_bad(&self) -> bool {
        match self {
            DiffEntry::DiffResult(diff) => diff.status == DiffStatus::Bad,
            DiffEntry::DiffLayer(_) => false,
        }
    }

    pub fn is_bad(&self) -> bool {
        self.is_immediately_bad() || self.has_bad_children()
    }

    pub fn print(&self, original_document: &str) -> Result<(), Box<dyn std::error::Error>> {
        self.print_indented(0, original_document)
    }

    fn print_indented(
        &self,
        indentation: usize,
        original_document: &str,
    ) -> Result<(), Box<dyn std::error::Error>> {
        match self {
            DiffEntry::DiffResult(diff) => diff.print_indented(indentation, original_document),
            DiffEntry::DiffLayer(diff) => diff.print_indented(indentation, original_document),
        }
    }
}

impl<'b, 's> DiffResult<'b, 's> {
    fn print_indented(
        &self,
        indentation: usize,
        original_document: &str,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let status_text = {
            match self.status {
                DiffStatus::Good => {
                    if self.has_bad_children() {
                        format!(
                            "{color}BADCHILD{reset}",
                            color = DiffResult::foreground_color(255, 255, 0),
                            reset = DiffResult::reset_color(),
                        )
                    } else {
                        format!(
                            "{color}GOOD{reset}",
                            color = DiffResult::foreground_color(0, 255, 0),
                            reset = DiffResult::reset_color(),
                        )
                    }
                }
                DiffStatus::Bad => format!(
                    "{color}BAD{reset}",
                    color = DiffResult::foreground_color(255, 0, 0),
                    reset = DiffResult::reset_color(),
                ),
            }
        };
        let rust_offset = self.rust_source.as_ptr() as usize - original_document.as_ptr() as usize;
        let preceding_text = &original_document[..rust_offset];
        println!(
            "{indentation}{status_text} {name} char({char_offset}) {message}",
            indentation = "  ".repeat(indentation),
            status_text = status_text,
            name = self.name,
            char_offset = preceding_text.chars().count() + 1,
            message = self.message.as_ref().map(|m| m.as_str()).unwrap_or("")
        );
        for child in self.children.iter() {
            child.print_indented(indentation + 1, original_document)?;
        }
        Ok(())
    }

    fn has_bad_children(&self) -> bool {
        self.children
            .iter()
            .any(|child| child.is_immediately_bad() || child.has_bad_children())
    }

    fn foreground_color(red: u8, green: u8, blue: u8) -> String {
        if DiffResult::should_use_color() {
            format!(
                "\x1b[38;2;{red};{green};{blue}m",
                red = red,
                green = green,
                blue = blue
            )
        } else {
            String::new()
        }
    }

    #[allow(dead_code)]
    fn background_color(red: u8, green: u8, blue: u8) -> String {
        if DiffResult::should_use_color() {
            format!(
                "\x1b[48;2;{red};{green};{blue}m",
                red = red,
                green = green,
                blue = blue
            )
        } else {
            String::new()
        }
    }

    fn reset_color() -> &'static str {
        if DiffResult::should_use_color() {
            "\x1b[0m"
        } else {
            ""
        }
    }

    fn should_use_color() -> bool {
        !std::env::var("NO_COLOR").is_ok_and(|val| !val.is_empty())
    }
}

impl<'b, 's> DiffLayer<'b, 's> {
    fn has_bad_children(&self) -> bool {
        self.children
            .iter()
            .any(|child| child.is_immediately_bad() || child.has_bad_children())
    }

    fn print_indented(
        &self,
        indentation: usize,
        original_document: &str,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let status_text = if self.has_bad_children() {
            format!(
                "{color}BADCHILD{reset}",
                color = DiffResult::foreground_color(255, 255, 0),
                reset = DiffResult::reset_color(),
            )
        } else {
            format!(
                "{color}GOOD{reset}",
                color = DiffResult::foreground_color(0, 255, 0),
                reset = DiffResult::reset_color(),
            )
        };
        println!(
            "{indentation}{status_text} {name}",
            indentation = "  ".repeat(indentation),
            status_text = status_text,
            name = self.name,
        );
        for child in self.children.iter() {
            child.print_indented(indentation + 1, original_document)?;
        }
        Ok(())
    }
}

fn artificial_diff_scope<'b, 's>(
    name: &'static str,
    children: Vec<DiffEntry<'b, 's>>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    Ok(DiffLayer {
        name: name.into(),
        children,
    }
    .into())
}

fn compare_ast_node<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: AstNode<'b, 's>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let compare_result: Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> = match rust {
        AstNode::Document(node) => _compare_document(source, emacs, node),
        AstNode::Heading(node) => compare_heading(source, emacs, node),
        AstNode::Section(node) => compare_section(source, emacs, node),
        AstNode::Paragraph(node) => compare_paragraph(source, emacs, node),
        AstNode::PlainList(node) => compare_plain_list(source, emacs, node),
        AstNode::PlainListItem(node) => compare_plain_list_item(source, emacs, node),
        AstNode::CenterBlock(node) => compare_center_block(source, emacs, node),
        AstNode::QuoteBlock(node) => compare_quote_block(source, emacs, node),
        AstNode::SpecialBlock(node) => compare_special_block(source, emacs, node),
        AstNode::DynamicBlock(node) => compare_dynamic_block(source, emacs, node),
        AstNode::FootnoteDefinition(node) => compare_footnote_definition(source, emacs, node),
        AstNode::Comment(node) => compare_comment(source, emacs, node),
        AstNode::Drawer(node) => compare_drawer(source, emacs, node),
        AstNode::PropertyDrawer(node) => compare_property_drawer(source, emacs, node),
        AstNode::NodeProperty(node) => compare_node_property(source, emacs, node),
        AstNode::Table(node) => compare_table(source, emacs, node),
        AstNode::TableRow(node) => compare_table_row(source, emacs, node),
        AstNode::VerseBlock(node) => compare_verse_block(source, emacs, node),
        AstNode::CommentBlock(node) => compare_comment_block(source, emacs, node),
        AstNode::ExampleBlock(node) => compare_example_block(source, emacs, node),
        AstNode::ExportBlock(node) => compare_export_block(source, emacs, node),
        AstNode::SrcBlock(node) => compare_src_block(source, emacs, node),
        AstNode::Clock(node) => compare_clock(source, emacs, node),
        AstNode::DiarySexp(node) => compare_diary_sexp(source, emacs, node),
        AstNode::Planning(node) => compare_planning(source, emacs, node),
        AstNode::FixedWidthArea(node) => compare_fixed_width_area(source, emacs, node),
        AstNode::HorizontalRule(node) => compare_horizontal_rule(source, emacs, node),
        AstNode::Keyword(node) => compare_keyword(source, emacs, node),
        AstNode::BabelCall(node) => compare_babel_call(source, emacs, node),
        AstNode::LatexEnvironment(node) => compare_latex_environment(source, emacs, node),
        AstNode::Bold(node) => compare_bold(source, emacs, node),
        AstNode::Italic(node) => compare_italic(source, emacs, node),
        AstNode::Underline(node) => compare_underline(source, emacs, node),
        AstNode::StrikeThrough(node) => compare_strike_through(source, emacs, node),
        AstNode::Code(node) => compare_code(source, emacs, node),
        AstNode::Verbatim(node) => compare_verbatim(source, emacs, node),
        AstNode::PlainText(node) => compare_plain_text(source, emacs, node),
        AstNode::RegularLink(node) => compare_regular_link(source, emacs, node),
        AstNode::RadioLink(node) => compare_radio_link(source, emacs, node),
        AstNode::RadioTarget(node) => compare_radio_target(source, emacs, node),
        AstNode::PlainLink(node) => compare_plain_link(source, emacs, node),
        AstNode::AngleLink(node) => compare_angle_link(source, emacs, node),
        AstNode::OrgMacro(node) => compare_org_macro(source, emacs, node),
        AstNode::Entity(node) => compare_entity(source, emacs, node),
        AstNode::LatexFragment(node) => compare_latex_fragment(source, emacs, node),
        AstNode::ExportSnippet(node) => compare_export_snippet(source, emacs, node),
        AstNode::FootnoteReference(node) => compare_footnote_reference(source, emacs, node),
        AstNode::Citation(node) => compare_citation(source, emacs, node),
        AstNode::CitationReference(node) => compare_citation_reference(source, emacs, node),
        AstNode::InlineBabelCall(node) => compare_inline_babel_call(source, emacs, node),
        AstNode::InlineSourceBlock(node) => compare_inline_source_block(source, emacs, node),
        AstNode::LineBreak(node) => compare_line_break(source, emacs, node),
        AstNode::Target(node) => compare_target(source, emacs, node),
        AstNode::StatisticsCookie(node) => compare_statistics_cookie(source, emacs, node),
        AstNode::Subscript(node) => compare_subscript(source, emacs, node),
        AstNode::Superscript(node) => compare_superscript(source, emacs, node),
        AstNode::TableCell(node) => compare_table_cell(source, emacs, node),
        AstNode::Timestamp(node) => compare_timestamp(source, emacs, node),
    };

    let mut compare_result = match compare_result.unwrap_or_else(|e| {
        DiffResult {
            status: DiffStatus::Bad,
            name: rust.get_elisp_fact().get_elisp_name(),
            message: Some(e.to_string()),
            children: Vec::new(),
            rust_source: rust.get_standard_properties().get_source(),
            emacs_token: emacs,
        }
        .into()
    }) {
        DiffEntry::DiffResult(inner) => inner,
        DiffEntry::DiffLayer(_) => {
            unreachable!("Layers are only interior to DiffResults of AST nodes.")
        }
    };

    // PlainText is a special case because upstream Org-Mode uses relative values for the bounds in plaintext rather than absolute so the below checks do not account for that.
    if let AstNode::PlainText(_) = rust {
    } else {
        match compare_standard_properties(source, emacs, &rust) {
            Err(err) => {
                compare_result.status = DiffStatus::Bad;
                compare_result.message = Some(err.to_string())
            }
            Ok(_) => {}
        }
    }

    Ok(compare_result.into())
}

pub fn compare_document<'b, 's>(
    emacs: &'b Token<'s>,
    rust: &'b Document<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    compare_ast_node(rust.source, emacs, rust.into())
}

fn _compare_document<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Document<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare :path
    // :path is a quoted string to the absolute path of the document.
    let document_path = get_property_quoted_string(emacs, ":path")?;
    let rust_document_path = rust.path.as_ref().map(|p| p.to_str()).flatten();
    match (
        document_path.as_ref().map(|s| s.as_str()),
        rust_document_path,
    ) {
        (None, None) => {}
        (None, Some(_)) | (Some(_), None) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Path mismatch (emacs != rust) {:?} != {:?}",
                document_path, rust_document_path
            ));
        }
        (Some(e), Some(r)) if e != r => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Path mismatch (emacs != rust) {:?} != {:?}",
                document_path, rust_document_path
            ));
        }
        (Some(_), Some(_)) => {}
    };

    // Compare category
    // :CATEGORY is specified either from "#+CATEGORY:" or it is the file name without the ".org" extension.
    let category = get_property_quoted_string(emacs, ":CATEGORY")?;
    match (category.as_ref(), rust.category.as_ref()) {
        (None, None) => {}
        (None, Some(_)) | (Some(_), None) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Category mismatch (emacs != rust) {:?} != {:?}",
                category, rust.category
            ));
        }
        (Some(e), Some(r)) if e != r => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Category mismatch (emacs != rust) {:?} != {:?}",
                category, rust.category
            ));
        }
        (Some(_), Some(_)) => {}
    };

    // Skipping "org-data" and its properties
    for (i, token) in children.iter().skip(2).enumerate() {
        let section_or_headline = token.as_list()?;
        let first_cell = section_or_headline
            .first()
            .ok_or("Should have at least one child.")?
            .as_atom()?;
        if first_cell == "section" {
            if i != 0 {
                return Err("Section cannot be after the first child of document.".into());
            }
            child_status.push(compare_ast_node(
                rust.source,
                token,
                rust.zeroth_section
                    .as_ref()
                    .ok_or("No corresponding zeroth-section")?
                    .into(),
            )?);
        } else if first_cell == "headline" {
            let corresponding_heading = rust
                .children
                .iter()
                .nth(i - rust.zeroth_section.as_ref().map(|_| 1).unwrap_or(0))
                .ok_or("Should have a corresponding heading.")?;
            child_status.push(compare_ast_node(
                rust.source,
                token,
                corresponding_heading.into(),
            )?);
        } else {
            return Err(format!(
                "Document should only contain sections and headlines, found: {}",
                first_cell
            )
            .into());
        }
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_section<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Section<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let this_status = DiffStatus::Good;
    let mut child_status = Vec::new();
    let message = None;

    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_heading<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Heading<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare level
    let level = get_property(emacs, ":level")?
        .ok_or("Level should not be nil")?
        .as_atom()?;
    if rust.level.to_string() != level {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Headline level do not match (emacs != rust): {} != {}",
            level, rust.level
        ))
    }

    // Compare tags
    let emacs_tags = get_tags_from_heading(emacs)?;
    let emacs_tags: HashSet<_> = emacs_tags.iter().map(|val| val.as_str()).collect();
    let rust_tags: HashSet<&str> = rust.tags.iter().map(|val| *val).collect();
    let difference: Vec<&str> = emacs_tags
        .symmetric_difference(&rust_tags)
        .map(|val| *val)
        .collect();
    if !difference.is_empty() {
        this_status = DiffStatus::Bad;
        message = Some(format!("Mismatched tags: {}", difference.join(", ")));
    }

    // Compare todo-keyword
    let todo_keyword = get_property(emacs, ":todo-keyword")?
        .map(Token::as_atom)
        .map_or(Ok(None), |r| r.map(Some))?
        .unwrap_or("nil");
    match (todo_keyword, &rust.todo_keyword, unquote(todo_keyword)) {
        ("nil", None, _) => {}
        (_, Some((_rust_todo_type, rust_todo)), Ok(emacs_todo)) if emacs_todo == *rust_todo => {}
        (emacs_todo, rust_todo, _) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "(emacs != rust) {:?} != {:?}",
                emacs_todo, rust_todo
            ));
        }
    };
    // Compare todo-type
    let todo_type = get_property(emacs, ":todo-type")?
        .map(Token::as_atom)
        .map_or(Ok(None), |r| r.map(Some))?
        .unwrap_or("nil");
    // todo-type is an unquoted string either todo, done, or nil
    match (todo_type, &rust.todo_keyword) {
        ("nil", None) => {}
        ("todo", Some((TodoKeywordType::Todo, _))) => {}
        ("done", Some((TodoKeywordType::Done, _))) => {}
        (emacs_todo, rust_todo) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "(emacs != rust) {:?} != {:?}",
                emacs_todo, rust_todo
            ));
        }
    };

    // Compare title
    let title = get_property(emacs, ":title")?;
    match (title, rust.title.len()) {
        (None, 0) => {}
        (None, _) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Titles do not match (emacs != rust): {:?} != {:?}",
                title, rust.title
            ))
        }
        (Some(title), _) => {
            let title_status = title
                .as_list()?
                .iter()
                .zip(rust.title.iter())
                .map(|(emacs_child, rust_child)| {
                    compare_ast_node(source, emacs_child, rust_child.into())
                })
                .collect::<Result<Vec<_>, _>>()?;
            child_status.push(artificial_diff_scope("title", title_status)?);
        }
    };

    // Compare priority
    let priority = get_property(emacs, ":priority")?;
    match (priority, rust.priority_cookie) {
        (None, None) => {}
        (None, Some(_)) | (Some(_), None) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Priority cookie mismatch (emacs != rust) {:?} != {:?}",
                priority, rust.priority_cookie
            ));
        }
        (Some(emacs_priority_cookie), Some(rust_priority_cookie)) => {
            let emacs_priority_cookie =
                emacs_priority_cookie.as_atom()?.parse::<PriorityCookie>()?;
            if emacs_priority_cookie != rust_priority_cookie {
                this_status = DiffStatus::Bad;
                message = Some(format!(
                    "Priority cookie mismatch (emacs != rust) {:?} != {:?}",
                    emacs_priority_cookie, rust_priority_cookie
                ));
            }
        }
    }

    // Compare archived
    let archived = get_property(emacs, ":archivedp")?;
    match (archived, rust.is_archived) {
        (None, true) | (Some(_), false) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "archived mismatch (emacs != rust) {:?} != {:?}",
                archived, rust.is_archived
            ));
        }
        (None, false) | (Some(_), true) => {}
    }

    // Compare commented
    let commented = get_property(emacs, ":commentedp")?;
    match (commented, rust.is_comment) {
        (None, true) | (Some(_), false) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "commented mismatch (emacs != rust) {:?} != {:?}",
                commented, rust.is_comment
            ));
        }
        (None, false) | (Some(_), true) => {}
    }

    // Compare raw-value
    let raw_value = get_property_quoted_string(emacs, ":raw-value")?
        .ok_or("Headlines should have :raw-value.")?;
    let rust_raw_value = rust.get_raw_value();
    if raw_value != rust_raw_value {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "raw-value mismatch (emacs != rust) {:?} != {:?}",
            raw_value, rust_raw_value
        ));
    }

    // Compare footnote-section-p
    let footnote_section = get_property_boolean(emacs, ":footnote-section-p")?;
    if footnote_section != rust.is_footnote_section {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "footnote section mismatch (emacs != rust) {:?} != {:?}",
            footnote_section, rust.is_footnote_section
        ));
    }

    // Compare scheduled
    let scheduled = get_property(emacs, ":scheduled")?;
    match (scheduled, &rust.scheduled) {
        (None, None) => {}
        (None, Some(_)) | (Some(_), None) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Scheduled mismatch (emacs != rust) {:?} != {:?}",
                scheduled, rust.scheduled
            ));
        }
        (Some(emacs_child), Some(rust_child)) => {
            let result = compare_ast_node(source, emacs_child, rust_child.into())?;
            child_status.push(artificial_diff_scope("scheduled", vec![result])?);
        }
    }

    // Compare deadline
    let deadline = get_property(emacs, ":deadline")?;
    match (deadline, &rust.deadline) {
        (None, None) => {}
        (None, Some(_)) | (Some(_), None) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Deadline mismatch (emacs != rust) {:?} != {:?}",
                deadline, rust.deadline
            ));
        }
        (Some(emacs_child), Some(rust_child)) => {
            let result = compare_ast_node(source, emacs_child, rust_child.into())?;
            child_status.push(artificial_diff_scope("deadline", vec![result])?);
        }
    }

    // Compare closed
    let closed = get_property(emacs, ":closed")?;
    match (closed, &rust.closed) {
        (None, None) => {}
        (None, Some(_)) | (Some(_), None) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Closed mismatch (emacs != rust) {:?} != {:?}",
                closed, rust.closed
            ));
        }
        (Some(emacs_child), Some(rust_child)) => {
            let result = compare_ast_node(source, emacs_child, rust_child.into())?;
            child_status.push(artificial_diff_scope("closed", vec![result])?);
        }
    }

    // TODO: Compare :pre-blank

    // Compare section
    let section_status = children
        .iter()
        .skip(2)
        .zip(rust.children.iter())
        .map(|(emacs_child, rust_child)| match rust_child {
            DocumentElement::Heading(rust_heading) => {
                compare_ast_node(source, emacs_child, rust_heading.into())
            }
            DocumentElement::Section(rust_section) => {
                compare_ast_node(source, emacs_child, rust_section.into())
            }
        })
        .collect::<Result<Vec<_>, _>>()?;
    child_status.push(artificial_diff_scope("section", section_status)?);

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn get_tags_from_heading<'b, 's>(
    emacs: &'b Token<'s>,
) -> Result<HashSet<String>, Box<dyn std::error::Error>> {
    let tags = match get_property(emacs, ":tags")? {
        Some(prop) => prop,
        None => return Ok(HashSet::new()),
    };

    match tags.as_atom() {
        Ok(val) => panic!("Unexpected value for tags: {:?}", val),
        Err(_) => {}
    };
    let tags = {
        let tags = tags.as_list()?;
        let strings = tags
            .iter()
            .map(Token::as_atom)
            .collect::<Result<Vec<&str>, _>>()?;
        strings
            .into_iter()
            .map(unquote)
            .collect::<Result<HashSet<String>, _>>()?
    };
    Ok(tags)
}

fn compare_paragraph<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Paragraph<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let this_status = DiffStatus::Good;
    let message = None;

    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_plain_list<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b PlainList<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare type
    // :type is an unquoted atom of either descriptive, ordered, or unordered
    let list_type = get_property_unquoted_atom(emacs, ":type")?;
    match (list_type, &rust.list_type) {
        (None, _) => panic!("Emacs returned a list with no type."),
        (Some("unordered"), PlainListType::Unordered) => {}
        (Some("ordered"), PlainListType::Ordered) => {}
        (Some("descriptive"), PlainListType::Descriptive) => {}
        _ => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "List type mismatch (emacs != rust) {:?} != {:?}",
                list_type, rust.list_type
            ));
        }
    }

    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_plain_list_item<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b PlainListItem<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare tag
    let tag = get_property(emacs, ":tag")?;
    match (tag, rust.tag.is_empty()) {
        (None, true) => {}
        (None, false) | (Some(_), true) => {
            this_status = DiffStatus::Bad;
            message = Some("Mismatched tags".to_owned());
        }
        (Some(tag), false) => {
            let tag_status = tag
                .as_list()?
                .iter()
                .zip(rust.tag.iter())
                .map(|(emacs_child, rust_child)| {
                    compare_ast_node(source, emacs_child, rust_child.into())
                })
                .collect::<Result<Vec<_>, _>>()?;
            child_status.push(artificial_diff_scope("tag", tag_status)?);
        }
    };

    // Compare contents
    let contents_status = children
        .iter()
        .skip(2)
        .zip(rust.children.iter())
        .map(|(emacs_child, rust_child)| compare_ast_node(source, emacs_child, rust_child.into()))
        .collect::<Result<Vec<_>, _>>()?;
    child_status.push(artificial_diff_scope("contents", contents_status)?);

    // Compare bullet
    let bullet = get_property_quoted_string(emacs, ":bullet")?
        .ok_or("Plain list items must have a :bullet.")?;
    if bullet != rust.bullet {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Bullet mismatch (emacs != rust) {:?} != {:?}",
            bullet, rust.bullet
        ));
    }

    // Compare counter
    let counter = get_property_unquoted_atom(emacs, ":counter")?;
    let counter: Option<PlainListItemCounter> = counter
        .map(|val| val.parse())
        .map_or(Ok(None), |r| r.map(Some))?;
    match (counter, rust.counter) {
        (None, None) => {}
        (None, Some(_)) | (Some(_), None) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Counter mismatch (emacs != rust) {:?} != {:?}",
                counter, rust.counter
            ));
        }
        (Some(e), Some(r)) if e != r => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Counter mismatch (emacs != rust) {:?} != {:?}",
                counter, rust.counter
            ));
        }
        (Some(_), Some(_)) => {}
    };

    // Compare checkbox
    let checkbox = get_property(emacs, ":checkbox")?
        .map(Token::as_atom)
        .map_or(Ok(None), |r| r.map(Some))?
        .unwrap_or("nil");
    match (checkbox, &rust.checkbox) {
        ("nil", None) => {}
        ("off", Some((CheckboxType::Off, _))) => {}
        ("trans", Some((CheckboxType::Trans, _))) => {}
        ("on", Some((CheckboxType::On, _))) => {}
        _ => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Checkbox mismatch (emacs != rust) {:?} != {:?}",
                checkbox, rust.checkbox
            ));
        }
    };

    // Compare pre-blank
    // :pre-blank appears to count the line breaks between "::" and the contents in a descriptive list. Oddly enough it does not count the spaces so I'm not quite sure what the value is.
    let pre_blank = get_property_unquoted_atom(emacs, ":pre-blank")?;
    let pre_blank: Option<PlainListItemPreBlank> = pre_blank
        .map(|val| val.parse())
        .map_or(Ok(None), |r| r.map(Some))?;
    if pre_blank.unwrap_or(0) != rust.pre_blank {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Pre-blank mismatch (emacs != rust) {:?} != {:?}",
            pre_blank, rust.pre_blank
        ));
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_center_block<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b CenterBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let this_status = DiffStatus::Good;
    let message = None;

    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_quote_block<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b QuoteBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let this_status = DiffStatus::Good;
    let message = None;

    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_special_block<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b SpecialBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare type
    let special_block_type =
        get_property_quoted_string(emacs, ":type")?.ok_or("Special blocks should have a name.")?;
    if special_block_type != rust.name {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Name mismatch (emacs != rust) {:?} != {:?}",
            special_block_type, rust.name
        ));
    }

    // Compare parameters
    let parameters = get_property_quoted_string(emacs, ":parameters")?;
    match (parameters.as_ref(), rust.parameters) {
        (None, None) => {}
        (Some(emacs_parameters), Some(rust_parameters)) if emacs_parameters == rust_parameters => {}
        _ => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Parameters mismatch (emacs != rust) {:?} != {:?}",
                parameters, rust.parameters
            ));
        }
    }

    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_dynamic_block<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b DynamicBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare block-name
    let block_name = get_property_quoted_string(emacs, ":block-name")?
        .ok_or("Dynamic blocks should have a name.")?;
    if block_name != rust.name {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Name mismatch (emacs != rust) {:?} != {:?}",
            block_name, rust.name
        ));
    }

    // Compare arguments
    let parameters = get_property_quoted_string(emacs, ":arguments")?;
    match (parameters.as_ref(), rust.parameters) {
        (None, None) => {}
        (Some(emacs_parameters), Some(rust_parameters)) if emacs_parameters == rust_parameters => {}
        _ => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Parameters mismatch (emacs != rust) {:?} != {:?}",
                parameters, rust.parameters
            ));
        }
    }

    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_footnote_definition<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b FootnoteDefinition<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;
    // TODO: Compare :pre-blank

    // Compare label
    let label = get_property_quoted_string(emacs, ":label")?
        .ok_or("Footnote definitions should have a name.")?;
    if label != rust.label {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Label mismatch (emacs != rust) {:?} != {:?}",
            label, rust.label
        ));
    }

    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_comment<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Comment<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare value
    let value =
        get_property_quoted_string(emacs, ":value")?.ok_or("Comments should have a value.")?;
    let rust_value = rust.get_value();
    if value != rust_value {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Value mismatch (emacs != rust) {:?} != {:?}",
            value, rust_value
        ));
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_drawer<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Drawer<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare drawer-name
    let name =
        get_property_quoted_string(emacs, ":drawer-name")?.ok_or("Drawers should have a name.")?;
    if name != rust.name {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Name mismatch (emacs != rust) {:?} != {:?}",
            name, rust.name
        ));
    }

    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_property_drawer<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b PropertyDrawer<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let this_status = DiffStatus::Good;
    let message = None;

    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_node_property<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b NodeProperty<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare key
    let key =
        get_property_quoted_string(emacs, ":key")?.ok_or("Node properties should have a key.")?;
    if key != rust.name {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Key mismatch (emacs != rust) {:?} != {:?}",
            key, rust.name
        ));
    }

    // Compare value
    let value = get_property_quoted_string(emacs, ":value")?;
    match (value.as_ref(), rust.value) {
        (None, None) => {}
        (Some(emacs_value), Some(rust_value)) if emacs_value == rust_value => {}
        _ => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Value mismatch (emacs != rust) {:?} != {:?}",
                value, rust.value
            ));
        }
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_table<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Table<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare formulas
    //
    // :tblfm is either nil or a list () filled with quoted strings containing the value for any tblfm keywords at the end of the table.
    let emacs_formulas = get_property(emacs, ":tblfm")?;
    if let Some(emacs_formulas) = emacs_formulas {
        let emacs_formulas = emacs_formulas.as_list()?;
        if emacs_formulas.len() != rust.formulas.len() {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Formulas do not match (emacs != rust): {:?} != {:?}",
                emacs_formulas, rust.formulas
            ))
        } else {
            let atoms = emacs_formulas
                .into_iter()
                .map(Token::as_atom)
                .collect::<Result<Vec<_>, _>>()?;
            let unquoted = atoms
                .into_iter()
                .map(unquote)
                .collect::<Result<BTreeSet<_>, _>>()?;
            for kw in &rust.formulas {
                if !unquoted.contains(kw.value) {
                    this_status = DiffStatus::Bad;
                    message = Some(format!("Could not find formula in emacs: {}", kw.value))
                }
            }
        }
    } else {
        if !rust.formulas.is_empty() {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Formulas do not match (emacs != rust): {:?} != {:?}",
                emacs_formulas, rust.formulas
            ))
        }
    }

    // Compare type
    let table_type = get_property_unquoted_atom(emacs, ":type")?.expect("Table should have a type");
    if table_type != "org" {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Table type mismatch (emacs != rust) {:?} != {:?}",
            table_type, "org"
        ));
    }

    // Compare value
    let value = get_property(emacs, ":value")?;
    if value.is_some() {
        // I don't know what :value is for, but it seems to always be nil. This is here to alert me to value being non-nil so I can investigate.
        this_status = DiffStatus::Bad;
        message = Some(format!("Non-nil value {:?}", value))
    }

    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_table_row<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b TableRow<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let mut child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare type
    let row_type = get_property_unquoted_atom(emacs, ":type")?;
    let rust_row_type = rust.get_type();
    match (row_type, &rust_row_type) {
        (Some("standard"), TableRowType::Standard) => {}
        (Some("rule"), TableRowType::Rule) => {}
        _ => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Type mismatch (emacs != rust) {:?} != {:?}",
                row_type, rust_row_type
            ));
        }
    }

    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_table_cell<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b TableCell<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let child_status = Vec::new();
    let this_status = DiffStatus::Good;
    let message = None;

    for (_emacs_child, _rust_child) in children.iter().skip(2).zip(rust.children.iter()) {}

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_verse_block<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b VerseBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let children = emacs.as_list()?;
    let child_status = Vec::new();
    let this_status = DiffStatus::Good;
    let message = None;

    for (_emacs_child, _rust_child) in children.iter().skip(2).zip(rust.children.iter()) {}

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_comment_block<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b CommentBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare value
    let contents = get_property_quoted_string(emacs, ":value")?.unwrap_or(String::new());
    if contents != rust.contents {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Value mismatch (emacs != rust) {:?} != {:?}",
            contents, rust.contents
        ));
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_example_block<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b ExampleBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // TODO: Compare :number-lines :preserve-indent :retain-labels :use-labels :label-fmt

    // Compare value
    let contents = get_property_quoted_string(emacs, ":value")?.unwrap_or(String::new());
    if contents != rust.contents {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Value mismatch (emacs != rust) {:?} != {:?}",
            contents, rust.contents
        ));
    }

    // Compare switches
    let switches = get_property_quoted_string(emacs, ":switches")?;
    if switches.as_ref().map(String::as_str) != rust.switches {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Switches mismatch (emacs != rust) {:?} != {:?}",
            switches, rust.switches
        ));
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_export_block<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b ExportBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :type :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_src_block<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b SrcBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :language :switches :parameters :number-lines :preserve-indent :retain-labels :use-labels :label-fmt :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_clock<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Clock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :status :value :duration

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_diary_sexp<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b DiarySexp<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_planning<'b, 's>(
    source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Planning<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let mut child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare scheduled
    let scheduled = get_property(emacs, ":scheduled")?;
    match (scheduled, &rust.scheduled) {
        (None, None) => {}
        (None, Some(_)) | (Some(_), None) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Scheduled mismatch (emacs != rust) {:?} != {:?}",
                scheduled, rust.scheduled
            ));
        }
        (Some(emacs_child), Some(rust_child)) => {
            let result = compare_ast_node(source, emacs_child, rust_child.into())?;
            child_status.push(artificial_diff_scope("scheduled", vec![result])?);
        }
    }

    // Compare deadline
    let deadline = get_property(emacs, ":deadline")?;
    match (deadline, &rust.deadline) {
        (None, None) => {}
        (None, Some(_)) | (Some(_), None) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Deadline mismatch (emacs != rust) {:?} != {:?}",
                deadline, rust.deadline
            ));
        }
        (Some(emacs_child), Some(rust_child)) => {
            let result = compare_ast_node(source, emacs_child, rust_child.into())?;
            child_status.push(artificial_diff_scope("deadline", vec![result])?);
        }
    }

    // Compare closed
    let closed = get_property(emacs, ":closed")?;
    match (closed, &rust.closed) {
        (None, None) => {}
        (None, Some(_)) | (Some(_), None) => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Closed mismatch (emacs != rust) {:?} != {:?}",
                closed, rust.closed
            ));
        }
        (Some(emacs_child), Some(rust_child)) => {
            let result = compare_ast_node(source, emacs_child, rust_child.into())?;
            child_status.push(artificial_diff_scope("closed", vec![result])?);
        }
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_fixed_width_area<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b FixedWidthArea<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let child_status = Vec::new();
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_horizontal_rule<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b HorizontalRule<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let child_status = Vec::new();
    let this_status = DiffStatus::Good;
    let message = None;

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_keyword<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Keyword<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    let key = unquote(
        get_property(emacs, ":key")?
            .ok_or("Emacs keywords should have a :key")?
            .as_atom()?,
    )?;
    if key != rust.key.to_uppercase() {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Mismatchs keyword keys (emacs != rust) {:?} != {:?}",
            key, rust.key
        ))
    }
    let value = unquote(
        get_property(emacs, ":value")?
            .ok_or("Emacs keywords should have a :value")?
            .as_atom()?,
    )?;
    if value != rust.value {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Mismatchs keyword values (emacs != rust) {:?} != {:?}",
            value, rust.value
        ))
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_babel_call<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b BabelCall<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let child_status = Vec::new();
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // TODO: Compare :call :inside-header :arguments :end-header
    let value = unquote(
        get_property(emacs, ":value")?
            .ok_or("Emacs keywords should have a :value")?
            .as_atom()?,
    )?;
    if value != rust.value {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Mismatchs keyword values (emacs != rust) {:?} != {:?}",
            value, rust.value
        ))
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_latex_environment<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b LatexEnvironment<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let child_status = Vec::new();
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: child_status,
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_plain_text<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b PlainText<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let mut this_status = DiffStatus::Good;
    let mut message = None;
    let rust_source = rust.get_source();
    let text = emacs.as_text()?;
    let start_ind: usize = text
        .properties
        .get(0)
        .expect("Should have start index.")
        .as_atom()?
        .parse()?;
    let end_ind: usize = text
        .properties
        .get(1)
        .expect("Should have end index.")
        .as_atom()?
        .parse()?;
    let emacs_text_length = end_ind - start_ind;
    if rust_source.chars().count() != emacs_text_length {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "(emacs len != rust len) {:?} != {:?}",
            emacs_text_length,
            rust_source.len()
        ));
    }
    let unquoted_text = unquote(text.text)?;
    if unquoted_text != rust_source {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "(emacs != rust) {:?} != {:?}",
            unquoted_text, rust_source
        ));
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source,
        emacs_token: emacs,
    }
    .into())
}

fn compare_bold<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Bold<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_italic<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Italic<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_underline<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Underline<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_verbatim<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Verbatim<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_code<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Code<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_strike_through<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b StrikeThrough<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_regular_link<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b RegularLink<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :type :path :format :raw-link :application :search-option

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_radio_link<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b RadioLink<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :type :path :format :raw-link :application :search-option

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_radio_target<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b RadioTarget<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_plain_link<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b PlainLink<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :type :path :format :raw-link :application :search-option

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_angle_link<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b AngleLink<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :type :path :format :raw-link :application :search-option

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_org_macro<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b OrgMacro<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :key :value :args

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_entity<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Entity<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :name :latex :latex-math-p :html :ascii :latin1 :utf-8 :use-brackets-p

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_latex_fragment<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b LatexFragment<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_export_snippet<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b ExportSnippet<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :back-end :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_footnote_reference<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b FootnoteReference<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :label :type

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_citation<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Citation<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :style :prefix :suffix

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_citation_reference<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b CitationReference<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :key :prefix :suffix

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_inline_babel_call<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b InlineBabelCall<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :call :inside-header :arguments :end-header :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_inline_source_block<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b InlineSourceBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :language :value :parameters

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_line_break<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b LineBreak<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_target<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Target<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_statistics_cookie<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b StatisticsCookie<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :value

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_subscript<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Subscript<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :use-brackets-p

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_superscript<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Superscript<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
    let this_status = DiffStatus::Good;
    let message = None;

    // TODO: Compare :use-brackets-p

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}

fn compare_timestamp<'b, 's>(
    _source: &'s str,
    emacs: &'b Token<'s>,
    rust: &'b Timestamp<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error + 's>> {
    let mut this_status = DiffStatus::Good;
    let mut message = None;

    // Compare type
    let timestamp_type = get_property_unquoted_atom(emacs, ":type")?;
    match (timestamp_type, &rust.timestamp_type) {
        (Some("diary"), TimestampType::Diary) => {}
        (Some("active"), TimestampType::Active) => {}
        (Some("inactive"), TimestampType::Inactive) => {}
        (Some("active-range"), TimestampType::ActiveRange) => {}
        (Some("inactive-range"), TimestampType::InactiveRange) => {}
        _ => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Timestamp type mismatch (emacs != rust) {:?} != {:?}",
                timestamp_type, rust.timestamp_type
            ));
        }
    }

    // Compare range-type
    let range_type = get_property_unquoted_atom(emacs, ":range-type")?;
    match (range_type, &rust.range_type) {
        (Some("daterange"), TimestampRangeType::DateRange) => {}
        (Some("timerange"), TimestampRangeType::TimeRange) => {}
        (None, TimestampRangeType::None) => {}
        _ => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Range type mismatch (emacs != rust) {:?} != {:?}",
                range_type, rust.range_type
            ));
        }
    }

    // Compare raw-value
    let raw_value = get_property_quoted_string(emacs, ":raw-value")?
        .ok_or("Timestamps should have a :raw-value.")?;
    if raw_value != rust.get_raw_value() {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Raw value mismatch (emacs != rust) {:?} != {:?}",
            raw_value,
            rust.get_raw_value()
        ));
    }

    // Compare start
    let year_start: Option<YearInner> = get_property_numeric(emacs, ":year-start")?;
    let month_start: Option<MonthInner> = get_property_numeric(emacs, ":month-start")?;
    let day_of_month_start: Option<DayOfMonthInner> = get_property_numeric(emacs, ":day-start")?;
    let rust_year_start = rust.start.as_ref().map(Date::get_year).map(Year::get_value);
    let rust_month_start = rust
        .start
        .as_ref()
        .map(Date::get_month)
        .map(Month::get_value);
    let rust_day_of_month_start = rust
        .start
        .as_ref()
        .map(Date::get_day_of_month)
        .map(DayOfMonth::get_value);
    if year_start != rust_year_start {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "year start mismatch (emacs != rust) {:?} != {:?}",
            year_start, rust_year_start
        ));
    }
    if month_start != rust_month_start {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "month start mismatch (emacs != rust) {:?} != {:?}",
            month_start, rust_month_start
        ));
    }
    if day_of_month_start != rust_day_of_month_start {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "day of month start mismatch (emacs != rust) {:?} != {:?}",
            day_of_month_start, rust_day_of_month_start
        ));
    }

    // Compare end
    let year_end: Option<YearInner> = get_property_numeric(emacs, ":year-end")?;
    let month_end: Option<MonthInner> = get_property_numeric(emacs, ":month-end")?;
    let day_of_month_end: Option<DayOfMonthInner> = get_property_numeric(emacs, ":day-end")?;
    let rust_year_end = rust.end.as_ref().map(Date::get_year).map(Year::get_value);
    let rust_month_end = rust.end.as_ref().map(Date::get_month).map(Month::get_value);
    let rust_day_of_month_end = rust
        .end
        .as_ref()
        .map(Date::get_day_of_month)
        .map(DayOfMonth::get_value);
    if year_end != rust_year_end {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "year end mismatch (emacs != rust) {:?} != {:?}",
            year_end, rust_year_end
        ));
    }
    if month_end != rust_month_end {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "month end mismatch (emacs != rust) {:?} != {:?}",
            month_end, rust_month_end
        ));
    }
    if day_of_month_end != rust_day_of_month_end {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "day of month end mismatch (emacs != rust) {:?} != {:?}",
            day_of_month_end, rust_day_of_month_end
        ));
    }

    // Compare time start
    let hour_start: Option<HourInner> = get_property_numeric(emacs, ":hour-start")?;
    let minute_start: Option<MinuteInner> = get_property_numeric(emacs, ":minute-start")?;
    let rust_hour_start = rust
        .start_time
        .as_ref()
        .map(Time::get_hour)
        .map(Hour::get_value);
    let rust_minute_start = rust
        .start_time
        .as_ref()
        .map(Time::get_minute)
        .map(Minute::get_value);
    if hour_start != rust_hour_start {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "hour start mismatch (emacs != rust) {:?} != {:?}",
            hour_start, rust_hour_start
        ));
    }
    if minute_start != rust_minute_start {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "minute start mismatch (emacs != rust) {:?} != {:?}",
            minute_start, rust_minute_start
        ));
    }

    // Compare time end
    let hour_end: Option<HourInner> = get_property_numeric(emacs, ":hour-end")?;
    let minute_end: Option<MinuteInner> = get_property_numeric(emacs, ":minute-end")?;
    let rust_hour_end = rust
        .end_time
        .as_ref()
        .map(Time::get_hour)
        .map(Hour::get_value);
    let rust_minute_end = rust
        .end_time
        .as_ref()
        .map(Time::get_minute)
        .map(Minute::get_value);
    if hour_end != rust_hour_end {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "hour end mismatch (emacs != rust) {:?} != {:?}",
            hour_end, rust_hour_end
        ));
    }
    if minute_end != rust_minute_end {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "minute end mismatch (emacs != rust) {:?} != {:?}",
            minute_end, rust_minute_end
        ));
    }

    // Compare repeater
    let repeater_type = get_property_unquoted_atom(emacs, ":repeater-type")?;
    let repeater_value: Option<RepeaterWarningDelayValueType> =
        get_property_numeric(emacs, ":repeater-value")?;
    let repeater_unit = get_property_unquoted_atom(emacs, ":repeater-unit")?;
    let rust_repeater_type = rust
        .repeater
        .as_ref()
        .map(|repeater| &repeater.repeater_type);
    let rust_repeater_value = rust.repeater.as_ref().map(|repeater| repeater.value);
    let rust_repeater_unit = rust.repeater.as_ref().map(|repeater| &repeater.unit);
    match (repeater_type, rust_repeater_type) {
        (Some("cumulate"), Some(RepeaterType::Cumulative)) => {}
        (Some("catch-up"), Some(RepeaterType::CatchUp)) => {}
        (Some("restart"), Some(RepeaterType::Restart)) => {}
        (None, None) => {}
        _ => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Repeater type mismatch (emacs != rust) {:?} != {:?}",
                repeater_type, rust_repeater_type
            ));
        }
    }
    if repeater_value != rust_repeater_value {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Repeater value mismatch (emacs != rust) {:?} != {:?}",
            repeater_value, rust_repeater_value
        ));
    }
    match (repeater_unit, rust_repeater_unit) {
        (Some("hour"), Some(TimeUnit::Hour)) => {}
        (Some("day"), Some(TimeUnit::Day)) => {}
        (Some("week"), Some(TimeUnit::Week)) => {}
        (Some("month"), Some(TimeUnit::Month)) => {}
        (Some("year"), Some(TimeUnit::Year)) => {}
        (None, None) => {}
        _ => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Repeater unit mismatch (emacs != rust) {:?} != {:?}",
                repeater_unit, rust_repeater_unit
            ));
        }
    }

    // Compare warning_delay
    let warning_delay_type = get_property_unquoted_atom(emacs, ":warning-type")?;
    let warning_delay_value: Option<RepeaterWarningDelayValueType> =
        get_property_numeric(emacs, ":warning-value")?;
    let warning_delay_unit = get_property_unquoted_atom(emacs, ":warning-unit")?;
    let rust_warning_delay_type = rust
        .warning_delay
        .as_ref()
        .map(|warning_delay| &warning_delay.warning_delay_type);
    let rust_warning_delay_value = rust
        .warning_delay
        .as_ref()
        .map(|warning_delay| warning_delay.value);
    let rust_warning_delay_unit = rust
        .warning_delay
        .as_ref()
        .map(|warning_delay| &warning_delay.unit);
    match (warning_delay_type, rust_warning_delay_type) {
        (Some("all"), Some(WarningDelayType::All)) => {}
        (Some("first"), Some(WarningDelayType::First)) => {}
        (None, None) => {}
        _ => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Warning delay type mismatch (emacs != rust) {:?} != {:?}",
                warning_delay_type, rust_warning_delay_type
            ));
        }
    }
    if warning_delay_value != rust_warning_delay_value {
        this_status = DiffStatus::Bad;
        message = Some(format!(
            "Warning delay value mismatch (emacs != rust) {:?} != {:?}",
            warning_delay_value, rust_warning_delay_value
        ));
    }
    match (warning_delay_unit, rust_warning_delay_unit) {
        (Some("hour"), Some(TimeUnit::Hour)) => {}
        (Some("day"), Some(TimeUnit::Day)) => {}
        (Some("week"), Some(TimeUnit::Week)) => {}
        (Some("month"), Some(TimeUnit::Month)) => {}
        (Some("year"), Some(TimeUnit::Year)) => {}
        (None, None) => {}
        _ => {
            this_status = DiffStatus::Bad;
            message = Some(format!(
                "Warning delay unit mismatch (emacs != rust) {:?} != {:?}",
                warning_delay_unit, rust_warning_delay_unit
            ));
        }
    }

    Ok(DiffResult {
        status: this_status,
        name: rust.get_elisp_name(),
        message,
        children: Vec::new(),
        rust_source: rust.get_source(),
        emacs_token: emacs,
    }
    .into())
}