//! Parse JSON frontmatter

use miette::{SourceOffset, SourceSpan};

use crate::Metadata;

/// JSON error.
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
pub enum Error {
    /// The JSON wasn't 'valid'.
    ///
    /// IE: the 'parser' couldn't count the braces properly.
    #[error("frontmatter is not valid json")]
    Invalid,
    /// Too many braces were counted.
    ///
    /// This was 'thrown' to prevent an 'endless' loop of parser.
    #[error("json frontmatter exceeded parse depth of 128 braces (come on :/)")]
    DepthExceeded,
    /// JSON parse error
    #[error(transparent)]
    Parse(ParseError),
}

/// JSON parse error
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
#[error("failed to deserialize toml frontmatter")]
pub struct ParseError {
    /// The 'source' of the frontmatter.
    #[source_code]
    src: String,
    /// The location where [`serde_json`] failed to parse.
    #[label("{err}")]
    location: SourceSpan,

    /// The error emitted.
    #[source]
    err: serde_json::Error,
}

/// Parse JSON frontmatter by counting the braces.
///
/// # Errors
///
/// This is will if the basic parser cannot count braces properly,
/// the brace count is too high (to prevent endless parsing),
/// or the contents failed to parse.
pub fn parse(data: &str) -> Result<(Option<Metadata>, &str), Error> {
    const MAX_DEPTH: usize = 128;

    let data = data.trim_start();

    if !data.starts_with('{') {
        return Err(Error::Invalid);
    }

    let mut depth = 0;

    let mut braces = 0;

    let mut is_in_string = false;
    let mut escape_next = false;

    let mut split_point = 0;

    for (i, ch) in data.char_indices() {
        if escape_next {
            escape_next = false;
            continue;
        }

        match ch {
            '"' if !is_in_string => {
                is_in_string = true;
            }
            '"' if is_in_string => {
                is_in_string = false;
            }
            '\\' if is_in_string => {
                escape_next = true;
            }
            '{' if !is_in_string => {
                depth += 1;

                braces += 1;

                if depth > MAX_DEPTH {
                    return Err(Error::DepthExceeded);
                }
            }
            '}' if !is_in_string => {
                braces -= 1;

                if depth > 0 {
                    depth = depth.saturating_sub(1);
                }

                if braces == 0 {
                    split_point = i;

                    break;
                }
            }
            _ => {}
        }
    }

    if braces != 0 {
        return Err(Error::Invalid);
    }

    let (frontmatter, body) = data.split_at(split_point);

    let parsed = serde_json::from_str(frontmatter).map_err(|err| {
        Error::Parse(ParseError {
            src: frontmatter.to_string(),
            location: SourceSpan::new(
                SourceOffset::from_location(frontmatter, err.line(), err.column()),
                1,
            ),
            err,
        })
    })?;

    Ok((Some(parsed), body))
}
