//! Tree Structure JSON Import/Export — Phase-90 (T90.5)
//!
//! Bidirectional serialization between `TreeDocument` and JSON,
//! compatible with PageIndex's JSON output format.
//!
//! # PageIndex JSON Format
//!
//! ```json
//! {
//!   "doc_name": "my-document",
//!   "doc_description": "Optional description",
//!   "structure": [
//!     {
//!       "title": "Chapter 1",
//!       "node_id": "0001",
//!       "summary": "Chapter summary",
//!       "text": "Full text content",
//!       "line_num": 1,
//!       "start_page": 1,
//!       "end_page": 5,
//!       "nodes": [
//!         {
//!           "title": "Section 1.1",
//!           "node_id": "0002",
//!           "text": "Section content",
//!           "nodes": []
//!         }
//!       ]
//!     }
//!   ]
//! }
//! ```

use serde::{Deserialize, Serialize};

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

// ─── PageIndex-Compatible JSON Types ─────────────────────────────────────────

/// PageIndex-compatible JSON root structure.
#[derive(Debug, Serialize, Deserialize)]
pub struct PageIndexJson {
    /// Document name.
    pub doc_name: String,

    /// Optional document description.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub doc_description: Option<String>,

    /// Tree structure (list of root nodes).
    pub structure: Vec<PageIndexNodeJson>,
}

/// PageIndex-compatible JSON node.
#[derive(Debug, Serialize, Deserialize)]
pub struct PageIndexNodeJson {
    /// Section title.
    pub title: String,

    /// Node ID (e.g., "0001").
    #[serde(skip_serializing_if = "Option::is_none")]
    pub node_id: Option<String>,

    /// Full text content.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub text: Option<String>,

    /// Summary (for leaf nodes).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub summary: Option<String>,

    /// Prefix summary (for branch nodes in PageIndex).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub prefix_summary: Option<String>,

    /// Line number in original document.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub line_num: Option<usize>,

    /// Start page (for PDF sources).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start_page: Option<usize>,

    /// End page (for PDF sources).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_page: Option<usize>,

    /// Child nodes (PageIndex uses "nodes" as the field name).
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub nodes: Vec<PageIndexNodeJson>,
}

// ─── Import (PageIndex JSON → TreeDocument) ──────────────────────────────────

/// Import a PageIndex-format JSON string into a TreeDocument.
///
/// Supports both the full PageIndex format and simplified variations.
pub fn import_from_json(json_str: &str) -> TreeIndexResult<TreeDocument> {
    let pi_json: PageIndexJson =
        serde_json::from_str(json_str).map_err(TreeIndexError::SerdeError)?;

    import_from_pageindex_json(pi_json)
}

/// Import from a parsed PageIndexJson structure.
pub fn import_from_pageindex_json(pi_json: PageIndexJson) -> TreeIndexResult<TreeDocument> {
    let mut metadata = TreeMetadata::new(&pi_json.doc_name);
    if let Some(desc) = &pi_json.doc_description {
        metadata = metadata.with_description(desc);
    }

    // Detect source format based on presence of page numbers
    let has_pages = pi_json.structure.iter().any(|n| n.start_page.is_some());
    if has_pages {
        metadata = metadata.with_source_format("pdf");
    } else {
        metadata = metadata.with_source_format("imported");
    }

    let mut counter = NodeIdCounter::new();
    let structure: Vec<TreeNode> = pi_json
        .structure
        .into_iter()
        .map(|n| convert_pi_node(n, 1, &mut counter))
        .collect();

    if structure.is_empty() {
        return Err(TreeIndexError::InvalidStructure(
            "Empty tree structure in JSON".to_string(),
        ));
    }

    Ok(TreeDocument::new(metadata, structure))
}

/// Import from a JSON file path.
pub fn import_from_file(path: &std::path::Path) -> TreeIndexResult<TreeDocument> {
    let content = std::fs::read_to_string(path).map_err(|e| {
        TreeIndexError::ParseError(format!("Failed to read file {}: {}", path.display(), e))
    })?;
    import_from_json(&content)
}

// ─── Export (TreeDocument → PageIndex JSON) ──────────────────────────────────

/// Export a TreeDocument to PageIndex-compatible JSON string.
pub fn export_to_json(doc: &TreeDocument, pretty: bool) -> TreeIndexResult<String> {
    let pi_json = export_to_pageindex_json(doc);

    if pretty {
        serde_json::to_string_pretty(&pi_json).map_err(TreeIndexError::SerdeError)
    } else {
        serde_json::to_string(&pi_json).map_err(TreeIndexError::SerdeError)
    }
}

