#[derive(Debug, PartialEq)]
pub enum Directive {
    Format(FormatDirective),
}

#[derive(Debug, PartialEq)]
pub enum FormatDirective {
    Skip,
    SkipFile,
    Table,
}

/// Parse a comment directive
///
/// These take the form:
///
/// ```text
/// # <category>: <command> <optional-argument>
/// ```
///
/// Such as:
///
/// ```text
/// # fmt: skip
/// # fmt: skip file
/// # fmt: table
/// # fmt: align-right
/// # lint: skip
/// # lint: skip rule
/// ```
///
/// Note that directives are applied to the node they are attached to.
///
/// `text` should be single line but we don't check for this. A potential usage
/// of this function is to iterate over a document line by line to scan for a
/// directive.
pub fn parse_comment_directive(text: &str) -> Option<Directive> {
    let text = trim_comment(text)?;

    let (category, text) = text.split_once(':')?;
    let text = text.trim();

    #[allow(clippy::single_match)]
    match category {
        "fmt" => parse_format_directive(text),
        _ => None,
    }
}

// This returns `None` if `text` is not a comment
fn trim_comment(text: &str) -> Option<&str> {
    let text = text.strip_prefix('#')?;
    let text = text.trim_start_matches('#');
    let text = text.trim_start();
    Some(text)
}

#[inline]
fn parse_format_directive(text: &str) -> Option<Directive> {
    match text {
        "skip" => Some(Directive::Format(FormatDirective::Skip)),
        "skip file" => Some(Directive::Format(FormatDirective::SkipFile)),
        "table" => Some(Directive::Format(FormatDirective::Table)),
        _ => None,
    }
}

// https://github.com/posit-dev/air/issues/219
// Should be called only on the first line in a block of comments.
pub fn parse_special_skip_file(text: &str) -> Option<Directive> {
    let text = trim_comment(text)?;

    // Convention used in the R community (Roxygen, Rcpp, etc)
    if text.starts_with("Generated by") {
        return Some(Directive::Format(FormatDirective::SkipFile));
    }

    None
}

#[cfg(test)]
mod test {
    use crate::Directive;
    use crate::parse_comment_directive;
    use crate::parse_special_skip_file;

    #[test]
    fn test_format_directive() {
        let format_skip = Some(Directive::Format(crate::FormatDirective::Skip));
        let format_skip_file = Some(Directive::Format(crate::FormatDirective::SkipFile));
        let format_table = Some(Directive::Format(crate::FormatDirective::Table));

        // Must have leading `#`
        assert!(parse_comment_directive("fmt: skip").is_none());

        // Must have `:`
        assert!(parse_comment_directive("# fmt skip").is_none());

        // `:` must be right after `fmt`
        assert!(parse_comment_directive("# fmt : skip").is_none());

        // Can't have extra spaces between `skip file`
        assert!(parse_comment_directive("# fmt: skip  file").is_none());

        // Can't have unrelated leading text
        assert!(parse_comment_directive("# please fmt: skip").is_none());

        // Can't have unrelated trailing text
        assert!(parse_comment_directive("# fmt: skip please").is_none());
        assert!(parse_comment_directive("# fmt: skip file please").is_none());

        assert_eq!(parse_comment_directive("# fmt: skip"), format_skip);
        assert_eq!(parse_comment_directive("#fmt:skip"), format_skip);
        assert_eq!(parse_comment_directive("#  fmt:  skip  "), format_skip);

        // Can have multiple `#`
        assert_eq!(parse_comment_directive("## fmt: skip"), format_skip);

        assert_eq!(
            parse_comment_directive("# fmt: skip file"),
            format_skip_file
        );
        assert_eq!(parse_comment_directive("#fmt:skip file"), format_skip_file);
        assert_eq!(
            parse_comment_directive("#  fmt:  skip file"),
            format_skip_file
        );

        assert!(parse_comment_directive("# Generated by:").is_none());
        assert_eq!(parse_special_skip_file("# Generated by:"), format_skip_file);

        assert_eq!(parse_comment_directive("# fmt: table"), format_table);
    }
}
