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

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub enum PageAggregateSource {
    KernelProjection,
    CompatMetaContentJoin,
    Fixture,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PageAggregateProjection {
    pub schema: String,
    pub projection_version: u32,
    pub source: PageAggregateSource,
    pub page_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub parent_id: Option<String>,
    pub title: String,
    #[serde(default)]
    pub path: Vec<String>,
    #[serde(default)]
    pub sidebar_tree_membership: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body_ref: Option<String>,
    pub layout_options: Value,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub updated_at: Option<String>,
    pub identity: PageIdentity,
    pub head: PageHead,
    pub layout: PageLayout,
    pub body: PageBody,
    pub tree: PageTree,
    pub stats: PageStats,
}

impl PageAggregateProjection {
    pub const SCHEMA: &'static str = "mnote.page_aggregate.v1";
    pub const VERSION: u32 = 1;

    pub fn source_label(&self) -> &'static str {
        match self.source {
            PageAggregateSource::KernelProjection => "rust-kernel",
            PageAggregateSource::CompatMetaContentJoin => "compat-join",
            PageAggregateSource::Fixture => "fixture",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PageIdentity {
    pub document_id: String,
    pub workspace_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PageHead {
    pub title: String,
    pub updated_at: Value,
    pub permissions: PagePermissions,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PagePermissions {
    pub read_only: bool,
    pub disable_download: bool,
    pub disable_copy: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PageLayout {
    pub page_options: PageOptions,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PageOptions {
    pub wide_layout: bool,
    pub small_text: bool,
    pub layout_density: String,
    pub show_heading_numbers: bool,
    pub show_toc: bool,
    pub show_structure: bool,
    pub protect_editing: bool,
    pub show_word_count: bool,
    pub collapse_backlinks: bool,
    pub page_font: String,
    pub hide_child_pages: bool,
    pub show_block_ref_count: bool,
    pub hide_title_header: bool,
    pub embed_default_block_id: Value,
}

impl Default for PageOptions {
    fn default() -> Self {
        Self {
            wide_layout: false,
            small_text: false,
            layout_density: "normal".into(),
            show_heading_numbers: false,
            show_toc: false,
            show_structure: false,
            protect_editing: false,
            show_word_count: true,
            collapse_backlinks: false,
            page_font: "default".into(),
            hide_child_pages: false,
            show_block_ref_count: false,
            hide_title_header: false,
            embed_default_block_id: Value::Null,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{PageBody, PageOptions};
    use serde_json::json;

    #[test]
    fn page_options_default_disables_heading_numbers() {
        assert!(!PageOptions::default().show_heading_numbers);
    }

    #[test]
    fn page_body_accepts_legacy_payload_without_block_projection() {
        let body: PageBody = serde_json::from_value(json!({
            "content": [],
            "revision": 7,
            "conflictDetectionKey": "doc_1:7"
        }))
        .expect("legacy page body should deserialize");

        assert_eq!(body.revision, json!(7));
        assert_eq!(body.block_projection_version, 0);
        assert_eq!(body.block_document, json!(null));
        assert_eq!(body.projection_source, "");
        assert_eq!(body.file_version, json!(null));
        assert_eq!(body.attachment_refs, json!(null));
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PageBody {
    pub content: Value,
    pub revision: Value,
    pub conflict_detection_key: Value,
    #[serde(default)]
    pub file_version: Value,
    #[serde(default)]
    pub block_document: Value,
    #[serde(default)]
    pub block_projection_version: u32,
    #[serde(default)]
    pub projection_source: String,
    #[serde(default)]
    pub attachment_refs: Value,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PageTree {
    pub page_subtree: Value,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct PageStats {
    pub word_count: u64,
    pub character_count: u64,
    pub block_count: u64,
    pub todo_total: u64,
    pub todo_done: u64,
}
