//! Tree Index Store — Phase-90 (T90.4)
//!
//! Persistent storage for tree-indexed documents using SocialGraphV2.
//!
//! # Storage Model
//!
//! Uses the existing `SocialGraphV2` (Entity + Relation) as the backing store:
//!
//! ```text
//! Entity(type="tree_doc")   ── HAS_ROOT ──>  Entity(type="tree_section")
//!                                                │
//!                                            HAS_CHILD
//!                                                │
//!                                                v
//!                                           Entity(type="tree_section")
//! ```
//!
//! **Entity properties** (stored in `Entity::properties`):
//! - `tree_doc`: `doc_name`, `description`, `page_count`, `source_path`, `source_format`, `node_count`, `max_depth`
//! - `tree_section`: `node_id`, `level`, `content`, `summary`, `token_count`, `line_num`, `start_page`, `end_page`
//!
//! **Relation metadata** (stored in `Relation::metadata`):
//! - `tree_has_root` / `tree_has_child`: `order` (child ordering, 0-based)

use std::sync::Arc;

use crate::social::SocialStoreV2;
use crate::types::graph::{Entity, Relation};

use super::types::{
    TreeDocument, TreeMetadata, TreeNode, NodeContent,
    TreeIndexError, TreeIndexResult,
    tree_entity_types, tree_relation_types,
};

/// Persistent tree index store backed by SocialGraphV2.
///
/// Stores tree-structured documents as graph entities and relations.
/// Supports CRUD operations on documents and their tree structures.
pub struct TreeIndexStore {
    graph: Arc<SocialStoreV2>,
}

impl TreeIndexStore {
    /// Create a new TreeIndexStore backed by the given graph store.
    pub fn new(graph: Arc<SocialStoreV2>) -> Self {
        Self { graph }
    }

    /// Get a reference to the underlying graph store.
    pub fn graph(&self) -> &Arc<SocialStoreV2> {
        &self.graph
    }

    // ========================================================================
    // Import / Write Operations
    // ========================================================================

    /// Import a complete tree document into the store.
    ///
    /// Creates entities for the document and all nodes, plus relations for the tree edges.
    /// Returns the document entity ID.
    pub fn import_tree(&self, doc: &TreeDocument) -> TreeIndexResult<String> {
        // 1. Create the document entity
        let doc_entity = self.create_doc_entity(doc);
        let doc_entity_id = doc_entity.id.clone();
        self.graph.put_entity(doc_entity);

        // 2. Import all root nodes and their subtrees
        for (order, root) in doc.structure.iter().enumerate() {
            let root_entity_id = self.import_node_recursive(root)?;

            let relation = Relation::new(
                &doc_entity_id,
                &root_entity_id,
                tree_relation_types::HAS_ROOT,
            )
            .with_metadata("order", serde_json::json!(order));

            self.graph.put_relation(relation);
        }

        Ok(doc_entity_id)
    }

    /// Import a tree document, replacing any existing document with the same name.
    ///
    /// This first deletes any existing document with the same name, then imports the new one.
    pub fn import_or_replace(&self, doc: &TreeDocument) -> TreeIndexResult<String> {
        // Check for existing document with same name
        if let Some(existing) = self.find_document_by_name(&doc.metadata.doc_name) {
            self.delete_document(&existing.id)?;
        }

        self.import_tree(doc)
    }

    /// Recursively import a node and all its children.
    /// Returns the entity ID of the created node.
    fn import_node_recursive(&self, node: &TreeNode) -> TreeIndexResult<String> {
        let entity = self.create_section_entity(node);
        let entity_id = entity.id.clone();
        self.graph.put_entity(entity);

        // Import children
        for (order, child) in node.children.iter().enumerate() {
            let child_entity_id = self.import_node_recursive(child)?;

            let relation = Relation::new(
                &entity_id,
                &child_entity_id,
                tree_relation_types::HAS_CHILD,
            )
            .with_metadata("order", serde_json::json!(order));

            self.graph.put_relation(relation);
        }

        Ok(entity_id)
    }

    // ========================================================================
    // Read / Query Operations
    // ========================================================================

    /// Retrieve a complete tree document by its entity ID.
    ///
    /// Only returns **active** nodes (shadow/demoted nodes are excluded).
    /// Use [`get_tree_full`] to include shadow nodes with markers.
    pub fn get_tree(&self, doc_id: &str) -> TreeIndexResult<TreeDocument> {
        self.get_tree_internal(doc_id, false)
    }

