use crate::error::{CoreError, CoreResult};
use crate::model::{BlockType, DocumentBlock, DocumentModel};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EditorCommand {
    SetBlockType {
        block_id: String,
        block_type: BlockType,
    },
    ReplaceBlock {
        block_id: String,
        text: String,
    },
    InsertBlockAfter {
        after_block_id: Option<String>,
        block: DocumentBlock,
    },
    DeleteBlock {
        block_id: String,
    },
    SplitBlock {
        block_id: String,
        offset: usize,
        new_block_id: String,
    },
    MergeWithPrevious {
        block_id: String,
    },
    MoveBlock {
        block_id: String,
        after_block_id: Option<String>,
    },
    IndentBlock {
        block_id: String,
    },
    OutdentBlock {
        block_id: String,
    },
    ToggleHeadingCollapse {
        block_id: String,
    },
}

#[derive(Debug, Default, Clone, Copy)]
pub struct CommandExecutor;

impl CommandExecutor {
    pub fn apply(document: &mut DocumentModel, command: EditorCommand) -> CoreResult<()> {
        match command {
            EditorCommand::SetBlockType {
                block_id,
                block_type,
            } => set_block_type(document, &block_id, block_type),
            EditorCommand::ReplaceBlock { block_id, text } => {
                replace_block(document, &block_id, text)
            }
            EditorCommand::InsertBlockAfter {
                after_block_id,
                block,
            } => insert_block_after(document, after_block_id.as_deref(), block),
            EditorCommand::DeleteBlock { block_id } => delete_block(document, &block_id),
            EditorCommand::SplitBlock {
                block_id,
                offset,
                new_block_id,
            } => split_block(document, &block_id, offset, new_block_id),
            EditorCommand::MergeWithPrevious { block_id } => {
                merge_with_previous(document, &block_id)
            }
            EditorCommand::MoveBlock {
                block_id,
                after_block_id,
            } => move_block(document, &block_id, after_block_id.as_deref()),
            EditorCommand::IndentBlock { block_id } => indent_block(document, &block_id),
            EditorCommand::OutdentBlock { block_id } => outdent_block(document, &block_id),
            EditorCommand::ToggleHeadingCollapse { block_id } => {
                toggle_heading_collapse(document, &block_id)
            }
        }
    }
}

fn set_block_type(
    document: &mut DocumentModel,
    block_id: &str,
    block_type: BlockType,
) -> CoreResult<()> {
    let block = document
        .block_mut(block_id)
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    block.block_type = block_type;
    Ok(())
}

fn replace_block(document: &mut DocumentModel, block_id: &str, text: String) -> CoreResult<()> {
    let block = document
        .block_mut(block_id)
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    block.content.text = text;
    Ok(())
}

fn insert_block_after(
    document: &mut DocumentModel,
    after_block_id: Option<&str>,
    block: DocumentBlock,
) -> CoreResult<()> {
    if document.contains_id(&block.id) {
        return Err(CoreError::DuplicateBlockId(block.id));
    }

    let insert_index = match after_block_id {
        Some(target_id) => {
            let range = document
                .subtree_range(target_id)
                .ok_or_else(|| CoreError::BlockNotFound(target_id.to_string()))?;
            *range.end() + 1
        }
        None => document.blocks().len(),
    };
    document.blocks_mut().insert(insert_index, block);
    Ok(())
}

fn delete_block(document: &mut DocumentModel, block_id: &str) -> CoreResult<()> {
    let range = document
        .subtree_range(block_id)
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    document.blocks_mut().drain(range);
    Ok(())
}

fn split_block(
    document: &mut DocumentModel,
    block_id: &str,
    offset: usize,
    new_block_id: String,
) -> CoreResult<()> {
    if document.contains_id(&new_block_id) {
        return Err(CoreError::DuplicateBlockId(new_block_id));
    }
    let index = document
        .index_of(block_id)
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    let range = document
        .subtree_range(block_id)
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    let block = document.blocks()[index].clone();
    let split_at = byte_index_for_char(&block.content.text, offset).ok_or_else(|| {
        CoreError::InvalidSplitOffset {
            block_id: block_id.to_string(),
            offset,
        }
    })?;
    let left = block.content.text[..split_at].to_string();
    let right = block.content.text[split_at..].to_string();

    {
        let current = document
            .block_mut(block_id)
            .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
        current.content.text = left;
    }

    let mut next_block = block;
    next_block.id = new_block_id;
    next_block.content.text = right;
    next_block.collapsed = false;
    document.blocks_mut().insert(*range.end() + 1, next_block);
    Ok(())
}

