#!/usr/bin/env python3
# Copyright: 2026, Maximiliano Curia <maxy@debian.org>
#
# License: ISC
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#  .
#  THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
#  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
#  AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
#  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
#  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
#  OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
#  PERFORMANCE OF THIS SOFTWARE.

"""Contract of IgnoreMatcher.

Files-Excluded matching: an exact path pattern, a directory pattern excluding
its subtree via the ancestor walk, a glob, an unrelated path, and the empty-list
no-op. Gitignore matching: root and nested .gitignore files, directory patterns,
negation re-inclusion, and the use_gitignore=False gate.
"""

from decopy.fstree.ignore import IgnoreMatcher


def _matcher(files_excluded):
    return IgnoreMatcher(
        use_gitignore=False, files_excluded=tuple(files_excluded), root="/nonexistent"
    )


def _git_matcher(root, *, use_gitignore=True, files_excluded=()):
    return IgnoreMatcher(
        use_gitignore=use_gitignore,
        files_excluded=tuple(files_excluded),
        root=str(root),
    )


def test_exact_path_pattern_matches_only_that_path():
    matcher = _matcher(["foo/bar.c"])
    assert matcher.is_ignored("foo/bar.c", is_dir=False)
    assert not matcher.is_ignored("foo/bar.cx", is_dir=False)
    assert not matcher.is_ignored("foo/baz.c", is_dir=False)


def test_directory_pattern_excludes_its_subtree():
    matcher = _matcher(["vendor"])
    assert matcher.is_ignored("vendor", is_dir=True)
    assert matcher.is_ignored("vendor/lib/x.c", is_dir=False)
    assert not matcher.is_ignored("vendored/x.c", is_dir=False)


def test_glob_pattern_matches():
    matcher = _matcher(["a/*"])
    assert matcher.is_ignored("a/b.c", is_dir=False)
    assert matcher.is_ignored("a/b/c.c", is_dir=False)
    assert not matcher.is_ignored("b/c.c", is_dir=False)


def test_unrelated_path_is_not_ignored():
    matcher = _matcher(["foo/bar.c"])
    assert not matcher.is_ignored("src/main.c", is_dir=False)


def test_empty_files_excluded_matches_nothing():
    matcher = _matcher([])
    assert not matcher.is_ignored("anything/at/all.c", is_dir=False)
    assert not matcher.is_ignored("foo", is_dir=True)


def test_root_gitignore_file_and_dir_patterns(tmp_path):
    matcher = _git_matcher(tmp_path)
    matcher.add_gitignore("", ["*.log", "build/"])
    assert matcher.is_ignored("a.log", is_dir=False)
    assert not matcher.is_ignored("a.txt", is_dir=False)
    assert matcher.is_ignored("build", is_dir=True)
    assert matcher.is_ignored("build/x.o", is_dir=False)


def test_negation_reincludes(tmp_path):
    matcher = _git_matcher(tmp_path)
    matcher.add_gitignore("", ["*.log", "!keep.log"])
    assert matcher.is_ignored("a.log", is_dir=False)
    assert not matcher.is_ignored("keep.log", is_dir=False)


def test_nested_gitignore_only_affects_its_subtree(tmp_path):
    matcher = _git_matcher(tmp_path)
    matcher.add_gitignore("src", ["tmp/"])
    assert matcher.is_ignored("src/tmp", is_dir=True)
    assert matcher.is_ignored("src/tmp/x.c", is_dir=False)
    assert not matcher.is_ignored("tmp", is_dir=True)


def test_use_gitignore_false_disables_gitignore_not_files_excluded(tmp_path):
    matcher = _git_matcher(tmp_path, use_gitignore=False, files_excluded=["drop.c"])
    matcher.add_gitignore("", ["*.log"])
    assert not matcher.is_ignored("a.log", is_dir=False)
    assert matcher.is_ignored("drop.c", is_dir=False)


def test_git_info_exclude_loaded_at_construction(tmp_path):
    info = tmp_path / ".git" / "info"
    info.mkdir(parents=True)
    (info / "exclude").write_text("*.bak\n")
    matcher = _git_matcher(tmp_path)
    assert matcher.is_ignored("notes.bak", is_dir=False)
    assert not matcher.is_ignored("notes.txt", is_dir=False)


def test_git_as_file_is_skipped(tmp_path):
    (tmp_path / ".git").write_text("gitdir: /elsewhere\n")
    matcher = _git_matcher(tmp_path)
    assert not matcher.is_ignored("notes.bak", is_dir=False)