    /// Retrieve a complete tree document including shadow (demoted) nodes.
    ///
    /// Shadow nodes are included in the tree structure with a `"__shadow": true`
    /// property marker, allowing callers to distinguish active vs. dormant nodes.
    /// This is useful for debugging, admin UIs, and full tree visualization.
    pub fn get_tree_full(&self, doc_id: &str) -> TreeIndexResult<TreeDocument> {
        self.get_tree_internal(doc_id, true)
    }

    /// Internal implementation shared by `get_tree` and `get_tree_full`.
    fn get_tree_internal(&self, doc_id: &str, include_shadows: bool) -> TreeIndexResult<TreeDocument> {
        let doc_entity = self.graph.get_entity_by_id(doc_id).ok_or_else(|| {
            TreeIndexError::DocumentNotFound(doc_id.to_string())
        })?;

        if doc_entity.entity_type != tree_entity_types::TREE_DOC {
            return Err(TreeIndexError::DocumentNotFound(format!(
                "Entity {} is not a tree document (type={})",
                doc_id, doc_entity.entity_type
            )));
        }

        let metadata = self.entity_to_metadata(&doc_entity);
        let structure = self.load_children_recursive(doc_id, tree_relation_types::HAS_ROOT, include_shadows)?;

        Ok(TreeDocument {
            id: doc_id.to_string(),
            metadata,
            structure,
        })
    }

    /// Get the content of a specific node by its entity ID.
    pub fn get_node_content(&self, node_entity_id: &str) -> TreeIndexResult<NodeContent> {
        let entity = self.graph.get_entity_by_id(node_entity_id).ok_or_else(|| {
            TreeIndexError::NodeNotFound(node_entity_id.to_string())
        })?;

        let text = entity
            .properties
            .get("content")
            .and_then(|v| v.as_str())
            .unwrap_or("")
            .to_string();

        let token_count = entity
            .properties
            .get("token_count")
            .and_then(|v| v.as_u64())
            .unwrap_or(0) as usize;

        Ok(NodeContent { text, token_count })
    }

    /// List all tree-indexed documents.
    pub fn list_documents(&self) -> Vec<(String, TreeMetadata)> {
        self.graph
            .list_entities_by_type(tree_entity_types::TREE_DOC)
            .into_iter()
            .map(|e| {
                let id = e.id.clone();
                let metadata = self.entity_to_metadata(&e);
                (id, metadata)
            })
            .collect()
    }

    /// Find a document entity by name.
    pub fn find_document_by_name(&self, doc_name: &str) -> Option<Entity> {
        self.graph
            .list_entities_by_type(tree_entity_types::TREE_DOC)
            .into_iter()
            .find(|e| {
                e.properties
                    .get("doc_name")
                    .and_then(|v| v.as_str())
                    == Some(doc_name)
            })
    }

    /// Get the number of indexed documents.
    pub fn document_count(&self) -> usize {
        self.graph
            .list_entities_by_type(tree_entity_types::TREE_DOC)
            .len()
    }

    // ========================================================================
    // Delete Operations
    // ========================================================================

    /// Delete a tree document and all its nodes/relations.
    ///
    /// Collects all reachable entity IDs first (including shadow nodes),
    /// then deletes them in reverse order (leaves first, then parents, then doc).
    pub fn delete_document(&self, doc_id: &str) -> TreeIndexResult<bool> {
        // Check document exists
        let doc_entity = match self.graph.get_entity_by_id(doc_id) {
            Some(e) if e.entity_type == tree_entity_types::TREE_DOC => e,
            Some(_) => {
                return Err(TreeIndexError::DocumentNotFound(format!(
                    "Entity {} is not a tree document",
                    doc_id
                )));
            }
            None => return Ok(false),
        };

        // Collect all entity IDs first (BFS/DFS), then delete
        let mut to_delete = Vec::new();
        self.collect_subtree_entities(doc_id, &mut to_delete);

        // Delete all collected entities (children first, then doc)
        for entity_id in to_delete.iter().rev() {
            self.graph.delete_entity(entity_id);
        }

        // Delete the document entity itself
        self.graph.delete_entity(&doc_entity.id);

        Ok(true)
    }

