pub mod commands;
use std::{fs, path::PathBuf};
use clap::{Parser, Subcommand};
use commands::{compile::Compile, init::Init, new_post::NewPost};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum BlogError {
    #[error("IO Error")]
    Io {
        error: std::io::Error,
        message: String,
    },
    #[error("Invalid Front Matter: {0}")]
    InvalidFrontMatter(String),
    #[error("Invalid File Name: {0}")]
    InvalidFileName(String),
    #[error("Invalid File Extension: {0}")]
    SimpleIoError(#[from] std::io::Error),
    #[error("Invalid State")]
    InvalidState {
        state: String,
        help: String,
        error: String,
    },
}
impl BlogError {
    pub fn io(error: std::io::Error, message: String) -> Self {
        BlogError::Io { error, message }
    }
}
type BlogResult<T> = Result<T, BlogError>;
pub trait Run {
    fn run(&self) -> BlogResult<()>;
}
#[derive(Debug, Parser)] #[command(name = "blog-rs", version, author, about = "A blog CLI")]
#[command(about = "Blogging Tool", long_about = None)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Commands,
}
impl Cli {
    pub fn run(&self) -> BlogResult<()> {
        self.command.run()
    }
}
#[derive(Debug, Subcommand)]
pub enum Commands {
    #[command(about = "Compile the blog")]
    Compile,
    #[command(about = "Initialize a new blog")]
    Init { path: String },
    #[command(about = "Create a new post")]
    Post { title: String },
}
impl Commands {
    pub fn run(&self) -> BlogResult<()> {
        match self {
            Commands::Compile => Compile.run(),
            Commands::Init { path } => Init.run(path),
            Commands::Post { title } => NewPost.run(title),
        }
    }
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Config {
    #[serde(default = "default_output")]
    pub output: PathBuf,
    #[serde(default = "default_template")]
    pub template: String,
    #[serde(default)]
    pub title: String,
}
fn default_output() -> PathBuf {
    PathBuf::from("static")
}
fn default_template() -> String {
    "default".to_string()
}
impl Config {
    pub(crate) fn template_dir(&self) -> PathBuf {
        PathBuf::from("templates").join(&self.template)
    }
    fn load_toml() -> BlogResult<Self> {
        let config = fs::read_to_string("config.toml").map_err(|e| BlogError::InvalidState {
            state: "config.toml".to_string(),
            help: "Run `blog-rs init` to create a new config".to_string(),
            error: e.to_string(),
        })?;
        toml::from_str(&config).map_err(|e| BlogError::InvalidState {
            state: "config.toml".to_string(),
            help: "Run `blog-rs init` to create a new config".to_string(),
            error: e.to_string(),
        })
    }
    pub fn load_posts() -> BlogResult<Vec<PathBuf>> {
        let dir = fs::read_dir("./posts")
            .map_err(|e| BlogError::io(e, "Unable to read posts directory".to_string()))?;
        let legible_posts: Vec<_> = dir
            .filter_map(|entry| {
                let entry = entry.ok()?;
                let path = entry.path();
                if path.is_file() {
                    Some(path)
                } else {
                    None
                }
            })
            .collect();
        Ok(legible_posts)
    }
}