use chrono::NaiveDate;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Manifest {
    pub book: BookMetadata,
    pub output: OutputConfig,
    #[serde(default)]
    pub defaults: DefaultsConfig,
    #[serde(default)]
    pub sections: Vec<SectionDefinition>,
    #[serde(default)]
    pub entries: IndexMap<String, EntryDefinition>,
    #[serde(default)]
    pub link_rules: LinkRulesConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BookMetadata {
    pub title: String,
    #[serde(default)]
    pub author: Option<String>,
    #[serde(default = "default_language")]
    pub language: String,
    #[serde(default = "default_identifier")]
    pub identifier: String,
    #[serde(default)]
    pub description: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputConfig {
    pub path: String,
    #[serde(default)]
    pub cover_image: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DefaultsConfig {
    #[serde(default = "default_true")]
    pub fetch_images: bool,
    #[serde(default = "default_true")]
    pub normalize_substack_embeds: bool,
    #[serde(default)]
    pub processing: ProcessingDefaults,
    #[serde(default)]
    pub metadata: MetadataOverrides,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SectionDefinition {
    pub id: String,
    pub title: String,
    #[serde(default)]
    pub entries: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntryDefinition {
    pub source: SourceDefinition,
    #[serde(default)]
    pub title: Option<String>,
    #[serde(default)]
    pub metadata: MetadataOverrides,
    #[serde(default)]
    pub processing: ProcessingOverrides,
    #[serde(default)]
    pub toc: TocConfig,
    #[serde(default)]
    pub links: EntryLinkConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum SourceDefinition {
    Substack { url: String },
    Html { path: String },
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MetadataOverrides {
    #[serde(default)]
    pub author: Option<String>,
    #[serde(default)]
    pub published: Option<NaiveDate>,
    #[serde(default)]
    pub subtitle: Option<String>,
    #[serde(default)]
    pub summary: Option<String>,
    #[serde(default)]
    pub tags: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessingDefaults {
    #[serde(default = "default_true")]
    pub include_author: bool,
    #[serde(default = "default_true")]
    pub include_date: bool,
    #[serde(default = "default_true")]
    pub include_source_url: bool,
    #[serde(default)]
    pub skip_first_paragraphs: u32,
}

impl Default for ProcessingDefaults {
    fn default() -> Self {
        Self {
            include_author: true,
            include_date: true,
            include_source_url: true,
            skip_first_paragraphs: 0,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ProcessingOverrides {
    #[serde(default)]
    pub include_author: Option<bool>,
    #[serde(default)]
    pub include_date: Option<bool>,
    #[serde(default)]
    pub include_source_url: Option<bool>,
    #[serde(default)]
    pub skip_first_paragraphs: Option<u32>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TocConfig {
    #[serde(default)]
    pub title: Option<String>,
    #[serde(default)]
    pub hidden: bool,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EntryLinkConfig {
    #[serde(default)]
    pub mode: Option<BuildMode>,
    #[serde(default)]
    pub allow_to: Vec<String>,
    #[serde(default)]
    pub block_to: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "lowercase")]
pub enum BuildMode {
    #[default]
    Auto,
    Explicit,
    None,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "kebab-case")]
pub enum LinkMatchMode {
    #[default]
    CanonicalUrl,
    SourceUrl,
    Disabled,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LinkRule {
    pub from: Vec<String>,
    pub to: Vec<String>,
    #[serde(default)]
    pub match_mode: LinkMatchMode,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LinkRulesConfig {
    #[serde(default)]
    pub mode: BuildMode,
    #[serde(default = "default_true")]
    pub rewrite_external_substack_links: bool,
    #[serde(default = "default_true")]
    pub preserve_other_external_links: bool,
    #[serde(default)]
    pub rules: Vec<LinkRule>,
}

impl Default for LinkRulesConfig {
    fn default() -> Self {
        Self {
            mode: BuildMode::Auto,
            rewrite_external_substack_links: true,
            preserve_other_external_links: true,
            rules: Vec::new(),
        }
    }
}

fn default_true() -> bool {
    true
}

fn default_language() -> String {
    "en".to_string()
}

fn default_identifier() -> String {
    format!("urn:uuid:{}", uuid::Uuid::new_v4())
}