    /// Recursively collect all entity IDs reachable via tree relations.
    ///
    /// Follows `HAS_ROOT`, `HAS_CHILD`, and `HAS_SHADOW` relations to ensure
    /// shadow (demoted) nodes are also collected for cleanup.
    fn collect_subtree_entities(&self, entity_id: &str, out: &mut Vec<String>) {
        let relations = self.graph.get_entity_relations(entity_id);

        for rel in &relations {
            let is_tree_rel = rel.source == entity_id
                && (rel.relation_type == tree_relation_types::HAS_ROOT
                    || rel.relation_type == tree_relation_types::HAS_CHILD
                    || rel.relation_type == tree_relation_types::HAS_SHADOW);

            if is_tree_rel {
                // Collect recursively first (depth-first)
                self.collect_subtree_entities(&rel.target, out);
                out.push(rel.target.clone());
            }
        }
    }

    // ========================================================================
    // Internal Helpers
    // ========================================================================

    /// Create an Entity for a tree document.
    fn create_doc_entity(&self, doc: &TreeDocument) -> Entity {
        Entity::new(&doc.metadata.doc_name, tree_entity_types::TREE_DOC)
            .with_id(&doc.id)
            .with_property("doc_name".to_string(), serde_json::json!(doc.metadata.doc_name))
            .with_property("source_format".to_string(), serde_json::json!(doc.metadata.source_format))
            .with_property("node_count".to_string(), serde_json::json!(doc.total_nodes()))
            .with_property("max_depth".to_string(), serde_json::json!(doc.max_depth()))
            .with_property(
                "description".to_string(),
                serde_json::json!(doc.metadata.description),
            )
            .with_property(
                "page_count".to_string(),
                serde_json::json!(doc.metadata.page_count),
            )
            .with_property(
                "source_path".to_string(),
                serde_json::json!(doc.metadata.source_path),
            )
    }

    /// Create an Entity for a tree section node.
    fn create_section_entity(&self, node: &TreeNode) -> Entity {
        let mut entity = Entity::new(&node.title, tree_entity_types::TREE_SECTION)
            .with_property("node_id".to_string(), serde_json::json!(node.node_id))
            .with_property("level".to_string(), serde_json::json!(node.level))
            .with_property("content".to_string(), serde_json::json!(node.content.text))
            .with_property("token_count".to_string(), serde_json::json!(node.content.token_count));

        if let Some(ref summary) = node.summary {
            entity = entity.with_property("summary".to_string(), serde_json::json!(summary));
        }
        if let Some(line_num) = node.line_num {
            entity = entity.with_property("line_num".to_string(), serde_json::json!(line_num));
        }
        if let Some(start_page) = node.start_page {
            entity = entity.with_property("start_page".to_string(), serde_json::json!(start_page));
        }
        if let Some(end_page) = node.end_page {
            entity = entity.with_property("end_page".to_string(), serde_json::json!(end_page));
        }

        // Store additional properties
        for (key, value) in &node.properties {
            entity = entity.with_property(key.clone(), value.clone());
        }

        entity
    }

    /// Load children recursively, optionally including shadow (demoted) nodes.
    ///
    /// When `include_shadows` is true, shadow nodes are included with a
    /// `"__shadow": true` marker in their `properties`, allowing callers to
    /// distinguish active from dormant nodes.
    fn load_children_recursive(
        &self,
        entity_id: &str,
        relation_type: &str,
        include_shadows: bool,
    ) -> TreeIndexResult<Vec<TreeNode>> {
        let relations = self.graph.get_entity_relations(entity_id);

        let mut children_with_order: Vec<(usize, TreeNode)> = Vec::new();

        for rel in &relations {
            if rel.source != entity_id {
                continue;
            }

            let is_active = rel.relation_type == relation_type;
            let is_shadow = include_shadows
                && rel.relation_type == tree_relation_types::HAS_SHADOW;

            if is_active || is_shadow {
                let order = rel
                    .metadata
                    .get("order")
                    .and_then(|v| v.as_u64())
                    .unwrap_or(0) as usize;

                if let Some(child_entity) = self.graph.get_entity_by_id(&rel.target) {
                    let mut child_node = self.entity_to_tree_node_recursive(&child_entity, include_shadows)?;
                    if is_shadow {
                        child_node.properties.insert(
                            "__shadow".to_string(),
                            serde_json::json!(true),
                        );
                    }
                    children_with_order.push((order, child_node));
                }
            }
        }

        // Sort by order
        children_with_order.sort_by_key(|(order, _)| *order);
        Ok(children_with_order.into_iter().map(|(_, node)| node).collect())
    }