fn merge_with_previous(document: &mut DocumentModel, block_id: &str) -> CoreResult<()> {
    let index = document
        .index_of(block_id)
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    if index == 0 {
        return Err(CoreError::InvalidOperation("首块无法与上一块合并"));
    }
    let range = document
        .subtree_range(block_id)
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    if *range.start() != *range.end() {
        return Err(CoreError::InvalidOperation(
            "当前最小实现不支持带子树的 merge",
        ));
    }

    let previous_index = index - 1;
    let text_to_append = document.blocks()[index].content.text.clone();
    document.blocks_mut()[previous_index]
        .content
        .text
        .push_str(&text_to_append);
    document.blocks_mut().remove(index);
    Ok(())
}

fn move_block(
    document: &mut DocumentModel,
    block_id: &str,
    after_block_id: Option<&str>,
) -> CoreResult<()> {
    let range = document
        .subtree_range(block_id)
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    let start = *range.start();
    let end = *range.end();
    let moved_ids: Vec<String> = document.blocks()[start..=end]
        .iter()
        .map(|block| block.id.clone())
        .collect();
    if let Some(target_id) = after_block_id {
        if moved_ids.iter().any(|id| id == target_id) {
            return Err(CoreError::InvalidOperation("不能把块移动到自己的子树后面"));
        }
    }

    let moved_blocks: Vec<DocumentBlock> = document.blocks_mut().drain(start..=end).collect();
    let insert_index = match after_block_id {
        Some(target_id) => {
            let target_range = document
                .subtree_range(target_id)
                .ok_or_else(|| CoreError::BlockNotFound(target_id.to_string()))?;
            *target_range.end() + 1
        }
        None => 0,
    };
    document
        .blocks_mut()
        .splice(insert_index..insert_index, moved_blocks);
    Ok(())
}

fn indent_block(document: &mut DocumentModel, block_id: &str) -> CoreResult<()> {
    let index = document
        .index_of(block_id)
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    if index == 0 {
        return Err(CoreError::InvalidOperation("首块无法缩进"));
    }
    let previous_id = document.blocks()[index - 1].id.clone();
    let range = document
        .subtree_range(block_id)
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    for position in *range.start()..=*range.end() {
        document.blocks_mut()[position].indent += 1;
    }
    document.blocks_mut()[*range.start()].parent_id = Some(previous_id);
    Ok(())
}

fn outdent_block(document: &mut DocumentModel, block_id: &str) -> CoreResult<()> {
    let block = document
        .block(block_id)
        .cloned()
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    let parent_id = block
        .parent_id
        .clone()
        .ok_or(CoreError::InvalidOperation("当前块已在根层级"))?;
    let next_parent_id = document
        .block(&parent_id)
        .and_then(|parent| parent.parent_id.clone());
    let range = document
        .subtree_range(block_id)
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    for position in *range.start()..=*range.end() {
        let current_indent = document.blocks()[position].indent;
        if current_indent == 0 {
            return Err(CoreError::InvalidOperation("根层级块不能继续反缩进"));
        }
        document.blocks_mut()[position].indent = current_indent - 1;
    }
    document.blocks_mut()[*range.start()].parent_id = next_parent_id;
    Ok(())
}

fn toggle_heading_collapse(document: &mut DocumentModel, block_id: &str) -> CoreResult<()> {
    let block = document
        .block_mut(block_id)
        .ok_or_else(|| CoreError::BlockNotFound(block_id.to_string()))?;
    block.collapsed = !block.collapsed;
    Ok(())
}

fn byte_index_for_char(text: &str, offset: usize) -> Option<usize> {
    if offset == text.chars().count() {
        return Some(text.len());
    }
    text.char_indices().nth(offset).map(|(index, _)| index)
}
