package ls

import (
	"context"

	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
)

func (l *LanguageService) getExportInfos(
	ctx context.Context,
	ch *checker.Checker,
	importingFile *ast.SourceFile,
	preferences *UserPreferences,
	exportMapKey ExportInfoMapKey,
) []*SymbolExportInfo {
	expInfoMap := NewExportInfoMap(l.GetProgram().GetGlobalTypingsCacheLocation())
	moduleCount := 0
	symbolNameMatch := func(symbolName string) bool {
		return symbolName == exportMapKey.SymbolName
	}
	forEachExternalModuleToImportFrom(
		ch,
		l.GetProgram(),
		preferences,
		// /*useAutoImportProvider*/ true,
		func(moduleSymbol *ast.Symbol, moduleFile *ast.SourceFile, ch *checker.Checker, isFromPackageJson bool) {
			if moduleCount = moduleCount + 1; moduleCount%100 == 0 && ctx.Err() != nil {
				return
			}
			if moduleFile == nil && moduleSymbol.Name != exportMapKey.AmbientModuleName {
				return
			}
			seenExports := collections.Set[string]{}
			defaultInfo := getDefaultLikeExportInfo(moduleSymbol, ch)
			var exportingModuleSymbol *ast.Symbol
			if defaultInfo != nil {
				exportingModuleSymbol = defaultInfo.exportingModuleSymbol
				// Note: I think we shouldn't actually see resolved module symbols here, but weird merges
				// can cause it to happen: see 'completionsImport_mergedReExport.ts'
				if isImportableSymbol(exportingModuleSymbol, ch) {
					expInfoMap.add(
						importingFile.Path(),
						exportingModuleSymbol,
						core.IfElse(defaultInfo.exportKind == ExportKindDefault, ast.InternalSymbolNameDefault, ast.InternalSymbolNameExportEquals),
						moduleSymbol,
						moduleFile,
						defaultInfo.exportKind,
						isFromPackageJson,
						ch,
						symbolNameMatch,
						nil,
					)
				}
			}
			ch.ForEachExportAndPropertyOfModule(moduleSymbol, func(exported *ast.Symbol, key string) {
				if exported != exportingModuleSymbol && isImportableSymbol(exported, ch) && seenExports.AddIfAbsent(key) {
					expInfoMap.add(
						importingFile.Path(),
						exported,
						key,
						moduleSymbol,
						moduleFile,
						ExportKindNamed,
						isFromPackageJson,
						ch,
						symbolNameMatch,
						nil,
					)
				}
			})
		})
	return expInfoMap.get(importingFile.Path(), ch, exportMapKey)
}

func (l *LanguageService) searchExportInfosForCompletions(
	ctx context.Context,
	ch *checker.Checker,
	importingFile *ast.SourceFile,
	preferences *UserPreferences,
	isForImportStatementCompletion bool,
	isRightOfOpenTag bool,
	isTypeOnlyLocation bool,
	lowerCaseTokenText string,
	action func([]*SymbolExportInfo, string, bool, ExportInfoMapKey) []*SymbolExportInfo,
) {
	symbolNameMatches := map[string]bool{}
	symbolNameMatch := func(symbolName string) bool {
		if !scanner.IsIdentifierText(symbolName, importingFile.LanguageVariant) {
			return false
		}
		if b, ok := symbolNameMatches[symbolName]; ok {
			return b
		}
		if isNonContextualKeyword(scanner.StringToToken(symbolName)) {
			symbolNameMatches[symbolName] = false
			return false
		}
		// Do not try to auto-import something with a lowercase first letter for a JSX tag
		firstChar := rune(symbolName[0])
		if isRightOfOpenTag && (firstChar < 'A' || firstChar > 'Z') {
			symbolNameMatches[symbolName] = false
			return false
		}

		symbolNameMatches[symbolName] = charactersFuzzyMatchInString(symbolName, lowerCaseTokenText)
		return symbolNameMatches[symbolName]
	}
	flagMatch := func(targetFlags ast.SymbolFlags) bool {
		if !isTypeOnlyLocation && !isForImportStatementCompletion && (targetFlags&ast.SymbolFlagsValue) == 0 {
			return false
		}
		if isTypeOnlyLocation && (targetFlags&(ast.SymbolFlagsModule|ast.SymbolFlagsType) == 0) {
			return false
		}
		return true
	}

	expInfoMap := NewExportInfoMap(l.GetProgram().GetGlobalTypingsCacheLocation())
	moduleCount := 0
	forEachExternalModuleToImportFrom(
		ch,
		l.GetProgram(),
		preferences,
		// /*useAutoImportProvider*/ true,
		func(moduleSymbol *ast.Symbol, moduleFile *ast.SourceFile, ch *checker.Checker, isFromPackageJson bool) {
			if moduleCount = moduleCount + 1; moduleCount%100 == 0 && ctx.Err() != nil {
				return
			}
			seenExports := collections.Set[string]{}
			defaultInfo := getDefaultLikeExportInfo(moduleSymbol, ch)
			// Note: I think we shouldn't actually see resolved module symbols here, but weird merges
			// can cause it to happen: see 'completionsImport_mergedReExport.ts'
			if defaultInfo != nil && isImportableSymbol(defaultInfo.exportingModuleSymbol, ch) {
				expInfoMap.add(
					importingFile.Path(),
					defaultInfo.exportingModuleSymbol,
					core.IfElse(defaultInfo.exportKind == ExportKindDefault, ast.InternalSymbolNameDefault, ast.InternalSymbolNameExportEquals),
					moduleSymbol,
					moduleFile,
					defaultInfo.exportKind,
					isFromPackageJson,
					ch,
					symbolNameMatch,
					flagMatch,
				)
			}
			var exportingModuleSymbol *ast.Symbol
			if defaultInfo != nil {
				exportingModuleSymbol = defaultInfo.exportingModuleSymbol
			}
			ch.ForEachExportAndPropertyOfModule(moduleSymbol, func(exported *ast.Symbol, key string) {
				if exported != exportingModuleSymbol && isImportableSymbol(exported, ch) && seenExports.AddIfAbsent(key) {
					expInfoMap.add(
						importingFile.Path(),
						exported,
						key,
						moduleSymbol,
						moduleFile,
						ExportKindNamed,
						isFromPackageJson,
						ch,
						symbolNameMatch,
						flagMatch,
					)
				}
			})
		})
	expInfoMap.search(
		ch,
		importingFile.Path(),
		/*preferCapitalized*/ isRightOfOpenTag,
		func(symbolName string, targetFlags ast.SymbolFlags) bool {
			return symbolNameMatch(symbolName) && flagMatch(targetFlags)
		},
		action,
	)
}
