//! EditorRuntimeActor - 页面块编辑运行时缓存层
//!
//! 职责：
//! - 持有 per-document `EditorBlockDocument` 内存态
//! - 接收 EditorCommand，在内存中 apply，产生 diff
//! - Convex 持久化仍由 block.rs 通过 execute_page_body_save 完成
//!
//! 不是 agent runtime：不维护会话、不做意图解析、不调模型（遵从 7-12 禁止项）。

use bridge_runtime::{
    apply_editor_command_to_document, editor_document_from_legacy_content,
    legacy_content_from_editor_document,
};
use core_protocol::{
    ContentNode, ContentNodePayload, EditorBlock, EditorBlockDocument, EditorBlockType,
    EditorCommand,
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
use std::time::Instant;

use crate::error::WebError;

#[derive(Debug, Clone)]
pub struct EditorDocumentState {
    pub document_id: String,
    pub workspace_id: Option<String>,
    pub document: EditorBlockDocument,
    pub revision: u64,
    pub conflict_detection_key: String,
    pub last_applied_at: Instant,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplyResult {
    pub ok: bool,
    pub command: String,
    pub document_id: Option<String>,
    pub workspace_id: Option<String>,
    pub new_revision: u64,
    pub conflict_detection_key: String,
    pub changed_blocks: Vec<ChangedBlock>,
    pub warnings: Vec<Value>,
    pub blocked: bool,
    pub risk: String,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ChangedBlock {
    pub block_id: String,
    pub op: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub before: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub after: Option<String>,
}

/// 编辑器增量 delta，可直接序列化为 JSON 传给 Tiptap bridge
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlockDelta {
    pub document_id: String,
    pub revision: u64,
    pub conflict_detection_key: String,
    pub operations: Vec<DeltaOperation>,
}

/// 一条增量操作，编辑器可通过 blockId 定位 + chain API 执行
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "op")]
pub enum DeltaOperation {
    #[serde(rename = "replace")]
    ReplaceBlock {
        block_id: String,
        text: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        block_type: Option<String>,
    },
    #[serde(rename = "insert_after")]
    InsertBlockAfter {
        anchor_block_id: String,
        block_id: String,
        text: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        block_type: Option<String>,
    },
    #[serde(rename = "delete")]
    DeleteBlock { block_id: String },
    #[serde(rename = "move_after")]
    MoveBlock {
        block_id: String,
        anchor_block_id: String,
    },
}

#[derive(Debug, Clone)]
pub struct EditorRuntimeActor {
    documents: Arc<RwLock<HashMap<String, EditorDocumentState>>>,
    block_delta_tx: Arc<RwLock<Option<tokio::sync::broadcast::Sender<Value>>>>,
}

impl EditorRuntimeActor {
    /// 连接广播 channel（Phase C：事件 stream delta）
    pub fn set_block_delta_tx(&self, tx: tokio::sync::broadcast::Sender<Value>) {
        if let Ok(mut guard) = self.block_delta_tx.write() {
            *guard = Some(tx);
        }
    }

    /// apply_command 完成后尝试推送 block.delta 到广播（Phase C）
    pub fn try_push_block_delta(&self, delta_json: &Value) {
        if let Ok(guard) = self.block_delta_tx.read() {
            if let Some(ref tx) = *guard {
                let _ = tx.send(delta_json.clone());
            }
        }
    }

    /// 在 apply 后构建 BlockDelta（Phase B 用于推送编辑器）
    pub fn build_block_delta(
        &self,
        document_id: &str,
        command: &EditorCommand,
    ) -> Result<BlockDelta, WebError> {
        let documents = self
            .documents
            .read()
            .map_err(|e| WebError::internal(format!("EditorRuntimeActor 锁失败：{e}")))?;
        let state = documents.get(document_id).ok_or_else(|| {
            WebError::bad_request_code(
                "mnote_editor_document_not_loaded",
                format!("文档 {document_id} 尚未加载"),
            )
        })?;

        let operations = match command {
            EditorCommand::ReplaceBlock(cmd) => {
                let block = state
                    .document
                    .blocks
                    .iter()
                    .find(|b| b.block_id == cmd.block_id);
                vec![DeltaOperation::ReplaceBlock {
                    block_id: cmd.block_id.clone(),
                    text: block_text_from_block(block),
                    block_type: block.map(|b| block_type_name(&b.block_type)),
                }]
            }
            EditorCommand::InsertBlockAfter(cmd) => {
                let new_block = state
                    .document
                    .blocks
                    .iter()
                    .find(|b| b.block_id == cmd.block.block_id);
                vec![DeltaOperation::InsertBlockAfter {
                    anchor_block_id: cmd.after_block_id.clone(),
                    block_id: cmd.block.block_id.clone(),
                    text: block_text_from_block(new_block),
                    block_type: new_block.map(|b| block_type_name(&b.block_type)),
                }]
            }
            EditorCommand::DeleteBlock(cmd) => {
                vec![DeltaOperation::DeleteBlock {
                    block_id: cmd.block_id.clone(),
                }]
            }
            EditorCommand::MoveBlock(cmd) => {
                let anchor = cmd.after_block_id.clone().unwrap_or_default();
                vec![DeltaOperation::MoveBlock {
                    block_id: cmd.block_id.clone(),
                    anchor_block_id: anchor,
                }]
            }
            _ => vec![],
        };

        Ok(BlockDelta {
            document_id: document_id.to_string(),
            revision: state.revision,
            conflict_detection_key: state.conflict_detection_key.clone(),
            operations,
        })
    }
    pub fn new() -> Self {
        Self {
            documents: Arc::new(RwLock::new(HashMap::new())),
            block_delta_tx: Arc::new(RwLock::new(None)),
        }
    }

    /// 读取或初始化文档内存态。
    pub fn load_or_init(
        &self,
        document_id: &str,
        workspace_id: Option<&str>,
        page_aggregate: &Value,
    ) -> Result<(), WebError> {
        let mut documents = self
            .documents
            .write()
            .map_err(|error| WebError::internal(format!("EditorRuntimeActor 锁失败：{error}")))?;

        if documents.contains_key(document_id) {
            return Ok(());
        }

        let content = page_aggregate
            .pointer("/body/content")
            .cloned()
            .unwrap_or_else(|| json!([]));
        let revision = page_aggregate
            .pointer("/body/revision")
            .and_then(|value| value.as_u64())
            .unwrap_or(1);
        let conflict_detection_key = page_aggregate
            .pointer("/body/conflictDetectionKey")
            .and_then(|value| value.as_str())
            .unwrap_or_default()
            .to_string();

        let document = editor_document_from_legacy_content(document_id, &content);

        let state = EditorDocumentState {
            document_id: document_id.to_string(),
            workspace_id: workspace_id.map(ToString::to_string),
            document,
            revision,
            conflict_detection_key,
            last_applied_at: Instant::now(),
        };

        documents.insert(document_id.to_string(), state);
        Ok(())
    }

    /// 在内存中应用 EditorCommand，产生 diff，返回 ApplyResult。
    pub fn apply_command(
        &self,
        document_id: &str,
        command: EditorCommand,
        command_name: &str,
    ) -> Result<ApplyResult, WebError> {
        let mut documents = self
            .documents
            .write()
            .map_err(|error| WebError::internal(format!("EditorRuntimeActor 锁失败：{error}")))?;

        let state = documents.get_mut(document_id).ok_or_else(|| {
            WebError::bad_request_code(
                "mnote_editor_document_not_loaded",
                format!("文档 {document_id} 尚未加载到 EditorRuntimeActor"),
            )
        })?;

        let changed_blocks = extract_changed_blocks(&state.document, &command);

        apply_editor_command_to_document(&mut state.document, command.clone()).map_err(
            |error| WebError::bad_request_code("mnote_editor_command_failed", format!("{error:?}")),
        )?;

        state.revision += 1;
        state.conflict_detection_key = format!(
            "{}:{}:{}",
            document_id,
            state.revision,
            state.last_applied_at.elapsed().as_micros()
        );
        state.last_applied_at = Instant::now();

        Ok(ApplyResult {
            ok: true,
            command: command_name.to_string(),
            document_id: Some(document_id.to_string()),
            workspace_id: state.workspace_id.clone(),
            new_revision: state.revision,
            conflict_detection_key: state.conflict_detection_key.clone(),
            changed_blocks,
            warnings: vec![],
            blocked: false,
            risk: "low".to_string(),
        })
    }

    /// 从内存态生成 legacy content（用于构建 Convex save payload）。
    pub fn legacy_content_for_save(&self, document_id: &str) -> Result<Value, WebError> {
        let documents = self
            .documents
            .read()
            .map_err(|error| WebError::internal(format!("EditorRuntimeActor 锁失败：{error}")))?;
        let state = documents.get(document_id).ok_or_else(|| {
            WebError::bad_request_code(
                "mnote_editor_document_not_loaded",
                format!("文档 {document_id} 尚未加载到 EditorRuntimeActor"),
            )
        })?;
        Ok(legacy_content_from_editor_document(&state.document))
    }

    pub fn current_revision(&self, document_id: &str) -> Option<u64> {
        self.documents
            .read()
            .ok()
            .and_then(|documents| documents.get(document_id).map(|state| state.revision))
    }

    pub fn current_conflict_detection_key(&self, document_id: &str) -> Option<String> {
        self.documents.read().ok().and_then(|documents| {
            documents
                .get(document_id)
                .map(|state| state.conflict_detection_key.clone())
        })
    }

    pub fn is_loaded(&self, document_id: &str) -> bool {
        self.documents
            .read()
            .ok()
            .map(|documents| documents.contains_key(document_id))
            .unwrap_or(false)
    }
}

fn extract_changed_blocks(
    document: &EditorBlockDocument,
    command: &EditorCommand,
) -> Vec<ChangedBlock> {
    match command {
        EditorCommand::ReplaceBlock(command) => {
            let before = document
                .blocks
                .iter()
                .find(|block| block.block_id == command.block_id)
                .and_then(block_text_summary);
            let after = block_text_from_content_nodes(&command.content_nodes);
            vec![ChangedBlock {
                block_id: command.block_id.clone(),
                op: "replace".to_string(),
                before,
                after,
            }]
        }
        EditorCommand::InsertBlockAfter(command) => {
            let after = block_text_from_content_nodes(&Some(command.block.content_nodes.clone()));
            vec![ChangedBlock {
                block_id: command.block.block_id.clone(),
                op: "insert_after".to_string(),
                before: None,
                after,
            }]
        }
        EditorCommand::DeleteBlock(command) => {
            let before = document
                .blocks
                .iter()
                .find(|block| block.block_id == command.block_id)
                .and_then(block_text_summary);
            vec![ChangedBlock {
                block_id: command.block_id.clone(),
                op: "delete".to_string(),
                before,
                after: None,
            }]
        }
        EditorCommand::MoveBlock(command) => {
            vec![ChangedBlock {
                block_id: command.block_id.clone(),
                op: "move".to_string(),
                before: None,
                after: None,
            }]
        }
        _ => vec![],
    }
}

fn block_text_summary(block: &EditorBlock) -> Option<String> {
    let text = block
        .content_nodes
        .iter()
        .filter_map(|node| match &node.payload {
            ContentNodePayload::Text { text, .. } => Some(text.as_str()),
            _ => None,
        })
        .collect::<Vec<_>>()
        .join("");
    if text.is_empty() {
        None
    } else {
        Some(text)
    }
}

fn block_text_from_content_nodes(nodes: &Option<Vec<ContentNode>>) -> Option<String> {
    nodes.as_ref().map(|nodes| {
        nodes
            .iter()
            .filter_map(|node| match &node.payload {
                ContentNodePayload::Text { text, .. } => Some(text.as_str()),
                _ => None,
            })
            .collect::<Vec<_>>()
            .join("")
    })
}

/// 从 EditorBlock 提取纯文本
fn block_text_from_block(block: Option<&EditorBlock>) -> String {
    block.map(block_text_summary).flatten().unwrap_or_default()
}

/// EditorBlockType → 字符串
fn block_type_name(block_type: &EditorBlockType) -> String {
    match block_type {
        EditorBlockType::Paragraph => "paragraph".into(),
        EditorBlockType::Heading => "heading".into(),
        EditorBlockType::Todo => "todo".into(),
        EditorBlockType::BulletListItem => "bullet_list".into(),
        EditorBlockType::NumberedListItem => "ordered_list".into(),
        EditorBlockType::CodeBlock => "code".into(),
        EditorBlockType::Quote => "blockquote".into(),
        EditorBlockType::Divider => "divider".into(),
        EditorBlockType::Image => "image".into(),
        EditorBlockType::Table => "table".into(),
        EditorBlockType::Mindmap => "mindmap".into(),
        EditorBlockType::Toc => "table_of_contents".into(),
        EditorBlockType::PageReference => "page_reference".into(),
        EditorBlockType::BlockReference => "block_reference".into(),
        EditorBlockType::Resource => "resource".into(),
    }
}
