package ls

import (
	"github.com/microsoft/typescript-go/internal/ast"
	"github.com/microsoft/typescript-go/internal/compiler"
	"github.com/microsoft/typescript-go/internal/ls/autoimport"
	"github.com/microsoft/typescript-go/internal/ls/lsconv"
	"github.com/microsoft/typescript-go/internal/ls/lsutil"
	"github.com/microsoft/typescript-go/internal/lsp/lsproto"
	"github.com/microsoft/typescript-go/internal/sourcemap"
	"github.com/microsoft/typescript-go/internal/tspath"
	"github.com/microsoft/typescript-go/internal/vfs/vfsmatch"
)

type LanguageService struct {
	projectPath             tspath.Path
	host                    Host
	activeConfig            lsutil.UserPreferences
	program                 *compiler.Program
	converters              *lsconv.Converters
	documentPositionMappers map[string]*sourcemap.DocumentPositionMapper
}

func NewLanguageService(
	projectPath tspath.Path,
	program *compiler.Program,
	host Host,
	activeFile string,
) *LanguageService {
	return &LanguageService{
		projectPath:             projectPath,
		host:                    host,
		program:                 program,
		converters:              host.Converters(),
		activeConfig:            host.GetPreferences(activeFile),
		documentPositionMappers: map[string]*sourcemap.DocumentPositionMapper{},
	}
}

func (l *LanguageService) toPath(fileName string) tspath.Path {
	return tspath.ToPath(fileName, l.program.GetCurrentDirectory(), l.UseCaseSensitiveFileNames())
}

func (l *LanguageService) GetProgram() *compiler.Program {
	return l.program
}

func (l *LanguageService) UserPreferences() lsutil.UserPreferences {
	return l.activeConfig
}

func (l *LanguageService) FormatOptions() lsutil.FormatCodeSettings {
	return l.activeConfig.FormatCodeSettings
}

func (l *LanguageService) tryGetProgramAndFile(fileName string) (*compiler.Program, *ast.SourceFile) {
	program := l.GetProgram()
	file := program.GetSourceFile(fileName)
	return program, file
}

func (l *LanguageService) getProgramAndFile(documentURI lsproto.DocumentUri) (*compiler.Program, *ast.SourceFile) {
	fileName := documentURI.FileName()
	program, file := l.tryGetProgramAndFile(fileName)
	if file == nil {
		panic("file not found: " + fileName)
	}
	return program, file
}

func (l *LanguageService) GetDocumentPositionMapper(fileName string) *sourcemap.DocumentPositionMapper {
	d, ok := l.documentPositionMappers[fileName]
	if !ok {
		d = sourcemap.GetDocumentPositionMapper(l, fileName)
		l.documentPositionMappers[fileName] = d
	}
	return d
}

func (l *LanguageService) ReadFile(fileName string) (string, bool) {
	return l.host.ReadFile(fileName)
}

func (l *LanguageService) UseCaseSensitiveFileNames() bool {
	return l.host.UseCaseSensitiveFileNames()
}

func (l *LanguageService) GetECMALineInfo(fileName string) *sourcemap.ECMALineInfo {
	return l.host.GetECMALineInfo(fileName)
}

// getPreparedAutoImportView returns an auto-import view for the given file if the registry is prepared
// to provide up-to-date auto-imports for it. If not, it returns ErrNeedsAutoImports.
// If auto-imports are disabled via user preferences, it returns (nil, nil).
func (l *LanguageService) getPreparedAutoImportView(fromFile *ast.SourceFile) (*autoimport.View, error) {
	if l.UserPreferences().IncludeCompletionsForModuleExports.IsFalse() {
		return nil, nil
	}
	registry := l.host.AutoImportRegistry()
	if !registry.IsPreparedForImportingFile(fromFile.FileName(), l.projectPath, l.UserPreferences()) {
		return nil, ErrNeedsAutoImports
	}

	view := autoimport.NewView(registry, fromFile, l.projectPath, l.program, l.UserPreferences().ModuleSpecifierPreferences())
	return view, nil
}

// getCurrentAutoImportView returns an auto-import view for the given file, based on the current state
// of the auto-import registry, which may or may not be up-to-date.
func (l *LanguageService) getCurrentAutoImportView(fromFile *ast.SourceFile) *autoimport.View {
	return autoimport.NewView(
		l.host.AutoImportRegistry(),
		fromFile,
		l.projectPath,
		l.program,
		l.UserPreferences().ModuleSpecifierPreferences(),
	)
}

// Used for module specifier completions.
func (l *LanguageService) DirectoryExists(path string) bool {
	return l.host.DirectoryExists(path)
}

// Used for module specifier completions.
func (l *LanguageService) ReadDirectory(path string, extensions []string, includes []string) []string {
	return l.host.ReadDirectory(l.program.GetCurrentDirectory(), path, extensions, nil /*excludes*/, includes, vfsmatch.UnlimitedDepth)
}

func (l *LanguageService) GetDirectories(path string) []string {
	return l.host.GetDirectories(path)
}
