use crate::error::EditorError;
use crate::model::document::Document;
use crate::model::inline::{
    TextSpan, next_char_boundary_utf16, prev_char_boundary_utf16, utf16_len, utf16_to_byte,
};
use crate::model::selection::Selection;
use crate::op::operation::Operation;

/// Compute the inverse of an operation BEFORE it is applied.
pub fn compute_inverse(
    doc: &Document,
    selection: &Option<Selection>,
    op: &Operation,
) -> Result<Vec<Operation>, EditorError> {
    match op {
        Operation::InsertTextAt {
            block_id,
            offset,
            text,
            ..
        } => Ok(vec![Operation::DeleteRange {
            block_id: block_id.clone(),
            start: *offset,
            end: offset + utf16_len(text),
        }]),

        Operation::DeleteRange {
            block_id,
            start,
            end,
        } => {
            let block = doc.require_block(block_id)?;

            let spans = extract_spans_in_range(&block.content, *start, *end);
            let mut inverse_ops = Vec::new();
            let mut offset = *start;

            for span in spans {
                inverse_ops.push(Operation::InsertTextAt {
                    block_id: block_id.clone(),
                    offset,
                    text: span.text.clone(),
                    marks: span.marks.clone(),
                });
                offset += span.len();
            }

            Ok(inverse_ops)
        }

        Operation::SplitBlock {
            block_id,
            new_block_id,
            ..
        } => Ok(vec![Operation::MergeBlock {
            target_id: block_id.clone(),
            source_id: new_block_id.clone(),
        }]),

        Operation::MergeBlock {
            target_id,
            source_id,
        } => {
            let target = doc.require_block(target_id)?;
            let source = doc.require_block(source_id)?;

            let split_offset = target.text_len();
            let source_type = source.ty.clone();
            let source_props = source.props.clone();

            let mut ops = vec![Operation::SplitBlock {
                block_id: target_id.clone(),
                offset: split_offset,
                new_block_id: source_id.clone(),
            }];

            // Restore the source block's type if it differs from target
            if source_type != target.ty || source_props != target.props {
                ops.push(Operation::SetBlockType {
                    block_id: source_id.clone(),
                    block_type: source_type,
                    props: source_props,
                });
            }

            Ok(ops)
        }

        Operation::SetBlockType { block_id, .. } => {
            let block = doc.require_block(block_id)?;
            Ok(vec![Operation::SetBlockType {
                block_id: block_id.clone(),
                block_type: block.ty.clone(),
                props: block.props.clone(),
            }])
        }

        Operation::SetBlockAlign { block_id, .. } => {
            let block = doc.require_block(block_id)?;
            Ok(vec![Operation::SetBlockAlign {
                block_id: block_id.clone(),
                align: block.alignment(),
            }])
        }

        Operation::ToggleMark {
            block_id,
            start,
            end,
            mark_type,
        } => Ok(vec![Operation::ToggleMark {
            block_id: block_id.clone(),
            start: *start,
            end: *end,
            mark_type: mark_type.clone(),
        }]),

        Operation::SetMark {
            block_id,
            start,
            end,
            ..
        }
        | Operation::ClearMark {
            block_id,
            start,
            end,
            ..
        } => {
            let block = doc.require_block(block_id)?;
            Ok(vec![Operation::RestoreRangeSpans {
                block_id: block_id.clone(),
                start: *start,
                end: *end,
                spans: extract_spans_in_range(&block.content, *start, *end),
            }])
        }

        Operation::RestoreRangeSpans {
            block_id,
            start,
            end,
            spans,
        } => {
            let block = doc.require_block(block_id)?;
            Ok(vec![Operation::RestoreRangeSpans {
                block_id: block_id.clone(),
                start: *start,
                end: *start + spans.iter().map(TextSpan::len).sum::<usize>(),
                spans: extract_spans_in_range(&block.content, *start, *end),
            }])
        }

        Operation::InsertBlock { block, .. } => Ok(vec![Operation::RemoveBlock {
            block_id: block.id.clone(),
        }]),

        Operation::RemoveBlock { block_id } => {
            let block = doc.require_block(block_id)?;
            let index = doc
                .block_root_index(block_id)
                .ok_or_else(|| EditorError::BlockNotFound(block_id.clone()))?;
            Ok(vec![Operation::InsertBlock {
                block: block.clone(),
                index,
            }])
        }

        Operation::MoveBlock { block_id, .. } => {
            let old_index = doc
                .block_root_index(block_id)
                .ok_or_else(|| EditorError::BlockNotFound(block_id.clone()))?;
            Ok(vec![Operation::MoveBlock {
                block_id: block_id.clone(),
                new_index: old_index,
            }])
        }

        Operation::SetSelection { .. } => Ok(vec![Operation::SetSelection {
            selection: selection.clone(),
        }]),

        // --- Table / nested block operations ---
        Operation::InsertChildBlock {
            parent_id, block, ..
        } => Ok(vec![Operation::RemoveChildBlock {
            parent_id: parent_id.clone(),
            block_id: block.id.clone(),
        }]),

        Operation::RemoveChildBlock {
            parent_id,
            block_id,
        } => {
            let block = doc.require_block(block_id)?;
            let parent = doc.require_block(parent_id)?;
            let index = parent
                .children
                .iter()
                .position(|id| id == block_id)
                .unwrap_or(0);
            Ok(vec![Operation::InsertChildBlock {
                parent_id: parent_id.clone(),
                block: block.clone(),
                index,
            }])
        }

        Operation::SetBlockProps {
            block_id,
            props,
            prev_props,
        } => {
            // Inverse: restore previous prop values.
            Ok(vec![Operation::SetBlockProps {
                block_id: block_id.clone(),
                props: prev_props.clone(),
                prev_props: props.clone(),
            }])
        }
    }
}

