use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

use crate::model::block::{Block, BlockAlignment, BlockId};
use crate::model::inline::{Mark, TextSpan};
use crate::model::selection::Selection;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Operation {
    InsertTextAt {
        block_id: BlockId,
        offset: usize,
        text: String,
        marks: Vec<Mark>,
    },
    DeleteRange {
        block_id: BlockId,
        start: usize,
        end: usize,
    },
    SplitBlock {
        block_id: BlockId,
        offset: usize,
        new_block_id: BlockId,
    },
    MergeBlock {
        /// Block that absorbs content.
        target_id: BlockId,
        /// Block that disappears.
        source_id: BlockId,
    },
    SetBlockType {
        block_id: BlockId,
        block_type: String,
        props: Map<String, Value>,
    },
    /// Set the text alignment (`props["align"]`) of a single block without
    /// touching any other props or its block type. [`BlockAlignment::Left`] is
    /// the default and is stored as the absence of the prop so undo/redo stays
    /// minimal and documents round-trip cleanly.
    SetBlockAlign {
        block_id: BlockId,
        align: BlockAlignment,
    },
    ToggleMark {
        block_id: BlockId,
        start: usize,
        end: usize,
        mark_type: String,
    },
    SetMark {
        block_id: BlockId,
        start: usize,
        end: usize,
        mark: Mark,
    },
    ClearMark {
        block_id: BlockId,
        start: usize,
        end: usize,
        mark_type: String,
    },
    RestoreRangeSpans {
        block_id: BlockId,
        start: usize,
        end: usize,
        spans: Vec<TextSpan>,
    },
    InsertBlock {
        block: Block,
        index: usize,
    },
    RemoveBlock {
        block_id: BlockId,
    },
    MoveBlock {
        block_id: BlockId,
        new_index: usize,
    },
    SetSelection {
        selection: Option<Selection>,
    },

    // --- Table / nested block operations ---
    /// Insert a child block under a parent (e.g. row under table, cell under row).
    /// The child is stored in Document.blocks but NOT in root.
    InsertChildBlock {
        parent_id: BlockId,
        block: Block,
        index: usize,
    },

    /// Remove a child block from its parent.
    RemoveChildBlock {
        parent_id: BlockId,
        block_id: BlockId,
    },

    /// Patch specific props on a block without changing its type.
    /// Only the keys present in `props` are updated; absent keys are untouched.
    /// `prev_props` captures the previous values of changed keys for undo.
    SetBlockProps {
        block_id: BlockId,
        props: Map<String, Value>,
        prev_props: Map<String, Value>,
    },
}
