package tsoptions

import (
	"regexp"
	"strings"

	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs"
	"github.com/dlclark/regexp2"
)

func getWildcardDirectories(include []string, exclude []string, comparePathsOptions tspath.ComparePathsOptions) map[string]bool {
	// We watch a directory recursively if it contains a wildcard anywhere in a directory segment
	// of the pattern:
	//
	//  /a/b/**/d   - Watch /a/b recursively to catch changes to any d in any subfolder recursively
	//  /a/b/*/d    - Watch /a/b recursively to catch any d in any immediate subfolder, even if a new subfolder is added
	//  /a/b        - Watch /a/b recursively to catch changes to anything in any recursive subfoler
	//
	// We watch a directory without recursion if it contains a wildcard in the file segment of
	// the pattern:
	//
	//  /a/b/*      - Watch /a/b directly to catch any new file
	//  /a/b/a?z    - Watch /a/b directly to catch any new file matching a?z

	if len(include) == 0 {
		return nil
	}

	rawExcludeRegex := vfs.GetRegularExpressionForWildcard(exclude, comparePathsOptions.CurrentDirectory, "exclude")
	var excludeRegex *regexp.Regexp
	if rawExcludeRegex != "" {
		options := ""
		if !comparePathsOptions.UseCaseSensitiveFileNames {
			options = "(?i)"
		}
		excludeRegex = regexp.MustCompile(options + rawExcludeRegex)
	}

	wildcardDirectories := make(map[string]bool)
	wildCardKeyToPath := make(map[string]string)

	var recursiveKeys []string

	for _, file := range include {
		spec := tspath.NormalizeSlashes(tspath.CombinePaths(comparePathsOptions.CurrentDirectory, file))
		if excludeRegex != nil && excludeRegex.MatchString(spec) {
			continue
		}

		match := getWildcardDirectoryFromSpec(spec, comparePathsOptions.UseCaseSensitiveFileNames)
		if match != nil {
			key := match.Key
			path := match.Path
			recursive := match.Recursive

			existingPath, existsPath := wildCardKeyToPath[key]
			var existingRecursive bool

			if existsPath {
				existingRecursive = wildcardDirectories[existingPath]
			}

			if !existsPath || (!existingRecursive && recursive) {
				pathToUse := path
				if existsPath {
					pathToUse = existingPath
				}
				wildcardDirectories[pathToUse] = recursive

				if !existsPath {
					wildCardKeyToPath[key] = path
				}

				if recursive {
					recursiveKeys = append(recursiveKeys, key)
				}
			}
		}

		// Remove any subpaths under an existing recursively watched directory
		for path := range wildcardDirectories {
			for _, recursiveKey := range recursiveKeys {
				key := toCanonicalKey(path, comparePathsOptions.UseCaseSensitiveFileNames)
				if key != recursiveKey && tspath.ContainsPath(recursiveKey, key, comparePathsOptions) {
					delete(wildcardDirectories, path)
				}
			}
		}
	}

	return wildcardDirectories
}

func toCanonicalKey(path string, useCaseSensitiveFileNames bool) string {
	if useCaseSensitiveFileNames {
		return path
	}
	return strings.ToLower(path)
}

// wildcardDirectoryPattern matches paths with wildcard characters
var wildcardDirectoryPattern = regexp2.MustCompile(`^[^*?]*(?=\/[^/]*[*?])`, 0)

// wildcardDirectoryMatch represents the result of a wildcard directory match
type wildcardDirectoryMatch struct {
	Key       string
	Path      string
	Recursive bool
}

func getWildcardDirectoryFromSpec(spec string, useCaseSensitiveFileNames bool) *wildcardDirectoryMatch {
	match, _ := wildcardDirectoryPattern.FindStringMatch(spec)
	if match != nil {
		// We check this with a few `Index` calls because it's more efficient than complex regex
		questionWildcardIndex := strings.Index(spec, "?")
		starWildcardIndex := strings.Index(spec, "*")
		lastDirectorySeparatorIndex := strings.LastIndexByte(spec, tspath.DirectorySeparator)

		// Determine if this should be watched recursively
		recursive := (questionWildcardIndex != -1 && questionWildcardIndex < lastDirectorySeparatorIndex) ||
			(starWildcardIndex != -1 && starWildcardIndex < lastDirectorySeparatorIndex)

		return &wildcardDirectoryMatch{
			Key:       toCanonicalKey(match.String(), useCaseSensitiveFileNames),
			Path:      match.String(),
			Recursive: recursive,
		}
	}

	if lastSepIndex := strings.LastIndexByte(spec, tspath.DirectorySeparator); lastSepIndex != -1 {
		lastSegment := spec[lastSepIndex+1:]
		if vfs.IsImplicitGlob(lastSegment) {
			path := tspath.RemoveTrailingDirectorySeparator(spec)
			return &wildcardDirectoryMatch{
				Key:       toCanonicalKey(path, useCaseSensitiveFileNames),
				Path:      path,
				Recursive: true,
			}
		}
	}

	return nil
}