fn extract_spans_in_range(spans: &[TextSpan], start: usize, end: usize) -> Vec<TextSpan> {
    let mut result = Vec::new();
    let text: String = spans.iter().map(|span| span.text.as_str()).collect();
    let total_len: usize = spans.iter().map(TextSpan::len).sum();
    let start = prev_char_boundary_utf16(&text, start.min(total_len));
    let end = next_char_boundary_utf16(&text, end.min(total_len));

    if start >= end {
        return result;
    }

    let mut cumulative = 0;

    for span in spans {
        let span_start = cumulative;
        let span_end = cumulative + span.len();
        cumulative = span_end;

        if span_end <= start || span_start >= end {
            continue;
        }

        let local_start = start.saturating_sub(span_start);
        let local_end = (end - span_start).min(span.len());
        let byte_start = utf16_to_byte(&span.text, local_start);
        let byte_end = utf16_to_byte(&span.text, local_end);

        result.push(TextSpan::with_marks(
            span.text[byte_start..byte_end].to_string(),
            span.marks.clone(),
        ));
    }

    result
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::block::{Block, BlockAlignment, BlockId};
    use crate::model::inline::TextSpan;
    use crate::op::apply::apply_op;

    fn doc_with_text(id: &str, text: &str) -> Document {
        let mut doc = Document::new();
        let mut block = Block::new_paragraph(BlockId::new(id));
        if !text.is_empty() {
            block.content = vec![TextSpan::plain(text)];
        }
        doc.insert_block(block, 0);
        doc
    }

    #[test]
    fn insert_then_undo() {
        let mut doc = doc_with_text("b1", "hello");
        let mut sel = None;
        let op = Operation::InsertTextAt {
            block_id: BlockId::new("b1"),
            offset: 5,
            text: " world".into(),
            marks: vec![],
        };

        let inverse = compute_inverse(&doc, &sel, &op).unwrap();
        apply_op(&mut doc, &mut sel, &op).unwrap();
        assert_eq!(doc.plain_text(&BlockId::new("b1")), "hello world");

        for inv_op in &inverse {
            apply_op(&mut doc, &mut sel, inv_op).unwrap();
        }
        assert_eq!(doc.plain_text(&BlockId::new("b1")), "hello");
    }

    #[test]
    fn delete_then_undo() {
        let mut doc = doc_with_text("b1", "hello world");
        let mut sel = None;
        let op = Operation::DeleteRange {
            block_id: BlockId::new("b1"),
            start: 5,
            end: 11,
        };

        let inverse = compute_inverse(&doc, &sel, &op).unwrap();
        apply_op(&mut doc, &mut sel, &op).unwrap();
        assert_eq!(doc.plain_text(&BlockId::new("b1")), "hello");

        for inv_op in &inverse {
            apply_op(&mut doc, &mut sel, inv_op).unwrap();
        }
        assert_eq!(doc.plain_text(&BlockId::new("b1")), "hello world");
    }

    #[test]
    fn split_then_undo() {
        let mut doc = doc_with_text("b1", "hello world");
        let mut sel = None;
        let op = Operation::SplitBlock {
            block_id: BlockId::new("b1"),
            offset: 5,
            new_block_id: BlockId::new("b2"),
        };

        let inverse = compute_inverse(&doc, &sel, &op).unwrap();
        apply_op(&mut doc, &mut sel, &op).unwrap();
        assert_eq!(doc.root_block_count(), 2);

        for inv_op in &inverse {
            apply_op(&mut doc, &mut sel, inv_op).unwrap();
        }
        assert_eq!(doc.root_block_count(), 1);
        assert_eq!(doc.plain_text(&BlockId::new("b1")), "hello world");
    }

    #[test]
    fn set_block_align_then_undo_restores_left() {
        // No prior alignment → default Left.
        let mut doc = doc_with_text("b1", "hi");
        let mut sel = None;

        let op = Operation::SetBlockAlign {
            block_id: BlockId::new("b1"),
            align: BlockAlignment::Center,
        };

        let inverse = compute_inverse(&doc, &sel, &op).unwrap();
        apply_op(&mut doc, &mut sel, &op).unwrap();
        assert_eq!(
            doc.get_block(&BlockId::new("b1")).unwrap().alignment(),
            BlockAlignment::Center
        );

        for inv_op in &inverse {
            apply_op(&mut doc, &mut sel, inv_op).unwrap();
        }
        let block = doc.get_block(&BlockId::new("b1")).unwrap();
        assert_eq!(block.alignment(), BlockAlignment::Left);
        // The Left inverse should have cleared the prop, not stored it.
        assert!(block.prop("align").is_none());
    }

    #[test]
    fn set_block_align_then_undo_restores_previous_alignment() {
        let mut doc = Document::new();
        let mut block = Block::new_paragraph(BlockId::new("b1"));
        block
            .props
            .insert("align".into(), serde_json::Value::String("right".into()));
        block.content = vec![TextSpan::plain("hi")];
        doc.insert_block(block, 0);

        let mut sel = None;
        let op = Operation::SetBlockAlign {
            block_id: BlockId::new("b1"),
            align: BlockAlignment::Center,
        };

        let inverse = compute_inverse(&doc, &sel, &op).unwrap();
        apply_op(&mut doc, &mut sel, &op).unwrap();
        assert_eq!(
            doc.get_block(&BlockId::new("b1")).unwrap().alignment(),
            BlockAlignment::Center
        );

        for inv_op in &inverse {
            apply_op(&mut doc, &mut sel, inv_op).unwrap();
        }
        assert_eq!(
            doc.get_block(&BlockId::new("b1")).unwrap().alignment(),
            BlockAlignment::Right
        );
    }

    #[test]
    fn set_block_type_then_undo() {
        let mut doc = doc_with_text("b1", "hello");
        let mut sel = None;

        let mut props = serde_json::Map::new();
        props.insert("level".into(), serde_json::Value::Number(1.into()));

        let op = Operation::SetBlockType {
            block_id: BlockId::new("b1"),
            block_type: "heading".into(),
            props,
        };

        let inverse = compute_inverse(&doc, &sel, &op).unwrap();
        apply_op(&mut doc, &mut sel, &op).unwrap();

        let block = doc.get_block(&BlockId::new("b1")).unwrap();
        assert!(block.is_heading());

        for inv_op in &inverse {
            apply_op(&mut doc, &mut sel, inv_op).unwrap();
        }
        let block = doc.get_block(&BlockId::new("b1")).unwrap();
        assert!(block.is_paragraph());
    }
}
