package main

import (
	"bufio"
	"os"
	"path/filepath"
	"strings"

	ignore "github.com/sabhiram/go-gitignore"
)

type gitignoreLayer struct {
	dir      string
	posRules *ignore.GitIgnore
	negRules *ignore.GitIgnore
}

type GitignoreStack struct {
	rootDir string
	layers  []gitignoreLayer
}

func NewGitignoreStack(rootDir string) *GitignoreStack {
	stack := &GitignoreStack{rootDir: rootDir}
	stack.loadAndPush(rootDir)
	return stack
}

func (s *GitignoreStack) Push(dir string) {
	s.loadAndPush(dir)
}

func (s *GitignoreStack) Pop() {
	if len(s.layers) > 0 {
		s.layers = s.layers[:len(s.layers)-1]
	}
}

func (s *GitignoreStack) IsIgnored(relPath string) bool {
	absPath := filepath.Join(s.rootDir, relPath)
	result := false

	for _, layer := range s.layers {
		childRel, err := filepath.Rel(layer.dir, absPath)
		if err != nil {
			continue
		}
		childRel = filepath.ToSlash(childRel)
		if strings.HasPrefix(childRel, "../") {
			continue
		}

		posMatch := layer.posRules != nil && (layer.posRules.MatchesPath(childRel) || layer.posRules.MatchesPath(childRel+"/"))
		negMatch := layer.negRules != nil && (layer.negRules.MatchesPath(childRel) || layer.negRules.MatchesPath(childRel+"/"))

		if negMatch {
			result = false
		} else if posMatch {
			result = true
		}
	}
	return result
}

func (s *GitignoreStack) ParentIgnored(dir string) bool {
	parent := filepath.Dir(dir)
	if parent == dir {
		return false
	}
	parentRel, err := filepath.Rel(s.rootDir, parent)
	if err != nil {
		return false
	}
	if parentRel == "." {
		return false
	}
	return s.IsIgnored(parentRel)
}

func (s *GitignoreStack) loadAndPush(dir string) {
	gitignorePath := filepath.Join(dir, ".gitignore")
	if _, err := os.Stat(gitignorePath); os.IsNotExist(err) {
		return
	}

	f, err := os.Open(gitignorePath)
	if err != nil {
		return
	}
	defer f.Close()

	var posLines, negLines []string
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		trimmed := strings.TrimSpace(line)
		if trimmed == "" || strings.HasPrefix(trimmed, "#") {
			continue
		}
		if strings.HasPrefix(trimmed, "!") {
			negLines = append(negLines, trimmed[1:])
		} else {
			posLines = append(posLines, trimmed)
		}
	}

	var posRules, negRules *ignore.GitIgnore
	if len(posLines) > 0 {
		posRules = ignore.CompileIgnoreLines(posLines...)
	}
	if len(negLines) > 0 {
		negRules = ignore.CompileIgnoreLines(negLines...)
	}

	s.layers = append(s.layers, gitignoreLayer{
		dir:      dir,
		posRules: posRules,
		negRules: negRules,
	})
}

func hasGitignore(dir string) bool {
	_, err := os.Stat(filepath.Join(dir, ".gitignore"))
	return err == nil
}

func isGitDir(path string) bool {
	return path == ".git" || strings.HasPrefix(path, ".git"+string(filepath.Separator))
}
