use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use ulid::Ulid;

use crate::{state::FileId, vfs::VPathBuf};

/// Top-level `%%` root attributes.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct RootAttributes {
    /// Unique ID of this page.
    /// Required for the page to be shown in feeds.
    #[serde(default)]
    pub id: Option<String>,

    /// Template to use for generating the page.
    /// Defaults to `_tree.hbs`.
    #[serde(default)]
    pub template: Option<String>,

    /// Title of the generated .html page.
    ///
    /// The page's tree path is used if empty.
    #[serde(default)]
    pub title: String,

    /// Page icon used in indexes.
    /// This is an emoji name, such as `page` (default).
    #[serde(default = "default_icon")]
    pub icon: String,

    /// ID of picture attached to the page, to be used as a thumbnail.
    #[serde(default)]
    pub thumbnail: Option<Picture>,

    /// Additional scripts to load into to the page.
    /// These are relative to the /static/js directory.
    #[serde(default)]
    pub scripts: Vec<String>,

    /// Additional styles to load into to the page.
    /// These are relative to the /static/css directory.
    #[serde(default)]
    pub styles: Vec<String>,

    /// Visibility of a page in the parent page's index.
    #[serde(default)]
    pub visibility: Visibility,

    /// The page's timestamps. These are automatically populated if a page has at least one branch
    /// with an ID that includes a timestamp.
    #[serde(default)]
    pub timestamps: Option<Timestamps>,

    /// Tags to assign to this page.
    #[serde(default)]
    pub tags: Vec<String>,
}

/// A picture reference.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Picture {
    /// ID of the picture.
    pub id: String,

    /// Optional alt text.
    #[serde(default)]
    pub alt: Option<String>,
}

/// Visibility of a page.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum Visibility {
    #[default]
    Public,
    /// Hidden from the parent page's index.
    Private,
}

/// Timestamps for a page.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct Timestamps {
    /// When the page was created. By default, this is the timestamp of the least recent branch.
    pub created: DateTime<Utc>,
    /// When the page was last updated. By default, this is the timestamp of the most recent branch.
    pub updated: DateTime<Utc>,
}

fn default_icon() -> String {
    String::from("page")
}

/// Branch attributes.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)]
pub struct Attributes {
    /// Unique identifier of the branch.
    ///
    /// Note that this must be unique to the _entire_ site, not just a single tree.
    /// This is because trees may be embedded within each other using [`Content::Link`].
    #[serde(default)]
    pub id: String,

    /// Redirect old and deleted listed in the list to this branch.
    ///
    /// This can be used to keep links permanent even in case the structure of the treehouse changes.
    #[serde(default)]
    pub redirect_here: Vec<String>,

    /// Controls how the block should be presented.
    #[serde(default)]
    pub content: Content,

    /// Do not persist the branch in localStorage.
    #[serde(default)]
    pub do_not_persist: bool,

    /// Strings of extra CSS class names to include in the generated HTML.
    #[serde(default)]
    pub classes: Classes,

    /// Enable `mini_template` templating in this branch.
    #[serde(default)]
    pub template: bool,

    /// Publishing stage; if `Draft`, the branch is invisible unless treehouse is compiled in
    /// debug mode.
    #[serde(default)]
    pub stage: Stage,

    /// List of extra spells to cast on the branch.
    #[serde(default)]
    pub cast: String,

    /// In feeds, specifies the list of tags to attach to an entry.
    /// This only has an effect on top-level branches.
    #[serde(default)]
    pub tags: Vec<String>,
}

/// Parses the timestamp out of a branch ID.
/// Returns `None` if the ID does not contain a timestamp.
pub fn timestamp_from_id(id: &str) -> Option<DateTime<Utc>> {
    Ulid::from_string(id)
        .ok()
        .as_ref()
        .map(Ulid::timestamp_ms)
        .and_then(|ms| DateTime::from_timestamp_millis(ms as i64))
}

impl Attributes {
    pub fn timestamp(&self) -> Option<DateTime<Utc>> {
        timestamp_from_id(&self.id)
    }
}

/// Controls for block content presentation.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Content {
    /// Children are stored inline in the block. Nothing special happens.
    #[default]
    Inline,

    /// Link to another tree.
    ///
    /// When JavaScript is enabled, the tree's roots will be embedded inline into the branch and
    /// loaded lazily.
    ///
    /// Without JavaScript, the tree will be linked with an `<a>` element.
    ///
    /// The string provided as an argument is relative to the `content` root and should not contain
    /// any file extensions. For example, to link to `content/my-article.tree`,
    /// use `content.link = "my-article"`.
    ///
    /// Note that `Link` branches must not contain any children. If a `Link` branch does contain
    /// children, an `attribute`-type error is raised.
    Link(VPathBuf),

    /// Valid link to another tree.
    /// This replaces `Content::Link` during semantic analysis.
    #[serde(skip)]
    ResolvedLink(FileId),
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)]
pub struct Classes {
    /// Classes to append to the branch itself (<li data-cast="b">).
    #[serde(default)]
    pub branch: String,

    /// Classes to append to the branch's <ul> element containing its children.
    #[serde(default)]
    pub branch_children: String,
}

/// Publish stage of a branch.
///
/// Draft branches are not included in release builds of treehouse. In debug builds, they are also
/// marked with an extra "draft" before the content.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)]
pub enum Stage {
    #[default]
    Public,
    Draft,
}
