// MODIFIED IN THIS FORK: 2025-09-12 Konstantin Ulitin e3893948a2061ee66b3bc9e3df22590aba344872 support ideGetTypeProperty command
// MODIFIED IN THIS FORK: 2025-08-25 Aleksei Berezkin 8aa1bd88535b085be08c607ccbecbe4e6fbfeeea Fixed fork after refactoring in upstream
// MODIFIED IN THIS FORK: 2025-08-14 Aleksei Berezkin 57b2402e912f23f4a1345039460fac83895ce6c8 WEB-74070 Migrated away from private defaultProjectFinder.findOrCreateProject
// MODIFIED IN THIS FORK: 2025-08-05 Aleksei Berezkin a9368494725330485924ed5ff3db447110dab0b3 WEB-74070 Projects cache cleanup
// MODIFIED IN THIS FORK: 2025-07-25 Aleksei Berezkin 1b4d405531446e2f049414861843068ad508d68d WEB-74070 Do not reopen a file if it's already opened
// MODIFIED IN THIS FORK: 2025-07-24 Aleksei Berezkin 5567c01219bf93fad344a80691da5df021923d10 WEB-74070 Supported the method IdeGetResolvedSignature
// MODIFIED IN THIS FORK: 2025-05-21 Piotr Tomiak dfd337df3e64d6fb2bc18c46bf4de05bcbea5a64 Support WebStorm types in LSP mode
package lsp

import (
	"context"
	"errors"
	"runtime/debug"

	"github.com/microsoft/typescript-go/internal/collections"
	"github.com/microsoft/typescript-go/internal/lsp/lsproto"
	"github.com/microsoft/typescript-go/internal/project"
)

var ProjectNotFoundError = errors.New("ProjectNotFoundError")

func (s *Server) jbHandleCustomTsServerCommand(ctx context.Context, req *lsproto.RequestMessage) error {
	// !!! most likely not needed once support is fully implemented
	defer func() {
		if r := recover(); r != nil {
			stack := debug.Stack()
			s.Log("panic running jbHandleCustomTsServerCommand:", r, string(stack))
			s.sendResult(req.ID, &map[string]string{})
		}
	}()
	params := req.Params.(*lsproto.JbHandleCustomTsServerCommandParams)
	switch params.IdeCommand {
	case lsproto.IdeCommandGetElementType:
		{
			args := params.Arguments.(*lsproto.GetElementTypeArguments)
			project, file, err := s.GetProjectAndFileName(args.ProjectFileName, args.File, ctx)
			if err != nil {
				s.jbSendResult(req.ID, nil, err)
				return nil
			}

			element, err := IdeGetTypeOfElement(ctx, project, file, &args.Range, args.ForceReturnType, args.TypeRequestKind)
			s.jbSendResult(req.ID, element, err)
		}
	case lsproto.IdeCommandGetSymbolType:
		{
			args := params.Arguments.(*lsproto.GetSymbolTypeArguments)
			symbolType, err := IdeGetSymbolType(ctx, args.IdeProjectId, uint64(args.IdeTypeCheckerId), args.SymbolId)
			s.jbSendResult(req.ID, symbolType, err)
		}
	case lsproto.IdeCommandGetTypeProperties:
		{
			args := params.Arguments.(*lsproto.GetTypePropertiesArguments)
			typeProperties, err := IdeGetTypeProperties(ctx, args.IdeProjectId, uint64(args.IdeTypeCheckerId), args.TypeId)
			s.jbSendResult(req.ID, typeProperties, err)
		}
	case lsproto.IdeCommandGetTypeProperty:
		{
			args := params.Arguments.(*lsproto.GetTypePropertyArguments)
			symbol, err := IdeGetTypeProperty(ctx, args.IdeProjectId, uint64(args.IdeTypeCheckerId), args.TypeId, args.PropertyName)
			s.jbSendResult(req.ID, symbol, err)
		}
	case lsproto.IdeCommandGetTypeText:
		{
			args := params.Arguments.(*lsproto.GetTypeTextArguments)
			typeText, err := IdeGetTypeText(ctx, args.IdeProjectId, uint64(args.IdeTypeCheckerId), args.TypeId, args.SymbolId, args.Flags)
			s.jbSendResult(req.ID, typeText, err)
		}

	case lsproto.IdeAreTypesMutuallyAssignable:
		{
			args := params.Arguments.(*lsproto.AreTypesMutuallyAssignableArguments)
			result, err := AreTypesMutuallyAssignable(ctx, args.IdeProjectId, uint64(args.IdeTypeCheckerId), args.Type1Id, args.Type2Id)
			s.jbSendResult(req.ID, result, err)
		}
	case lsproto.IdeGetResolvedSignature:
		{
			args := params.Arguments.(*lsproto.GetResolvedSignatureArguments)
			project, file, err := s.GetProjectAndFileName(args.ProjectFileName, args.File, ctx)
			if err != nil {
				s.jbSendResult(req.ID, nil, err)
				return nil
			}

			result, err := GetResolvedSignature(ctx, project, file, args.Range)
			s.jbSendResult(req.ID, result, err)
		}
	case lsproto.IdeCommandGetCompletionSymbols:
		{
			args := params.Arguments.(*lsproto.GetCompletionSymbolsArguments)
			proj, file, err := s.GetProjectAndFileName(args.ProjectFileName, args.File, ctx)
			if err != nil {
				s.jbSendResult(req.ID, nil, err)
				return nil
			}

			snapshot, release := s.session.Snapshot()
			defer release()
			result, err := IdeGetCompletionSymbols(ctx, proj, snapshot, file, args.Position)
			s.jbSendResult(req.ID, result, err)
		}
	}
	snapshot, release := s.session.Snapshot()
	defer release()

	CleanupProjectsCache(append(snapshot.ProjectCollection.Projects(), GetAllSelfManagedProjects(s, ctx)...), s.logger)
	return nil
}

