use mnote_editor_core::{
    BlockType, CommandExecutor, DocumentBlock, DocumentModel, EditorCommand, VisibilitySnapshot,
};

#[test]
fn command_executor_replaces_inserts_and_deletes_blocks() {
    let mut document = DocumentModel::new(vec![
        DocumentBlock::new("block_a", BlockType::Paragraph).with_text("hello"),
        DocumentBlock::new("block_b", BlockType::Paragraph).with_text("world"),
    ]);

    CommandExecutor::apply(
        &mut document,
        EditorCommand::ReplaceBlock {
            block_id: "block_a".into(),
            text: "hello mnote".into(),
        },
    )
    .expect("replace should succeed");
    CommandExecutor::apply(
        &mut document,
        EditorCommand::InsertBlockAfter {
            after_block_id: Some("block_a".into()),
            block: DocumentBlock::new("block_h", BlockType::Heading)
                .with_heading_level(2)
                .with_text("section"),
        },
    )
    .expect("insert should succeed");
    CommandExecutor::apply(
        &mut document,
        EditorCommand::DeleteBlock {
            block_id: "block_b".into(),
        },
    )
    .expect("delete should succeed");

    assert_eq!(
        document
            .blocks()
            .iter()
            .map(|block| block.id.as_str())
            .collect::<Vec<_>>(),
        vec!["block_a", "block_h"]
    );
    assert_eq!(
        document.block("block_a").expect("block_a").content.text,
        "hello mnote"
    );
}

#[test]
fn command_executor_splits_merges_moves_and_reindents_blocks() {
    let mut document = DocumentModel::new(vec![
        DocumentBlock::new("block_h", BlockType::Heading)
            .with_heading_level(1)
            .with_text("root"),
        DocumentBlock::new("block_b", BlockType::Paragraph).with_text("AlphaBeta"),
        DocumentBlock::new("block_c", BlockType::Paragraph).with_text("Tail"),
    ]);

    CommandExecutor::apply(
        &mut document,
        EditorCommand::SplitBlock {
            block_id: "block_b".into(),
            offset: 5,
            new_block_id: "block_d".into(),
        },
    )
    .expect("split should succeed");
    assert_eq!(
        document.block("block_b").expect("block_b").content.text,
        "Alpha"
    );
    assert_eq!(
        document.block("block_d").expect("block_d").content.text,
        "Beta"
    );

    CommandExecutor::apply(
        &mut document,
        EditorCommand::MergeWithPrevious {
            block_id: "block_d".into(),
        },
    )
    .expect("merge should succeed");
    assert_eq!(
        document.block("block_b").expect("block_b").content.text,
        "AlphaBeta"
    );

    CommandExecutor::apply(
        &mut document,
        EditorCommand::MoveBlock {
            block_id: "block_c".into(),
            after_block_id: Some("block_h".into()),
        },
    )
    .expect("move should succeed");
    assert_eq!(
        document
            .blocks()
            .iter()
            .map(|block| block.id.as_str())
            .collect::<Vec<_>>(),
        vec!["block_h", "block_c", "block_b"]
    );

    CommandExecutor::apply(
        &mut document,
        EditorCommand::IndentBlock {
            block_id: "block_b".into(),
        },
    )
    .expect("indent should succeed");
    let indented = document.block("block_b").expect("block_b after indent");
    assert_eq!(indented.parent_id.as_deref(), Some("block_c"));
    assert_eq!(indented.indent, 1);

    CommandExecutor::apply(
        &mut document,
        EditorCommand::OutdentBlock {
            block_id: "block_b".into(),
        },
    )
    .expect("outdent should succeed");
    let outdented = document.block("block_b").expect("block_b after outdent");
    assert_eq!(outdented.parent_id, None);
    assert_eq!(outdented.indent, 0);
}

#[test]
fn command_executor_toggles_heading_collapse_visibility() {
    let mut document = DocumentModel::new(vec![
        DocumentBlock::new("heading_root", BlockType::Heading)
            .with_heading_level(1)
            .with_text("root"),
        DocumentBlock::new("child_para", BlockType::Paragraph)
            .with_parent("heading_root", 1)
            .with_text("hidden after collapse"),
        DocumentBlock::new("tail", BlockType::Paragraph).with_text("tail"),
    ]);

    CommandExecutor::apply(
        &mut document,
        EditorCommand::ToggleHeadingCollapse {
            block_id: "heading_root".into(),
        },
    )
    .expect("toggle collapse should succeed");

    assert!(document.block("heading_root").expect("heading").collapsed);
    let projection = VisibilitySnapshot::derive(&document);
    assert_eq!(
        projection.visible_block_ids,
        vec!["heading_root".to_string(), "tail".to_string()]
    );
}

#[test]
fn command_executor_changes_block_type() {
    let mut document =
        DocumentModel::new(vec![DocumentBlock::new("block_a", BlockType::Paragraph)]);
    CommandExecutor::apply(
        &mut document,
        EditorCommand::SetBlockType {
            block_id: "block_a".into(),
            block_type: BlockType::Todo,
        },
    )
    .expect("set block type should succeed");

    assert_eq!(
        document.block("block_a").expect("block_a").block_type,
        BlockType::Todo
    );
}
