//! implement tag behaviour similar to [ansible](https://docs.ansible.com/ansible/latest/user_guide/playbooks_tags.html)

use std::collections::BTreeSet;

pub(crate) fn tags_match<'a>(
    got: impl Iterator<Item = &'a str>,
    skip_tags: impl Iterator<Item = &'a str>,
    tags: impl Iterator<Item = &'a str>,
) -> bool {
    let got = got.collect::<BTreeSet<_>>();
    let skip_tags = skip_tags.collect::<BTreeSet<_>>();
    let tags = tags.collect::<BTreeSet<_>>();

    if got.contains(TAG_ALWAYS) && skip_tags.contains(TAG_ALWAYS) {
        return false;
    }
    if got.contains(TAG_NEVER) && !tags.contains(TAG_NEVER) {
        return false;
    }
    if got.intersection(&skip_tags).count() > 0 {
        return false;
    }
    got.contains(TAG_ALWAYS)
        || tags.contains(TAG_ALL)
        || got.intersection(&tags).count() > 0
        || (!got.is_empty() && tags.contains(TAG_TAGGED))
        || (got.is_empty() && tags.contains(TAG_UNTAGGED))
}

const TAG_ALL: &str = "all";
const TAG_ALWAYS: &str = "always";
const TAG_NEVER: &str = "never";
const TAG_TAGGED: &str = "tagged";
const TAG_UNTAGGED: &str = "untagged";

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn tags_match_false() {
        let cases = vec![
            (
                // `--tags all` matches any tag except "never"
                vec!["never"],
                vec![],
                vec!["all"],
            ),
            (
                // `--tags all` matches any tag except "never"
                vec!["cat", "dog", "never"],
                vec![],
                vec!["all"],
            ),
            (
                // `--skip-tags always --tags all` skips the "always" tag
                vec!["always"],
                vec!["always"],
                vec!["all"],
            ),
            (
                // `--skip-tags always --tags all` skips the "always" tag
                vec!["cat", "dog", "always"],
                vec!["always"],
                vec!["all"],
            ),
            (
                // `--tags cat` matches the "cat" tag
                vec!["mouse", "dog"],
                vec![],
                vec!["cat"],
            ),
            (
                // `--skip-tags cat --tags all` skips the "cat" tag
                vec!["cat", "dog"],
                vec!["cat"],
                vec!["all"],
            ),
            (
                // `--tags tagged` matches any non-empty list of tags without "never"
                vec![],
                vec![],
                vec!["tagged"],
            ),
            (
                // `--tags tagged` matches any non-empty list of tags without "never"
                vec!["cat", "dog", "never"],
                vec![],
                vec!["tagged"],
            ),
            (
                // `--tags untagged` matches any empty list of tags
                vec!["cat", "dog"],
                vec![],
                vec!["untagged"],
            ),
        ];
        for (got, skip_tags, tags) in cases {
            assert!(
                !tags_match(
                    got.iter().copied(),
                    skip_tags.iter().copied(),
                    tags.iter().copied()
                ),
                "got={got:?} skip_tags={skip_tags:?} tags={tags:?}",
            );
        }
    }

    #[test]
    fn tags_match_true() {
        let cases = vec![
            (
                // `--tags all` matches any tag except "never"
                vec!["cat", "dog"],
                vec![],
                vec!["all"],
            ),
            (
                // `--tags all` matches any tag except "never"
                vec!["always"],
                vec![],
                vec!["all"],
            ),
            (
                // `--tags never` runs the "never" tag
                vec!["never"],
                vec![],
                vec!["never"],
            ),
            (
                // `--tags never` runs the "never" tag
                vec!["cat", "dog", "never"],
                vec![],
                vec!["never"],
            ),
            (
                // `--tags cat` matches the "cat" tag
                vec!["cat", "dog"],
                vec![],
                vec!["cat"],
            ),
            (
                // `--skip-tags cat --tags all` matches anything but the "cat" tag
                vec!["mouse", "dog"],
                vec!["cat"],
                vec!["all"],
            ),
            (
                // `--tags tagged` matches any non-empty list of tags without "never"
                vec!["cat", "dog"],
                vec![],
                vec!["tagged"],
            ),
            (
                // `--tags untagged` matches any empty list of tags
                vec![],
                vec![],
                vec!["untagged"],
            ),
        ];
        for (got, skip_tags, tags) in cases {
            assert!(
                tags_match(
                    got.iter().copied(),
                    skip_tags.iter().copied(),
                    tags.iter().copied(),
                ),
                "got={got:?} skip_tags={skip_tags:?} tags={tags:?}",
            );
        }
    }
}