    /// Convert an Entity back to a TreeNode, optionally including shadow children.
    fn entity_to_tree_node_recursive(
        &self,
        entity: &Entity,
        include_shadows: bool,
    ) -> TreeIndexResult<TreeNode> {
        let node_id = entity
            .properties
            .get("node_id")
            .and_then(|v| v.as_str())
            .unwrap_or("0000")
            .to_string();

        let level = entity
            .properties
            .get("level")
            .and_then(|v| v.as_u64())
            .unwrap_or(0) as u8;

        let text = entity
            .properties
            .get("content")
            .and_then(|v| v.as_str())
            .unwrap_or("")
            .to_string();

        let token_count = entity
            .properties
            .get("token_count")
            .and_then(|v| v.as_u64())
            .unwrap_or(0) as usize;

        let summary = entity
            .properties
            .get("summary")
            .and_then(|v| v.as_str())
            .map(String::from);

        let line_num = entity
            .properties
            .get("line_num")
            .and_then(|v| v.as_u64())
            .map(|v| v as usize);

        let start_page = entity
            .properties
            .get("start_page")
            .and_then(|v| v.as_u64())
            .map(|v| v as usize);

        let end_page = entity
            .properties
            .get("end_page")
            .and_then(|v| v.as_u64())
            .map(|v| v as usize);

        // Load children recursively (including shadows if requested)
        let children = self.load_children_recursive(
            &entity.id,
            tree_relation_types::HAS_CHILD,
            include_shadows,
        )?;

        Ok(TreeNode {
            node_id,
            title: entity.name.clone(),
            level,
            content: NodeContent { text, token_count },
            summary,
            children,
            line_num,
            start_page,
            end_page,
            properties: Default::default(),
        })
    }

    /// Convert an Entity to TreeMetadata.
    fn entity_to_metadata(&self, entity: &Entity) -> TreeMetadata {
        let doc_name = entity
            .properties
            .get("doc_name")
            .and_then(|v| v.as_str())
            .unwrap_or(&entity.name)
            .to_string();

        let description = entity
            .properties
            .get("description")
            .and_then(|v| v.as_str())
            .map(String::from);

        let page_count = entity
            .properties
            .get("page_count")
            .and_then(|v| v.as_u64())
            .map(|v| v as usize);

        let source_path = entity
            .properties
            .get("source_path")
            .and_then(|v| v.as_str())
            .map(String::from);

        let source_format = entity
            .properties
            .get("source_format")
            .and_then(|v| v.as_str())
            .unwrap_or("markdown")
            .to_string();

        TreeMetadata {
            doc_name,
            description,
            page_count,
            source_path,
            source_format,
            created_at: entity.created_at,
            updated_at: entity.updated_at,
            properties: Default::default(),
        }
    }
}