/// Export to a PageIndexJson structure.
pub fn export_to_pageindex_json(doc: &TreeDocument) -> PageIndexJson {
    PageIndexJson {
        doc_name: doc.metadata.doc_name.clone(),
        doc_description: doc.metadata.description.clone(),
        structure: doc
            .structure
            .iter()
            .map(tree_node_to_pi_node)
            .collect(),
    }
}

/// Export to a JSON file.
pub fn export_to_file(doc: &TreeDocument, path: &std::path::Path) -> TreeIndexResult<()> {
    let json = export_to_json(doc, true)?;
    std::fs::write(path, json).map_err(|e| {
        TreeIndexError::ParseError(format!("Failed to write file {}: {}", path.display(), e))
    })
}

// ─── Internal Conversion Helpers ─────────────────────────────────────────────

/// Counter for generating node IDs when they're missing in the import.
struct NodeIdCounter {
    next: usize,
}

impl NodeIdCounter {
    fn new() -> Self {
        Self { next: 1 }
    }

    fn next_id(&mut self) -> String {
        let id = format!("{:04}", self.next);
        self.next += 1;
        id
    }
}

/// Convert a PageIndex JSON node to a TreeNode.
fn convert_pi_node(
    pi_node: PageIndexNodeJson,
    level: u8,
    counter: &mut NodeIdCounter,
) -> TreeNode {
    let node_id = pi_node
        .node_id
        .unwrap_or_else(|| counter.next_id());

    let text = pi_node.text.unwrap_or_default();
    let token_count = estimate_tokens(&text);

    // PageIndex uses "summary" for leaf nodes and "prefix_summary" for branches.
    // We unify both into our single "summary" field.
    let summary = pi_node
        .summary
        .or(pi_node.prefix_summary);

    let children: Vec<TreeNode> = pi_node
        .nodes
        .into_iter()
        .map(|child| convert_pi_node(child, level + 1, counter))
        .collect();

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

/// Convert a TreeNode to a PageIndex JSON node.
fn tree_node_to_pi_node(node: &TreeNode) -> PageIndexNodeJson {
    let (summary, prefix_summary) = if node.is_leaf() {
        (node.summary.clone(), None)
    } else {
        (None, node.summary.clone())
    };

    PageIndexNodeJson {
        title: node.title.clone(),
        node_id: Some(node.node_id.clone()),
        text: if node.content.text.is_empty() {
            None
        } else {
            Some(node.content.text.clone())
        },
        summary,
        prefix_summary,
        line_num: node.line_num,
        start_page: node.start_page,
        end_page: node.end_page,
        nodes: node.children.iter().map(tree_node_to_pi_node).collect(),
    }
}

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

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

    const SAMPLE_PAGEINDEX_JSON: &str = r#"{
        "doc_name": "sample-paper",
        "doc_description": "A sample research paper",
        "structure": [
            {
                "title": "Abstract",
                "node_id": "0001",
                "text": "This paper presents...",
                "summary": "Paper abstract summary",
                "nodes": []
            },
            {
                "title": "Introduction",
                "node_id": "0002",
                "text": "We introduce...",
                "nodes": [
                    {
                        "title": "Background",
                        "node_id": "0003",
                        "text": "Prior work shows...",
                        "summary": "Background on the topic",
                        "nodes": []
                    },
                    {
                        "title": "Motivation",
                        "node_id": "0004",
                        "text": "The key insight is...",
                        "nodes": []
                    }
                ]
            }
        ]
    }"#;

    #[test]
    fn test_import_pageindex_json() {
        let doc = import_from_json(SAMPLE_PAGEINDEX_JSON).unwrap();

        assert_eq!(doc.metadata.doc_name, "sample-paper");
        assert_eq!(
            doc.metadata.description.as_deref(),
            Some("A sample research paper")
        );
        assert_eq!(doc.structure.len(), 2);

        let abstract_node = &doc.structure[0];
        assert_eq!(abstract_node.title, "Abstract");
        assert_eq!(abstract_node.node_id, "0001");
        assert!(abstract_node.content.text.contains("This paper presents"));
        assert_eq!(
            abstract_node.summary.as_deref(),
            Some("Paper abstract summary")
        );
        assert!(abstract_node.is_leaf());

        let intro = &doc.structure[1];
        assert_eq!(intro.title, "Introduction");
        assert_eq!(intro.children.len(), 2);
        assert_eq!(intro.children[0].title, "Background");
        assert_eq!(intro.children[1].title, "Motivation");
    }

    #[test]
    fn test_export_to_json() {
        let meta = TreeMetadata::new("export-test")
            .with_description("Test export");

        let mut root = TreeNode::new("0001", "Root", 1)
            .with_content("Root content")
            .with_summary("Root summary");

        let child = TreeNode::new("0002", "Child", 2)
            .with_content("Child content")
            .with_summary("Child summary");

        root.add_child(child);

        let doc = TreeDocument::new(meta, vec![root]);

        let json = export_to_json(&doc, true).unwrap();
        assert!(json.contains("export-test"));
        assert!(json.contains("Test export"));
        assert!(json.contains("Root"));
        assert!(json.contains("Child"));
    }

    #[test]
    fn test_roundtrip() {
        // Import → Export → Import should preserve structure
        let doc1 = import_from_json(SAMPLE_PAGEINDEX_JSON).unwrap();
        let json = export_to_json(&doc1, false).unwrap();
        let doc2 = import_from_json(&json).unwrap();

        assert_eq!(doc1.metadata.doc_name, doc2.metadata.doc_name);
        assert_eq!(doc1.structure.len(), doc2.structure.len());
        assert_eq!(doc1.total_nodes(), doc2.total_nodes());

        // Check specific nodes
        assert_eq!(doc1.structure[0].title, doc2.structure[0].title);
        assert_eq!(
            doc1.structure[1].children.len(),
            doc2.structure[1].children.len()
        );
    }

    #[test]
    fn test_import_without_node_ids() {
        // PageIndex sometimes omits node_ids
        let json = r#"{
            "doc_name": "no-ids",
            "structure": [
                {
                    "title": "Section A",
                    "text": "Content A",
                    "nodes": [
                        {
                            "title": "Sub A.1",
                            "text": "Content A.1",
                            "nodes": []
                        }
                    ]
                }
            ]
        }"#;

        let doc = import_from_json(json).unwrap();
        assert_eq!(doc.structure[0].node_id, "0001");
        assert_eq!(doc.structure[0].children[0].node_id, "0002");
    }

    #[test]
    fn test_import_with_page_numbers() {
        let json = r#"{
            "doc_name": "pdf-doc",
            "structure": [
                {
                    "title": "Chapter 1",
                    "node_id": "0001",
                    "start_page": 1,
                    "end_page": 10,
                    "nodes": []
                }
            ]
        }"#;

        let doc = import_from_json(json).unwrap();
        assert_eq!(doc.metadata.source_format, "pdf");
        assert_eq!(doc.structure[0].start_page, Some(1));
        assert_eq!(doc.structure[0].end_page, Some(10));
    }

    #[test]
    fn test_import_empty_structure() {
        let json = r#"{"doc_name": "empty", "structure": []}"#;
        let result = import_from_json(json);
        assert!(result.is_err());
    }

    #[test]
    fn test_import_invalid_json() {
        let result = import_from_json("not valid json");
        assert!(result.is_err());
    }

    #[test]
    fn test_prefix_summary_mapping() {
        // PageIndex uses prefix_summary for branch nodes
        let json = r#"{
            "doc_name": "summary-test",
            "structure": [
                {
                    "title": "Branch",
                    "node_id": "0001",
                    "prefix_summary": "Branch overview",
                    "nodes": [
                        {
                            "title": "Leaf",
                            "node_id": "0002",
                            "summary": "Leaf summary",
                            "nodes": []
                        }
                    ]
                }
            ]
        }"#;

        let doc = import_from_json(json).unwrap();
        // prefix_summary should be mapped to summary
        assert_eq!(
            doc.structure[0].summary.as_deref(),
            Some("Branch overview")
        );
        assert_eq!(
            doc.structure[0].children[0].summary.as_deref(),
            Some("Leaf summary")
        );
    }

    #[test]
    fn test_level_assignment() {
        let json = r#"{
            "doc_name": "levels",
            "structure": [
                {
                    "title": "L1",
                    "nodes": [
                        {
                            "title": "L2",
                            "nodes": [
                                {
                                    "title": "L3",
                                    "nodes": []
                                }
                            ]
                        }
                    ]
                }
            ]
        }"#;

        let doc = import_from_json(json).unwrap();
        assert_eq!(doc.structure[0].level, 1);
        assert_eq!(doc.structure[0].children[0].level, 2);
        assert_eq!(doc.structure[0].children[0].children[0].level, 3);
    }
}
