pub mod one_more;
pub mod slugify;
pub mod templates;
pub mod word_count;

use anyhow::anyhow;
use askama::Template;
use serde::Deserialize;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;

pub fn output_writer(path: impl AsRef<Path>) -> Result<impl Write, std::io::Error> {
    let path = crate::generator::output_path(path);
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    let file = File::create(path)?;
    Ok(BufWriter::new(file))
}

pub fn output_rendered_template(
    template: &impl Template,
    file: impl AsRef<Path>,
) -> Result<(), std::io::Error> {
    let path = file.as_ref();
    let writer = output_writer(path)?;
    template.render_into(&mut FmtWriter(writer)).map_err(|e| {
        std::io::Error::new(
            std::io::ErrorKind::Other,
            format!("writing template {}: {}", path.display(), e),
        )
    })
}

struct FmtWriter<W: Write>(W);

impl<W: Write> std::fmt::Write for FmtWriter<W> {
    fn write_str(&mut self, s: &str) -> std::fmt::Result {
        self.0.write_all(s.as_bytes()).map_err(|_| std::fmt::Error)
    }

    fn write_char(&mut self, c: char) -> std::fmt::Result {
        let mut buf = [0u8; 4];
        c.encode_utf8(&mut buf);
        self.0.write_all(&buf).map_err(|_| std::fmt::Error)
    }

    fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::fmt::Result {
        self.0.write_fmt(args).map_err(|_| std::fmt::Error)
    }
}

pub fn from_frontmatter<'de, D: Deserialize<'de>>(
    contents: &'de str,
) -> anyhow::Result<(D, &'de str)> {
    let mut chars = contents.char_indices();
    for i in 0..=2 {
        if chars.next() != Some((i, '`')) {
            return Err(anyhow!("no frontmatter"));
        }
    }
    let mut seen_backticks = 0;
    let mut end_index = None;
    while let Some((idx, c)) = chars.next() {
        if c == '`' {
            seen_backticks += 1;
            if seen_backticks == 3 {
                end_index = Some(idx - 3);
                break;
            }
        } else {
            seen_backticks = 0;
        }
    }
    let end_index = end_index.ok_or(anyhow!("missing frontmatter end"))?;
    let frontmatter = &contents[3..=end_index];
    let deserialized = toml::from_str::<'de, D>(frontmatter)?;
    Ok((deserialized, chars.as_str()))
}
