use super::{Lint, LintKind, Linter, Suggestion};
use crate::TokenStringExt;
use crate::{Document, Token, TokenKind};

#[derive(Debug, Default)]
pub struct Spaces;

impl Linter for Spaces {
    fn lint(&mut self, document: &Document) -> Vec<Lint> {
        let mut output = Vec::new();

        for sentence in document.iter_sentences() {
            for space_idx in sentence.iter_space_indices() {
                if space_idx == 0 {
                    continue;
                }

                let space = &sentence[space_idx];

                let TokenKind::Space(count) = space.kind else {
                    panic!("The space iterator should only return spaces.")
                };

                if count > 1 {
                    output.push(Lint {
                        span: space.span,
                        lint_kind: LintKind::Formatting,
                        suggestions: vec![Suggestion::ReplaceWith(vec![' '])],
                        message: format!(
                            "There are {count} spaces where there should be only one."
                        ),
                        priority: 15,
                    })
                }
            }

            if matches!(
                sentence,
                [
                    ..,
                    Token {
                        kind: TokenKind::Word(_),
                        ..
                    },
                    Token {
                        kind: TokenKind::Space(_),
                        ..
                    },
                    Token {
                        kind: TokenKind::Punctuation(_),
                        ..
                    }
                ]
            ) && let Some(space) = sentence.get_rel(-2)
            {
                output.push(Lint {
                    span: space.span,
                    lint_kind: LintKind::Formatting,
                    suggestions: vec![Suggestion::Remove],
                    message: "Unnecessary space at the end of the sentence.".to_owned(),
                    priority: 63,
                })
            }
        }

        output
    }

    fn description(&self) -> &'static str {
        "Words should be separated by at most one space."
    }
}

#[cfg(test)]
mod tests {
    use super::Spaces;
    use crate::linting::tests::{assert_lint_count, assert_no_lints};

    #[test]
    fn detects_space_before_period() {
        let source = "There is a space at the end of this sentence .";

        assert_lint_count(source, Spaces, 1)
    }

    #[test]
    fn allows_period_without_space() {
        let source = "There isn't a space at the end of this sentence.";

        assert_lint_count(source, Spaces, 0)
    }

    #[test]
    fn ignores_french_spacing() {
        assert_no_lints(
            "This is a short sentence.  This is another short sentence.",
            Spaces,
        );
    }
}