func (s *Server) GetProjectAndFileName(
	projectFileNameUri *lsproto.DocumentUri,
	fileUri lsproto.DocumentUri,
	ctx context.Context,
) (*project.Project, string, error) {
	file := fileUri.FileName()

	snapshot, release := s.session.Snapshot()
	released := false
	releaseOnce := func() {
		if !released {
			release()
			released = true
		}
	}
	defer releaseOnce()

	if projectFileNameUri != nil {
		projectFileName := projectFileNameUri.FileName()

		if IsSelfManagedProject(projectFileName) {
			if p := GetOrCreateSelfManagedProjectForFile(s, projectFileName, file, ctx); p != nil {
				return p, file, nil
			}
		}

		for _, p := range snapshot.ProjectCollection.Projects() {
			if p.Name() == projectFileName && p.GetProgram().GetSourceFile(file) != nil {
				return p, file, nil
			}
		}

		if p := GetOrCreateSelfManagedProjectForFile(s, projectFileName, file, ctx); p != nil {
			return p, file, nil
		}
	}

	if p := snapshot.GetDefaultProject(fileUri); p != nil {
		return p, file, nil
	}

	releaseOnce()

	if _, err := s.session.GetLanguageService(ctx, fileUri); err == nil {
		// Get a fresh snapshot since GetLanguageService may have updated it
		newSnapshot, release := s.session.Snapshot()
		defer release()
		if p := newSnapshot.GetDefaultProject(fileUri); p != nil {
			return p, file, nil
		}
	}

	// No project found
	return nil, file, ProjectNotFoundError

	/*
		// Unstable API, skip so far

		canonicalFileName := tspath.GetCanonicalFileName(file, s.projectService.FS().UseCaseSensitiveFileNames())
		scriptInfo := s.projectService.DocumentStore().GetScriptInfoByPath(tspath.Path(canonicalFileName))
		if scriptInfo != nil {
			return scriptInfo.ContainingProjects()[0], file
		}

		// Default project - may be inferred
		// TODO - handle list of automatically opened files and close them automatically as well
		fileContents, ok := s.projectService.FS().ReadFile(file)
		if !ok {
			panic("Failed to read " + file)
		}
		scriptKind := core.GetScriptKindFromFileName(file)
		s.projectService.OpenFile(file, fileContents, scriptKind, file)
		_, proj = s.projectService.EnsureDefaultProjectForFile(file)

		return proj, file
	*/
}

func (s *Server) jbSendResult(id *lsproto.ID, result *collections.OrderedMap[string, interface{}], err error) {
	response := make(map[string]interface{})
	if err == nil {
		response["response"] = result
	} else {
		errorResponse := make(map[string]interface{})
		errorResponse["error"] = err.Error()
		response["response"] = errorResponse
	}
	s.sendResult(id, response)
}
