package tsoptions

import (
	"strings"

	"github.com/microsoft/typescript-go/internal/tspath"
	"github.com/microsoft/typescript-go/internal/vfs/vfsmatch"
)

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
	}

	excludeMatcher := vfsmatch.NewSpecMatcher(exclude, comparePathsOptions.CurrentDirectory, vfsmatch.UsageExclude, comparePathsOptions.UseCaseSensitiveFileNames)

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

	var recursiveKeys []string

	for _, file := range include {
		spec := tspath.NormalizePath(tspath.CombinePaths(comparePathsOptions.CurrentDirectory, file))
		if excludeMatcher != nil && excludeMatcher.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)
}

// 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 {
	// Find the first occurrence of a wildcard character
	firstWildcard := strings.IndexAny(spec, "*?")
	if firstWildcard != -1 {
		// Find the last directory separator before the wildcard
		lastSepBeforeWildcard := strings.LastIndexByte(spec[:firstWildcard], tspath.DirectorySeparator)
		if lastSepBeforeWildcard != -1 {
			path := spec[:lastSepBeforeWildcard]
			lastDirectorySeparatorIndex := strings.LastIndexByte(spec, tspath.DirectorySeparator)

			// Determine if this should be watched recursively:
			// recursive if the wildcard appears in a directory segment (not just the final file segment)
			recursive := firstWildcard < lastDirectorySeparatorIndex

			return &wildcardDirectoryMatch{
				Key:       toCanonicalKey(path, useCaseSensitiveFileNames),
				Path:      path,
				Recursive: recursive,
			}
		}
	}

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

	return nil
}