// ─── Tests ───────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use crate::social::SocialStoreV2Config;

    fn create_test_store() -> (TreeIndexStore, tempfile::TempDir) {
        let tmp = tempfile::tempdir().unwrap();
        let config = SocialStoreV2Config::new(tmp.path());
        let graph = Arc::new(SocialStoreV2::new(config));
        let store = TreeIndexStore::new(graph);
        (store, tmp)
    }

    fn sample_document() -> TreeDocument {
        let meta = TreeMetadata::new("test-doc")
            .with_description("A test document")
            .with_source_format("markdown");

        let mut root = TreeNode::new("0001", "Introduction", 1)
            .with_content("Welcome to the document.");

        let child1 = TreeNode::new("0002", "Background", 2)
            .with_content("Some background info.")
            .with_summary("Background summary");

        let mut child2 = TreeNode::new("0003", "Methods", 2)
            .with_content("Method description.");

        let grandchild = TreeNode::new("0004", "Experiment A", 3)
            .with_content("Details of experiment A.");

        child2.add_child(grandchild);
        root.add_child(child1);
        root.add_child(child2);

        TreeDocument::new(meta, vec![root])
    }

    #[test]
    fn test_import_and_retrieve() {
        let (store, _tmp) = create_test_store();
        let doc = sample_document();
        let doc_id = doc.id.clone();

        let stored_id = store.import_tree(&doc).unwrap();
        assert_eq!(stored_id, doc_id);

        // Retrieve
        let retrieved = store.get_tree(&doc_id).unwrap();
        assert_eq!(retrieved.metadata.doc_name, "test-doc");
        assert_eq!(retrieved.structure.len(), 1);

        let root = &retrieved.structure[0];
        assert_eq!(root.title, "Introduction");
        assert_eq!(root.children.len(), 2);
        assert_eq!(root.children[0].title, "Background");
        assert_eq!(root.children[1].title, "Methods");
        assert_eq!(root.children[1].children.len(), 1);
        assert_eq!(root.children[1].children[0].title, "Experiment A");
    }

    #[test]
    fn test_list_documents() {
        let (store, _tmp) = create_test_store();

        assert_eq!(store.document_count(), 0);

        let doc = sample_document();
        store.import_tree(&doc).unwrap();

        let docs = store.list_documents();
        assert_eq!(docs.len(), 1);
        assert_eq!(docs[0].1.doc_name, "test-doc");
    }

    #[test]
    fn test_find_document_by_name() {
        let (store, _tmp) = create_test_store();

        let doc = sample_document();
        store.import_tree(&doc).unwrap();

        assert!(store.find_document_by_name("test-doc").is_some());
        assert!(store.find_document_by_name("nonexistent").is_none());
    }

    #[test]
    fn test_delete_document() {
        let (store, _tmp) = create_test_store();

        let doc = sample_document();
        let doc_id = doc.id.clone();
        store.import_tree(&doc).unwrap();

        assert_eq!(store.document_count(), 1);

        let deleted = store.delete_document(&doc_id).unwrap();
        assert!(deleted);
        assert_eq!(store.document_count(), 0);

        // Deleting non-existent returns false
        let deleted = store.delete_document(&doc_id).unwrap();
        assert!(!deleted);
    }

    #[test]
    fn test_import_or_replace() {
        let (store, _tmp) = create_test_store();

        let doc1 = sample_document();
        store.import_or_replace(&doc1).unwrap();
        assert_eq!(store.document_count(), 1);

        // Import another document with the same name
        let mut doc2 = sample_document();
        doc2.id = uuid::Uuid::new_v4().to_string();
        store.import_or_replace(&doc2).unwrap();

        // Should still be 1 (replaced, not duplicated)
        assert_eq!(store.document_count(), 1);
    }

    #[test]
    fn test_node_content_preserved() {
        let (store, _tmp) = create_test_store();

        let meta = TreeMetadata::new("content-test");
        let node = TreeNode::new("0001", "Test", 1)
            .with_content("Full text content here.\nWith multiple lines.")
            .with_summary("Summary of the content")
            .with_line_num(42);

        let doc = TreeDocument::new(meta, vec![node]);
        let doc_id = doc.id.clone();
        store.import_tree(&doc).unwrap();

        let retrieved = store.get_tree(&doc_id).unwrap();
        let node = &retrieved.structure[0];
        assert!(node.content.text.contains("Full text content here"));
        assert_eq!(node.summary.as_deref(), Some("Summary of the content"));
        assert_eq!(node.line_num, Some(42));
    }

    #[test]
    fn test_get_node_content() {
        let (store, _tmp) = create_test_store();

        let meta = TreeMetadata::new("node-content-test");
        let node = TreeNode::new("0001", "Test", 1)
            .with_content("Node content text");

        let doc = TreeDocument::new(meta, vec![node]);
        let doc_id = doc.id.clone();
        store.import_tree(&doc).unwrap();

        // Get the tree to find the section entity ID
        let retrieved = store.get_tree(&doc_id).unwrap();

        // Get root section entities via relations
        let relations = store.graph.get_entity_relations(&doc_id);
        let root_rel = relations
            .iter()
            .find(|r| r.relation_type == tree_relation_types::HAS_ROOT)
            .expect("should have root relation");

        let content = store.get_node_content(&root_rel.target).unwrap();
        assert_eq!(content.text, "Node content text");
        assert!(content.token_count > 0);
    }

    // ─── Phase-95 compatibility tests ─────────────────────────────────

    #[test]
    fn test_delete_document_traverses_shadow_relations() {
        use crate::tree_index::dynamic::DynamicTreeManager;
        use crate::tree_index::DynamicTreeConfig;

        let (store, _tmp) = create_test_store();
        let store = std::sync::Arc::new(store);

        // Create a doc with children to demote
        let meta = TreeMetadata::new("shadow-delete-test");
        let child1 = TreeNode::new("0002", "Sub A", 2).with_content("Content A");
        let child2 = TreeNode::new("0003", "Sub B", 2).with_content("Content B");
        let mut root = TreeNode::new("0001", "Root", 1)
            .with_content("Root content");
        root.add_child(child1);
        root.add_child(child2);
        let doc = TreeDocument::new(meta, vec![root]);
        let doc_id = store.import_tree(&doc).unwrap();

        // Demote children → creates shadow entities
        let config = DynamicTreeConfig {
            enabled: true,
            preserve_top_levels: 0,
            ..Default::default()
        };
        let mgr = DynamicTreeManager::new(store.clone(), config);

        let root_entity_id = {
            let rels = store.graph().get_entity_relations(&doc_id);
            rels.iter().find(|r| r.relation_type == tree_relation_types::HAS_ROOT)
                .unwrap().target.clone()
        };

        let report = mgr.demote_subtree(&root_entity_id).unwrap();
        assert_eq!(report.shadow_count, 2);
        assert_eq!(mgr.shadow_node_count(&doc_id), 2);

        // Verify collect_subtree_entities finds shadow nodes via HAS_SHADOW
        let mut collected = Vec::new();
        store.collect_subtree_entities(&doc_id, &mut collected);
        // Should find root + 2 shadow children = 3 entities
        assert_eq!(collected.len(), 3,
            "collect_subtree_entities must traverse HAS_SHADOW relations \
             (found {}, expected 3: root + 2 shadow children)", collected.len());

        // Delete the document
        let deleted = store.delete_document(&doc_id).unwrap();
        assert!(deleted);

        // Document should no longer be accessible via API
        assert_eq!(store.document_count(), 0);
        assert!(store.get_tree(&doc_id).is_err());

        // A new document should not be affected by residual data
        let meta2 = TreeMetadata::new("fresh-doc");
        let root2 = TreeNode::new("0001", "Fresh Root", 1).with_content("Clean content");
        let doc2 = TreeDocument::new(meta2, vec![root2]);
        let doc2_id = store.import_tree(&doc2).unwrap();
        let retrieved = store.get_tree(&doc2_id).unwrap();
        assert_eq!(retrieved.structure[0].title, "Fresh Root");
        assert_eq!(store.document_count(), 1);
    }

    #[test]
    fn test_get_tree_full_includes_shadows() {
        use crate::tree_index::dynamic::DynamicTreeManager;
        use crate::tree_index::DynamicTreeConfig;

        let (store, _tmp) = create_test_store();
        let store = std::sync::Arc::new(store);

        // Create doc with children
        let meta = TreeMetadata::new("shadow-full-test");
        let child1 = TreeNode::new("0002", "Sub A", 2).with_content("Content A about algorithms");
        let child2 = TreeNode::new("0003", "Sub B", 2).with_content("Content B about databases");
        let mut root = TreeNode::new("0001", "Root", 1)
            .with_content("Root content");
        root.add_child(child1);
        root.add_child(child2);
        let doc = TreeDocument::new(meta, vec![root]);
        let doc_id = store.import_tree(&doc).unwrap();

        // Demote children
        let config = DynamicTreeConfig {
            enabled: true,
            preserve_top_levels: 0,
            ..Default::default()
        };
        let mgr = DynamicTreeManager::new(store.clone(), config);

        let root_entity_id = {
            let rels = store.graph().get_entity_relations(&doc_id);
            rels.iter().find(|r| r.relation_type == tree_relation_types::HAS_ROOT)
                .unwrap().target.clone()
        };

        mgr.demote_subtree(&root_entity_id).unwrap();

        // get_tree (default) should NOT show shadow children
        let tree_active = store.get_tree(&doc_id).unwrap();
        let root_active = &tree_active.structure[0];
        assert_eq!(root_active.children.len(), 0, "Active-only view should have no children after demote");

        // get_tree_full should show shadow children with __shadow marker
        let tree_full = store.get_tree_full(&doc_id).unwrap();
        let root_full = &tree_full.structure[0];
        assert_eq!(root_full.children.len(), 2, "Full view should show 2 shadow children");

        // Verify __shadow markers
        for child in &root_full.children {
            let is_shadow = child.properties.get("__shadow")
                .and_then(|v| v.as_bool())
                .unwrap_or(false);
            assert!(is_shadow, "Shadow child '{}' should have __shadow=true marker", child.title);
        }
    }
}
