package project_test

import (
	"context"
	"io/fs"
	"maps"
	"slices"
	"strings"
	"testing"

	"github.com/microsoft/typescript-go/internal/bundled"
	"github.com/microsoft/typescript-go/internal/core"
	"github.com/microsoft/typescript-go/internal/ls/lsutil"
	"github.com/microsoft/typescript-go/internal/lsp/lsproto"
	"github.com/microsoft/typescript-go/internal/project"
	"github.com/microsoft/typescript-go/internal/testutil/projecttestutil"
	"github.com/microsoft/typescript-go/internal/tspath"
	"github.com/microsoft/typescript-go/internal/vfs/vfstest"
	"gotest.tools/v3/assert"
)

func TestSession(t *testing.T) {
	t.Parallel()
	if !bundled.Embedded {
		t.Skip("bundled files are not embedded")
	}

	defaultFiles := map[string]any{
		"/home/projects/TS/p1/tsconfig.json": `{
			"compilerOptions": {
				"noLib": true,
				"module": "nodenext",
				"strict": true
			},
			"include": ["src"]
		}`,
		"/home/projects/TS/p1/src/index.ts": `import { x } from "./x";`,
		"/home/projects/TS/p1/src/x.ts":     `export const x = 1;`,
		"/home/projects/TS/p1/config.ts":    `let x = 1, y = 2;`,
	}

	t.Run("DidOpenFile", func(t *testing.T) {
		t.Parallel()
		t.Run("create configured project", func(t *testing.T) {
			t.Parallel()
			session, _ := projecttestutil.Setup(defaultFiles)
			snapshot := session.Snapshot()
			assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 0)

			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			snapshot = session.Snapshot()
			assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)

			configuredProject := snapshot.ProjectCollection.ConfiguredProject(tspath.Path("/home/projects/ts/p1/tsconfig.json"))
			assert.Assert(t, configuredProject != nil)

			// Get language service to access the program
			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()
			assert.Assert(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil)
			assert.Equal(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts").Text(), "export const x = 1;")
		})

		t.Run("create inferred project", func(t *testing.T) {
			t.Parallel()
			session, _ := projecttestutil.Setup(defaultFiles)

			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/config.ts", 1, defaultFiles["/home/projects/TS/p1/config.ts"].(string), lsproto.LanguageKindTypeScript)

			// Find tsconfig, load, notice config.ts is not included, create inferred project
			snapshot := session.Snapshot()
			assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 2)

			// Should have both configured project (for tsconfig.json) and inferred project
			configuredProject := snapshot.ProjectCollection.ConfiguredProject(tspath.Path("/home/projects/ts/p1/tsconfig.json"))
			inferredProject := snapshot.ProjectCollection.InferredProject()
			assert.Assert(t, configuredProject != nil)
			assert.Assert(t, inferredProject != nil)
		})

		t.Run("inferred project for in-memory files", func(t *testing.T) {
			t.Parallel()
			session, _ := projecttestutil.Setup(defaultFiles)

			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/config.ts", 1, defaultFiles["/home/projects/TS/p1/config.ts"].(string), lsproto.LanguageKindTypeScript)
			session.DidOpenFile(context.Background(), "untitled:Untitled-1", 1, "x", lsproto.LanguageKindTypeScript)
			session.DidOpenFile(context.Background(), "untitled:Untitled-2", 1, "y", lsproto.LanguageKindTypeScript)

			snapshot := session.Snapshot()

			assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
			assert.Assert(t, snapshot.ProjectCollection.InferredProject() != nil)
		})

		t.Run("inferred project JS file", func(t *testing.T) {
			t.Parallel()
			jsFiles := map[string]any{
				"/home/projects/TS/p1/index.js": `import { x } from "./x";`,
			}
			session, _ := projecttestutil.Setup(jsFiles)

			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/index.js", 1, jsFiles["/home/projects/TS/p1/index.js"].(string), lsproto.LanguageKindJavaScript)

			snapshot := session.Snapshot()
			assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)

			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/index.js")
			assert.NilError(t, err)
			program := ls.GetProgram()
			assert.Assert(t, program.GetSourceFile("/home/projects/TS/p1/index.js") != nil)
		})
	})

	t.Run("watchChange and didOpen in same batch rebuilds program", func(t *testing.T) {
		t.Parallel()
		files := map[string]any{
			"/home/projects/TS/p1/tsconfig.json": `{
				"compilerOptions": {
					"noLib": true,
					"strict": true
				}
			}`,
			"/home/projects/TS/p1/src/a.ts": "export const a = 1;\n",
			"/home/projects/TS/p1/src/b.ts": "export const b = 1;\n",
		}
		session, utils := projecttestutil.Setup(files)
		oldContent := files["/home/projects/TS/p1/src/a.ts"].(string)

		// Open b.ts to create the project; a.ts is included via tsconfig.
		session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/b.ts", 1, files["/home/projects/TS/p1/src/b.ts"].(string), lsproto.LanguageKindTypeScript)

		// Verify a.ts is in the program with the original content.
		ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/b.ts")
		assert.NilError(t, err)
		assert.Equal(t, ls.GetProgram().GetSourceFile("/home/projects/TS/p1/src/a.ts").Text(), oldContent)

		// Modify a.ts on disk (simulate a build tool or git checkout).
		newContent := "export const a = 2;\nexport const extra = true;\n"
		err = utils.FS().WriteFile("/home/projects/TS/p1/src/a.ts", newContent)
		assert.NilError(t, err)

		// Queue a watch event for the disk change (not flushed yet).
		session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
			{Type: lsproto.FileChangeTypeChanged, Uri: "file:///home/projects/TS/p1/src/a.ts"},
		})

		// Open a.ts in the editor—flushes both watch event and didOpen together.
		// Before the fix, processChanges would discard the watch event,
		// leaving the project with a stale SourceFile and a mismatched line map.
		session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/a.ts", 1, newContent, lsproto.LanguageKindTypeScript)

		// The program's SourceFile must reflect the overlay (new) content.
		ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/a.ts")
		assert.NilError(t, err)
		assert.Equal(t, ls.GetProgram().GetSourceFile("/home/projects/TS/p1/src/a.ts").Text(), newContent)
	})

	t.Run("DidChangeFile", func(t *testing.T) {
		t.Parallel()
		t.Run("update file and program", func(t *testing.T) {
			t.Parallel()
			session, _ := projecttestutil.Setup(defaultFiles)

			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript)

			lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts")
			assert.NilError(t, err)
			programBefore := lsBefore.GetProgram()

			session.DidChangeFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{
				{
					Partial: new(lsproto.TextDocumentContentChangePartial{
						Range: lsproto.Range{
							Start: lsproto.Position{
								Line:      0,
								Character: 17,
							},
							End: lsproto.Position{
								Line:      0,
								Character: 18,
							},
						},
						Text: "2",
					}),
				},
			})

			lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts")
			assert.NilError(t, err)
			programAfter := lsAfter.GetProgram()

			// Program should change due to the file content change
			assert.Check(t, programAfter != programBefore)
			assert.Equal(t, programAfter.GetSourceFile("/home/projects/TS/p1/src/x.ts").Text(), "export const x = 2;")
		})

		t.Run("update untitled file", func(t *testing.T) {
			t.Parallel()
			session, _ := projecttestutil.Setup(defaultFiles)

			session.DidOpenFile(context.Background(), "untitled:Untitled-1", 1, "let x = 1;", lsproto.LanguageKindTypeScript)

			lsBefore, err := session.GetLanguageService(context.Background(), "untitled:Untitled-1")
			assert.NilError(t, err)
			programBefore := lsBefore.GetProgram()
			untitledFileName := lsproto.DocumentUri("untitled:Untitled-1").FileName()
			assert.Equal(t, programBefore.GetSourceFile(untitledFileName).Text(), "let x = 1;")

			session.DidChangeFile(context.Background(), "untitled:Untitled-1", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{
				{
					Partial: new(lsproto.TextDocumentContentChangePartial{
						Range: lsproto.Range{
							Start: lsproto.Position{
								Line:      0,
								Character: 8,
							},
							End: lsproto.Position{
								Line:      0,
								Character: 9,
							},
						},
						Text: "2",
					}),
				},
			})

			lsAfter, err := session.GetLanguageService(context.Background(), "untitled:Untitled-1")
			assert.NilError(t, err)
			programAfter := lsAfter.GetProgram()

			assert.Check(t, programAfter != programBefore)
			assert.Equal(t, programAfter.GetSourceFile(untitledFileName).Text(), "let x = 2;")
		})

		t.Run("unchanged source files are reused", func(t *testing.T) {
			t.Parallel()
			session, _ := projecttestutil.Setup(defaultFiles)

			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript)

			lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts")
			assert.NilError(t, err)
			programBefore := lsBefore.GetProgram()
			indexFileBefore := programBefore.GetSourceFile("/home/projects/TS/p1/src/index.ts")

			session.DidChangeFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{
				{
					Partial: new(lsproto.TextDocumentContentChangePartial{
						Range: lsproto.Range{
							Start: lsproto.Position{
								Line:      0,
								Character: 0,
							},
							End: lsproto.Position{
								Line:      0,
								Character: 0,
							},
						},
						Text: ";",
					}),
				},
			})

			lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts")
			assert.NilError(t, err)
			programAfter := lsAfter.GetProgram()

			// Unchanged file should be reused
			assert.Equal(t, programAfter.GetSourceFile("/home/projects/TS/p1/src/index.ts"), indexFileBefore)
		})

		t.Run("change can pull in new files", func(t *testing.T) {
			t.Parallel()
			files := maps.Clone(defaultFiles)
			files["/home/projects/TS/p1/y.ts"] = `export const y = 2;`
			session, _ := projecttestutil.Setup(files)

			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			// Verify y.ts is not initially in the program
			lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			programBefore := lsBefore.GetProgram()
			assert.Check(t, programBefore.GetSourceFile("/home/projects/TS/p1/y.ts") == nil)

			session.DidChangeFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{
				{
					Partial: new(lsproto.TextDocumentContentChangePartial{
						Range: lsproto.Range{
							Start: lsproto.Position{
								Line:      0,
								Character: 0,
							},
							End: lsproto.Position{
								Line:      0,
								Character: 0,
							},
						},
						Text: `import { y } from "../y";\n`,
					}),
				},
			})

			lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			programAfter := lsAfter.GetProgram()

			// y.ts should now be included in the program
			assert.Assert(t, programAfter.GetSourceFile("/home/projects/TS/p1/y.ts") != nil)
		})

		t.Run("single-file change followed by config change reloads program", func(t *testing.T) {
			t.Parallel()
			files := maps.Clone(defaultFiles)
			files["/home/projects/TS/p1/tsconfig.json"] = `{
				"compilerOptions": {
					"noLib": true,
					"module": "nodenext",
					"strict": true
				},
				"include": ["src/index.ts"]
			}`
			session, utils := projecttestutil.Setup(files)

			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			programBefore := lsBefore.GetProgram()
			assert.Equal(t, len(programBefore.GetSourceFiles()), 2)

			session.DidChangeFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{
				{
					Partial: new(lsproto.TextDocumentContentChangePartial{
						Range: lsproto.Range{
							Start: lsproto.Position{
								Line:      0,
								Character: 0,
							},
							End: lsproto.Position{
								Line:      0,
								Character: 0,
							},
						},
						Text: "\n",
					}),
				},
			})

			err = utils.FS().WriteFile("/home/projects/TS/p1/tsconfig.json", `{
				"compilerOptions": {
					"noLib": true,
					"module": "nodenext",
					"strict": true
				},
				"include": ["./**/*"]
			}`)
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeChanged,
					Uri:  "file:///home/projects/TS/p1/tsconfig.json",
				},
			})

			lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			programAfter := lsAfter.GetProgram()
			assert.Equal(t, len(programAfter.GetSourceFiles()), 3)
		})
	})

	t.Run("DidCloseFile", func(t *testing.T) {
		t.Parallel()
		t.Run("Configured projects", func(t *testing.T) {
			t.Parallel()
			t.Run("delete a file, close it, recreate it", func(t *testing.T) {
				t.Parallel()
				files := maps.Clone(defaultFiles)
				session, utils := projecttestutil.Setup(files)

				session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, files["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript)
				session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

				assert.NilError(t, utils.FS().Remove("/home/projects/TS/p1/src/x.ts"))

				session.DidCloseFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts")
				ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
				assert.NilError(t, err)
				program := ls.GetProgram()
				assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts") == nil)

				err = utils.FS().WriteFile("/home/projects/TS/p1/src/x.ts", "")
				assert.NilError(t, err)

				session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, "", lsproto.LanguageKindTypeScript)

				ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts")
				assert.NilError(t, err)
				program = ls.GetProgram()
				assert.Assert(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil)
				assert.Equal(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts").Text(), "")
			})
		})

		t.Run("Inferred projects", func(t *testing.T) {
			t.Parallel()
			t.Run("delete a file, close it, recreate it", func(t *testing.T) {
				t.Parallel()
				files := maps.Clone(defaultFiles)
				delete(files, "/home/projects/TS/p1/tsconfig.json")
				session, utils := projecttestutil.Setup(files)

				session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, files["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript)
				session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

				err := utils.FS().Remove("/home/projects/TS/p1/src/x.ts")
				assert.NilError(t, err)

				session.DidCloseFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts")

				ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
				assert.NilError(t, err)
				program := ls.GetProgram()
				assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts") == nil)

				err = utils.FS().WriteFile("/home/projects/TS/p1/src/x.ts", "")
				assert.NilError(t, err)

				session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, "", lsproto.LanguageKindTypeScript)

				ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts")
				assert.NilError(t, err)
				program = ls.GetProgram()
				assert.Assert(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil)
				assert.Equal(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts").Text(), "")
			})

			t.Run("close untitled file", func(t *testing.T) {
				t.Parallel()
				session, _ := projecttestutil.Setup(defaultFiles)

				session.DidOpenFile(context.Background(), "untitled:Untitled-1", 1, "let x = 1;", lsproto.LanguageKindTypeScript)
				session.DidCloseFile(context.Background(), "untitled:Untitled-1")
				session.DidOpenFile(context.Background(), "untitled:Untitled-2", 1, "", lsproto.LanguageKindTypeScript)
			})
		})
	})

	t.Run("DidSaveFile", func(t *testing.T) {
		t.Parallel()
		t.Run("save event first", func(t *testing.T) {
			t.Parallel()
			session, _ := projecttestutil.Setup(defaultFiles)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			snapshot := session.Snapshot()
			assert.Equal(t, snapshot.ID(), uint64(1))

			session.DidSaveFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeChanged,
					Uri:  "file:///home/projects/TS/p1/src/index.ts",
				},
			})

			session.WaitForBackgroundTasks()
			snapshot = session.Snapshot()
			// We didn't need a snapshot change, but the session overlays should be updated.
			assert.Equal(t, snapshot.ID(), uint64(1))

			// Open another file to force a snapshot update so we can see the changes.
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript)
			snapshot = session.Snapshot()
			assert.Equal(t, snapshot.GetFile("/home/projects/TS/p1/src/index.ts").MatchesDiskText(), true)
		})

		t.Run("watch event first", func(t *testing.T) {
			t.Parallel()
			session, _ := projecttestutil.Setup(defaultFiles)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			snapshot := session.Snapshot()
			assert.Equal(t, snapshot.ID(), uint64(1))

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeChanged,
					Uri:  "file:///home/projects/TS/p1/src/index.ts",
				},
			})
			session.DidSaveFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts")

			session.WaitForBackgroundTasks()
			snapshot = session.Snapshot()
			// We didn't need a snapshot change, but the session overlays should be updated.
			assert.Equal(t, snapshot.ID(), uint64(1))

			// Open another file to force a snapshot update so we can see the changes.
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript)
			snapshot = session.Snapshot()
			assert.Equal(t, snapshot.GetFile("/home/projects/TS/p1/src/index.ts").MatchesDiskText(), true)
		})
	})

	t.Run("Source file sharing", func(t *testing.T) {
		t.Parallel()
		t.Run("projects with similar options share source files", func(t *testing.T) {
			t.Parallel()
			files := maps.Clone(defaultFiles)
			files["/home/projects/TS/p2/tsconfig.json"] = `{
				"compilerOptions": {
					"noLib": true,
					"module": "nodenext",
					"strict": true,
					"noCheck": true
				}
			}`
			files["/home/projects/TS/p2/src/index.ts"] = `import { x } from "../../p1/src/x";`
			session, _ := projecttestutil.Setup(files)

			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p2/src/index.ts", 1, files["/home/projects/TS/p2/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			snapshot := session.Snapshot()
			assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 2)

			ls1, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program1 := ls1.GetProgram()

			ls2, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p2/src/index.ts")
			assert.NilError(t, err)
			program2 := ls2.GetProgram()

			assert.Equal(
				t,
				program1.GetSourceFile("/home/projects/TS/p1/src/x.ts"),
				program2.GetSourceFile("/home/projects/TS/p1/src/x.ts"),
			)
		})

		t.Run("projects with different options do not share source files", func(t *testing.T) {
			t.Parallel()
			files := maps.Clone(defaultFiles)
			files["/home/projects/TS/p2/tsconfig.json"] = `{
				"compilerOptions": {
					"noLib": true,
					"module": "nodenext",
					"strict": true,
					"moduleDetection": "auto"
				},
				"include": ["src"]
			}`
			files["/home/projects/TS/p2/src/index.ts"] = `import { x } from "../../p1/src/x";`
			session, _ := projecttestutil.Setup(files)

			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p2/src/index.ts", 1, files["/home/projects/TS/p2/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			snapshot := session.Snapshot()
			assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 2)

			ls1, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program1 := ls1.GetProgram()

			ls2, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p2/src/index.ts")
			assert.NilError(t, err)
			program2 := ls2.GetProgram()

			x1 := program1.GetSourceFile("/home/projects/TS/p1/src/x.ts")
			x2 := program2.GetSourceFile("/home/projects/TS/p1/src/x.ts")
			assert.Assert(t, x1 != nil && x2 != nil)
			assert.Assert(t, x1 != x2)
		})
	})

	t.Run("DidChangeWatchedFiles", func(t *testing.T) {
		t.Parallel()

		t.Run("change open file", func(t *testing.T) {
			t.Parallel()
			files := maps.Clone(defaultFiles)
			session, utils := projecttestutil.Setup(files)

			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, files["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			programBefore := lsBefore.GetProgram()

			err = utils.FS().WriteFile("/home/projects/TS/p1/src/x.ts", `export const x = 2;`)
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeChanged,
					Uri:  "file:///home/projects/TS/p1/src/x.ts",
				},
			})

			lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			// Program should remain the same since the file is open and changes are handled through DidChangeTextDocument
			assert.Equal(t, programBefore, lsAfter.GetProgram())
		})

		t.Run("change closed program file", func(t *testing.T) {
			t.Parallel()
			files := maps.Clone(defaultFiles)
			session, utils := projecttestutil.Setup(files)

			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			programBefore := lsBefore.GetProgram()

			err = utils.FS().WriteFile("/home/projects/TS/p1/src/x.ts", `export const x = 2;`)
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeChanged,
					Uri:  "file:///home/projects/TS/p1/src/x.ts",
				},
			})

			lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			assert.Check(t, lsAfter.GetProgram() != programBefore)
		})

		t.Run("change program file not in tsconfig root files", func(t *testing.T) {
			t.Parallel()
			for _, workspaceDir := range []string{"/", "/home/projects/TS/p1", "/somewhere/else/entirely"} {
				t.Run("workspaceDir="+strings.ReplaceAll(workspaceDir, "/", "_"), func(t *testing.T) {
					t.Parallel()
					files := map[string]any{
						"/home/projects/TS/p1/tsconfig.json": `{
							"compilerOptions": {
								"noLib": true,
								"module": "nodenext",
								"strict": true
							},
							"files": ["src/index.ts"]
						}`,
						"/home/projects/TS/p1/src/index.ts": `import { x } from "../../x";`,
						"/home/projects/TS/x.ts":            `export const x = 1;`,
					}

					session, utils := projecttestutil.SetupWithOptions(files, &project.SessionOptions{
						CurrentDirectory:   workspaceDir,
						DefaultLibraryPath: bundled.LibPath(),
						TypingsLocation:    projecttestutil.TestTypingsLocation,
						PositionEncoding:   lsproto.PositionEncodingKindUTF8,
						WatchEnabled:       true,
						LoggingEnabled:     true,
					})
					session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
					lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
					assert.NilError(t, err)
					programBefore := lsBefore.GetProgram()
					session.WaitForBackgroundTasks()

					assert.Check(t, utils.WatchesFile("/home/projects/ts/x.ts"))

					err = utils.FS().WriteFile("/home/projects/TS/x.ts", `export const x = 2;`)
					assert.NilError(t, err)

					session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
						{
							Type: lsproto.FileChangeTypeChanged,
							Uri:  "file:///home/projects/TS/x.ts",
						},
					})

					lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
					assert.NilError(t, err)
					assert.Check(t, lsAfter.GetProgram() != programBefore)
				})
			}
		})

		t.Run("change config file", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true,
						"strict": false
					}
				}`,
				"/home/projects/TS/p1/src/x.ts": `export declare const x: number | undefined;`,
				"/home/projects/TS/p1/src/index.ts": `
					import { x } from "./x";
					let y: number = x;`,
			}

			session, utils := projecttestutil.Setup(files)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0)

			err = utils.FS().WriteFile("/home/projects/TS/p1/tsconfig.json", `{
				"compilerOptions": {
					"noLib": false,
					"strict": true
				}
			}`)
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeChanged,
					Uri:  "file:///home/projects/TS/p1/tsconfig.json",
				},
			})

			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1)
		})

		t.Run("delete explicitly included file", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true
					},
					"files": ["src/index.ts", "src/x.ts"]
				}`,
				"/home/projects/TS/p1/src/x.ts":     `export declare const x: number | undefined;`,
				"/home/projects/TS/p1/src/index.ts": `import { x } from "./x";`,
			}
			session, utils := projecttestutil.Setup(files)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()
			assert.Check(t, slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/x.ts"))
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0)

			err = utils.FS().Remove("/home/projects/TS/p1/src/x.ts")
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeDeleted,
					Uri:  "file:///home/projects/TS/p1/src/x.ts",
				},
			})

			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			// File name is still in the command line, was explicitly included
			assert.Check(t, slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/x.ts"))
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1)
			assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts") == nil)

			// Open file to trigger cleanup
			session.DidOpenFile(context.Background(), "untitled:Untitled-1", 1, "", lsproto.LanguageKindTypeScript)
			snapshot := session.Snapshot()
			assert.Check(t, snapshot.GetFile("/home/projects/TS/p1/src/x.ts") == nil)
		})

		t.Run("delete wildcard included file", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true
					},
					"include": ["src"]
				}`,
				"/home/projects/TS/p1/src/index.ts": `let x = 2;`,
				"/home/projects/TS/p1/src/x.ts":     `let y = x;`,
			}
			session, utils := projecttestutil.Setup(files)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, files["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript)

			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()
			assert.Check(t, slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/index.ts"))
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/x.ts"))), 0)

			err = utils.FS().Remove("/home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeDeleted,
					Uri:  "file:///home/projects/TS/p1/src/index.ts",
				},
			})

			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			// File name is gone from the command line, was originally included via wildcard
			assert.Check(t, !slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/index.ts"))
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/x.ts"))), 1)

			// Open file to trigger cleanup
			session.DidOpenFile(context.Background(), "untitled:Untitled-1", 1, "", lsproto.LanguageKindTypeScript)
			snapshot := session.Snapshot()
			assert.Check(t, snapshot.GetFile("/home/projects/TS/p1/src/index.ts") == nil)
		})

		t.Run("delete directory with wildcard included files", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true
					},
					"include": ["src"]
				}`,
				"/home/projects/TS/p1/src/index.ts": `import { x } from "./sub/x";`,
				"/home/projects/TS/p1/src/sub/x.ts": `export const x = 1;`,
			}
			session, utils := projecttestutil.Setup(files)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()
			assert.Check(t, slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/sub/x.ts"))
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0)

			// Delete the entire subdirectory from the file system.
			err = utils.FS().Remove("/home/projects/TS/p1/src/sub")
			assert.NilError(t, err)

			// When a directory is deleted, the client typically sends a single deletion
			// event for the directory itself. Because the registered glob pattern includes
			// file extensions (e.g. **/*.{ts,...}), the directory path does not match
			// and the event is filtered out, so the server is never notified.
			// Simulate this by sending a delete event for the directory URI.
			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeDeleted,
					Uri:  "file:///home/projects/TS/p1/src/sub",
				},
			})

			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			// The directory was deleted, so the file should no longer be in the program.
			assert.Check(t, !slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/sub/x.ts"))
			// The import should now be an error since the module is missing.
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1)
		})

		t.Run("delete directory with program-only files", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true
					},
					"files": ["src/index.ts"]
				}`,
				"/home/projects/TS/p1/src/index.ts": `import { x } from "./sub/x";`,
				"/home/projects/TS/p1/src/sub/x.ts": `export const x = 1;`,
			}
			session, utils := projecttestutil.Setup(files)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()
			assert.Check(t, slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/index.ts"))
			// x.ts is not in "files" but is pulled in via the import.
			assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/sub/x.ts") != nil)
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0)

			// Delete the entire subdirectory from the file system.
			err = utils.FS().Remove("/home/projects/TS/p1/src/sub")
			assert.NilError(t, err)

			// Send a delete event for the directory URI.
			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeDeleted,
					Uri:  "file:///home/projects/TS/p1/src/sub",
				},
			})

			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			// The directory was deleted, so the file should no longer be resolvable.
			assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/sub/x.ts") == nil)
			// The import should now be an error since the module is missing.
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1)
		})

		t.Run("delete sibling folder schedules diagnostics refresh", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true
					},
					"files": ["index.ts"]
				}`,
				"/home/projects/TS/p1/index.ts": `import { content } from "./f/content";

export const value = content;`,
				"/home/projects/TS/p1/f/content.ts": `export const content = 1;`,
			}
			session, utils := projecttestutil.Setup(files)
			contentURI := lsproto.DocumentUri("file:///home/projects/TS/p1/f/content.ts")
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/index.ts", 1, files["/home/projects/TS/p1/index.ts"].(string), lsproto.LanguageKindTypeScript)
			session.DidOpenFile(context.Background(), contentURI, 1, files["/home/projects/TS/p1/f/content.ts"].(string), lsproto.LanguageKindTypeScript)

			_, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/index.ts")
			assert.NilError(t, err)
			session.WaitForBackgroundTasks()

			baselineRefreshCount := len(utils.Client().RefreshDiagnosticsCalls())

			err = utils.FS().Remove("/home/projects/TS/p1/f")
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeDeleted,
					Uri:  "file:///home/projects/TS/p1/f",
				},
			})
			session.DidCloseFile(context.Background(), contentURI)
			session.WaitForBackgroundTasks()

			refreshCount := len(utils.Client().RefreshDiagnosticsCalls())
			assert.Assert(t, refreshCount > baselineRefreshCount,
				"expected RefreshDiagnostics to be called after deleting /home/projects/TS/p1/f, got %d calls (baseline %d)",
				refreshCount, baselineRefreshCount)
		})

		t.Run("delete sibling folder schedules diagnostics refresh after opening third file", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true
					},
					"files": ["index.ts", "third.ts"]
				}`,
				"/home/projects/TS/p1/index.ts": `import { content } from "./f/content";

export const value = content;`,
				"/home/projects/TS/p1/f/content.ts": `export const content = 1;`,
				"/home/projects/TS/p1/third.ts":     `export const third = 3;`,
			}
			session, utils := projecttestutil.Setup(files)
			contentURI := lsproto.DocumentUri("file:///home/projects/TS/p1/f/content.ts")
			thirdURI := lsproto.DocumentUri("file:///home/projects/TS/p1/third.ts")
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/index.ts", 1, files["/home/projects/TS/p1/index.ts"].(string), lsproto.LanguageKindTypeScript)
			session.DidOpenFile(context.Background(), contentURI, 1, files["/home/projects/TS/p1/f/content.ts"].(string), lsproto.LanguageKindTypeScript)

			_, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/index.ts")
			assert.NilError(t, err)
			session.WaitForBackgroundTasks()

			baselineRefreshCount := len(utils.Client().RefreshDiagnosticsCalls())

			err = utils.FS().Remove("/home/projects/TS/p1/f")
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeDeleted,
					Uri:  "file:///home/projects/TS/p1/f",
				},
			})
			session.DidOpenFile(context.Background(), thirdURI, 1, files["/home/projects/TS/p1/third.ts"].(string), lsproto.LanguageKindTypeScript)
			session.WaitForBackgroundTasks()

			refreshCount := len(utils.Client().RefreshDiagnosticsCalls())
			assert.Assert(t, refreshCount > baselineRefreshCount,
				"expected RefreshDiagnostics to be called after deleting /home/projects/TS/p1/f and opening /home/projects/TS/p1/third.ts, got %d calls (baseline %d)",
				refreshCount, baselineRefreshCount)
		})

		t.Run("create explicitly included file", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true
					},
					"files": ["src/index.ts", "src/y.ts"]
				}`,
				"/home/projects/TS/p1/src/index.ts": `import { y } from "./y";`,
			}
			session, utils := projecttestutil.Setup(files)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()

			// Initially should have an error because y.ts is missing
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1)

			// Add the missing file
			err = utils.FS().WriteFile("/home/projects/TS/p1/src/y.ts", `export const y = 1;`)
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeCreated,
					Uri:  "file:///home/projects/TS/p1/src/y.ts",
				},
			})

			// Error should be resolved
			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0)
			assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/y.ts") != nil)
		})

		t.Run("create failed lookup location", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true
					},
					"files": ["src/index.ts"]
				}`,
				"/home/projects/TS/p1/src/index.ts": `import { z } from "./z";`,
			}
			session, utils := projecttestutil.Setup(files)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()

			// Initially should have an error because z.ts is missing
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1)

			// Add a new file through failed lookup watch
			err = utils.FS().WriteFile("/home/projects/TS/p1/src/z.ts", `export const z = 1;`)
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeCreated,
					Uri:  "file:///home/projects/TS/p1/src/z.ts",
				},
			})

			// Error should be resolved and the new file should be included in the program
			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0)
			assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/z.ts") != nil)
		})

		t.Run("create wildcard included file", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true
					},
					"include": ["src"]
				}`,
				"/home/projects/TS/p1/src/index.ts": `a;`,
			}
			session, utils := projecttestutil.Setup(files)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()

			// Initially should have an error because declaration for 'a' is missing
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1)

			// Add a new file through wildcard watch
			err = utils.FS().WriteFile("/home/projects/TS/p1/src/a.ts", `const a = 1;`)
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeCreated,
					Uri:  "file:///home/projects/TS/p1/src/a.ts",
				},
			})

			// Error should be resolved and the new file should be included in the program
			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0)
			assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/a.ts") != nil)
		})

		t.Run("irrelevant extension changes are filtered out", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true
					},
					"include": ["src"]
				}`,
				"/home/projects/TS/p1/src/index.ts": `export const x = 1;`,
				"/home/projects/TS/p1/src/data.txt": `some text`,
			}
			session, utils := projecttestutil.Setup(files)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0)
			oldProgram := program

			// Modify an irrelevant file and send change/create events for files with
			// extensions that are not relevant to TypeScript compilation.
			err = utils.FS().WriteFile("/home/projects/TS/p1/src/data.txt", `updated text`)
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeChanged,
					Uri:  "file:///home/projects/TS/p1/src/data.txt",
				},
				{
					Type: lsproto.FileChangeTypeCreated,
					Uri:  "file:///home/projects/TS/p1/src/styles.css",
				},
				{
					Type: lsproto.FileChangeTypeCreated,
					Uri:  "file:///home/projects/TS/p1/src/image.png",
				},
			})

			// The program should not have been rebuilt since all events had irrelevant extensions.
			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			assert.Equal(t, program, oldProgram, "program should not be rebuilt for irrelevant extension changes")
		})

		t.Run("pnpm install links local package", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/pnpm/pnpm-workspace.yaml": `packages:
  - 'packages/*'`,
				"/home/projects/pnpm/packages/alpha/package.json": `{ "name": "@repo/alpha", "main": "index.ts" }`,
				"/home/projects/pnpm/packages/alpha/tsconfig.json": `{
					"compilerOptions": { "noLib": true, "composite": true }
				}`,
				"/home/projects/pnpm/packages/alpha/index.ts":    `export const alpha = 1;`,
				"/home/projects/pnpm/packages/beta/package.json": `{ "name": "@repo/beta" }`,
				"/home/projects/pnpm/packages/beta/tsconfig.json": `{
					"compilerOptions": { "noLib": true }
				}`,
				"/home/projects/pnpm/packages/beta/index.ts": `import { alpha } from "@repo/alpha";`,
			}
			session, utils := projecttestutil.Setup(files)
			session.DidOpenFile(context.Background(), "file:///home/projects/pnpm/packages/beta/index.ts", 1, files["/home/projects/pnpm/packages/beta/index.ts"].(string), lsproto.LanguageKindTypeScript)

			// Before pnpm install: the import is unresolved because node_modules/@repo/alpha doesn't exist.
			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/pnpm/packages/beta/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/pnpm/packages/beta/index.ts"))), 1)

			// Simulate pnpm install: create a symlink from beta's node_modules/@repo/alpha to packages/alpha.
			mapFS := utils.FsFromFileMap().FSys().(*vfstest.MapFS)
			err = mapFS.MkdirAll("home/projects/pnpm/packages/beta/node_modules/@repo", fs.ModePerm)
			assert.NilError(t, err)
			mapFS.AddSymlink("home/projects/pnpm/packages/beta/node_modules/@repo/alpha", "home/projects/pnpm/packages/alpha")

			// Fire watch events mimicking what VS Code sends for a pnpm install.
			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{Type: lsproto.FileChangeTypeCreated, Uri: "file:///home/projects/pnpm/packages/beta/node_modules"},
				{Type: lsproto.FileChangeTypeCreated, Uri: "file:///home/projects/pnpm/packages/beta/node_modules/%40repo"},
				{Type: lsproto.FileChangeTypeCreated, Uri: "file:///home/projects/pnpm/packages/beta/node_modules/%40repo/alpha"},
				{Type: lsproto.FileChangeTypeCreated, Uri: "file:///home/projects/pnpm/pnpm-lock.yaml"},
				{Type: lsproto.FileChangeTypeChanged, Uri: "file:///home/projects/pnpm/packages/beta/node_modules/.bin/tsc"},
				{Type: lsproto.FileChangeTypeChanged, Uri: "file:///home/projects/pnpm/packages/beta/node_modules/.bin/tsserver"},
			})

			// After pnpm install: the import should resolve.
			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/pnpm/packages/beta/index.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			diags := program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/pnpm/packages/beta/index.ts"))
			for _, d := range diags {
				t.Logf("diagnostic: %s", d.String())
			}
			assert.Equal(t, len(diags), 0)
		})

		t.Run("symlinked node_modules package.json change invalidates resolution", func(t *testing.T) {
			t.Parallel()
			// Set up a project that imports a symlinked node_modules package.
			// The package resolves via "main" in package.json to dist/index.js.
			files := map[string]any{
				"/home/projects/myproject/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true,
						"module": "nodenext",
						"moduleResolution": "nodenext"
					},
					"files": ["src/index.ts"]
				}`,
				"/home/projects/myproject/src/index.ts": `import { foo } from "mylib";`,
				// The real package lives as a sibling directory
				"/home/projects/mylib/package.json": `{
					"name": "mylib",
					"main": "dist/index.js"
				}`,
				"/home/projects/mylib/dist/index.js":   `exports.foo = function() { return 1; };`,
				"/home/projects/mylib/dist/index.d.ts": `export declare function foo(): number;`,
				// node_modules/mylib is a symlink to the sibling
				"/home/projects/myproject/node_modules/mylib": vfstest.Symlink("/home/projects/mylib"),
			}

			session, utils := projecttestutil.SetupWithOptions(files, &project.SessionOptions{
				CurrentDirectory:   "/home/projects/myproject",
				DefaultLibraryPath: bundled.LibPath(),
				TypingsLocation:    projecttestutil.TestTypingsLocation,
				PositionEncoding:   lsproto.PositionEncodingKindUTF8,
				WatchEnabled:       true,
				LoggingEnabled:     true,
			})
			session.DidOpenFile(context.Background(), "file:///home/projects/myproject/src/index.ts", 1, files["/home/projects/myproject/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			// Initial state: import resolves successfully via package.json main -> dist/index.d.ts
			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/myproject/src/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()
			session.WaitForBackgroundTasks()
			diags := program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/myproject/src/index.ts"))
			for _, d := range diags {
				t.Logf("initial diagnostic: %s", d.String())
			}
			assert.Equal(t, len(diags), 0, "import should resolve initially")

			// Assert: watched file globs cover the realpath of package.json and dist/index.d.ts.
			// With a workspace dir set, watchers use RelativePattern with a base URI.
			assert.Check(t, utils.WatchesFile("/home/projects/mylib/package.json"), "realpath of package.json should be watched")
			assert.Check(t, utils.WatchesFile("/home/projects/mylib/dist/index.d.ts"), "realpath of dist/index.d.ts should be watched")

			// Edit package.json to remove "main" field
			err = utils.FS().WriteFile("/home/projects/mylib/package.json", `{
				"name": "mylib"
			}`)
			assert.NilError(t, err)

			// Fire watch event for the realpath of the changed package.json.
			// A real editor would fire this for the realpath since it watches realpaths.
			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeChanged,
					Uri:  "file:///home/projects/mylib/package.json",
				},
			})

			// After removing "main" from package.json, the import should no longer resolve.
			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/myproject/src/index.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			diags = program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/myproject/src/index.ts"))
			assert.Assert(t, len(diags) > 0, "import should fail after removing main from package.json")
		})

		t.Run("create file in non-existent directory", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true
					},
					"files": ["src/index.ts"]
				}`,
				"/home/projects/TS/p1/src/index.ts": `import { helper } from "./lib/helper";`,
			}
			session, utils := projecttestutil.Setup(files)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			// Initially should have an error because lib/helper.ts doesn't exist
			// and src/lib/ directory doesn't exist either.
			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1)

			// Create the directory and file.
			err = utils.FS().WriteFile("/home/projects/TS/p1/src/lib/helper.ts", `export const helper = 1;`)
			assert.NilError(t, err)

			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeCreated,
					Uri:  "file:///home/projects/TS/p1/src/lib/helper.ts",
				},
			})

			// Error should be resolved.
			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0)
			assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/lib/helper.ts") != nil)
		})

		t.Run("create symlink directory matching include pattern", func(t *testing.T) {
			t.Parallel()
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true
					},
					"include": ["src"]
				}`,
				"/home/projects/TS/p1/src/index.ts":   `export const x = 1;`,
				"/home/projects/TS/shared/utils.ts":   `export const util = "hello";`,
				"/home/projects/TS/shared/helpers.ts": `export const helper = 42;`,
			}
			session, utils := projecttestutil.Setup(files)
			session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript)

			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program := ls.GetProgram()

			// Initially, project only has the one file in src/.
			assert.Check(t, slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/index.ts"))
			assert.Check(t, !slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/linked/utils.ts"))
			assert.Check(t, !slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/linked/helpers.ts"))

			// Create a symlink directory inside src/ that points to the shared directory.
			mapFS := utils.FsFromFileMap().FSys().(*vfstest.MapFS)
			mapFS.AddSymlink("home/projects/TS/p1/src/linked", "home/projects/TS/shared")

			// Send directory creation event (what VS Code sends when a symlink directory appears).
			session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
				{
					Type: lsproto.FileChangeTypeCreated,
					Uri:  "file:///home/projects/TS/p1/src/linked",
				},
			})

			// After the symlink directory is created, the files inside it should be
			// picked up by the wildcard include pattern.
			ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			program = ls.GetProgram()
			assert.Check(t, slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/index.ts"))
			assert.Check(t, slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/linked/utils.ts"))
			assert.Check(t, slices.Contains(program.CommandLine().ParsedConfig.FileNames, "/home/projects/TS/p1/src/linked/helpers.ts"))
		})
	})

	t.Run("refreshes code lenses and inlay hints when relevant user preferences change", func(t *testing.T) {
		t.Parallel()
		files := map[string]any{
			"/src/tsconfig.json": "{}",
			"/src/index.ts":      "export const x = 1;",
		}
		session, utils := projecttestutil.Setup(files)
		session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, files["/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
		_, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
		assert.NilError(t, err)

		session.Configure(lsutil.NewDefaultUserPreferences())
		// Change user preferences for code lens and inlay hints.
		newPrefs := session.Config()
		newPrefs.CodeLens.ReferencesCodeLensEnabled = core.TSTrue
		newPrefs.InlayHints.IncludeInlayFunctionLikeReturnTypeHints = core.TSTrue

		session.Configure(newPrefs)

		codeLensRefreshCalls := utils.Client().RefreshCodeLensCalls()
		inlayHintsRefreshCalls := utils.Client().RefreshInlayHintsCalls()
		assert.Equal(t, len(codeLensRefreshCalls), 1, "expected one RefreshCodeLens call after code lens preference change")
		assert.Equal(t, len(inlayHintsRefreshCalls), 1, "expected one RefreshInlayHints call after inlay hints preference change")
	})

	t.Run("schedules diagnostics refresh when reportStyleChecksAsWarnings changes", func(t *testing.T) {
		t.Parallel()
		files := map[string]any{
			"/src/tsconfig.json": "{}",
			"/src/index.ts":      "export const x = 1;",
		}
		session, utils := projecttestutil.Setup(files)
		session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, files["/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
		_, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
		assert.NilError(t, err)
		session.WaitForBackgroundTasks()

		// Record the baseline count of RefreshDiagnostics calls.
		baselineRefreshCount := len(utils.Client().RefreshDiagnosticsCalls())

		// Toggle reportStyleChecksAsWarnings (default is true, so set it to false).
		prefs := lsutil.NewDefaultUserPreferences()
		prefs.ReportStyleChecksAsWarnings = core.TSFalse
		session.Configure(prefs)
		session.WaitForBackgroundTasks()

		refreshCount := len(utils.Client().RefreshDiagnosticsCalls())
		assert.Assert(t, refreshCount > baselineRefreshCount,
			"expected RefreshDiagnostics to be called after reportStyleChecksAsWarnings change, got %d calls (baseline %d)",
			refreshCount, baselineRefreshCount)
	})

	t.Run("config parsing", func(t *testing.T) {
		t.Parallel()
		files := map[string]any{
			"/src/tsconfig.json": "{}",
			"/src/index.ts":      "export const x = 1;",
		}
		session, _ := projecttestutil.Setup(files)
		session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, files["/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
		_, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
		assert.NilError(t, err)

		configMap1 := map[string]any{
			"preferences": map[string]any{
				"useAliasesForRenames": true,
				"quoteStyle":           "single",
			},
			"unstable": map[string]any{
				"organizeImportsSort": "ordinalIgnoreCase",
			},
		}
		session.Configure(lsutil.ParseUserPreferences(map[string]any{"js/ts": configMap1}))
		actualConfig1 := session.Config()
		expectedPrefs1 := lsutil.NewDefaultUserPreferences()
		expectedPrefs1.UseAliasesForRename = core.TSTrue
		expectedPrefs1.QuotePreference = lsutil.QuotePreferenceSingle
		expectedPrefs1.OrganizeImportsSort = lsutil.OrganizeImportsSortOrdinalIgnoreCase

		assert.DeepEqual(t, actualConfig1, expectedPrefs1)

		configMap2 := map[string]any{
			"preferences": map[string]any{
				"useAliasesForRenames": false,
				"quoteStyle":           "double",
			},
			"unstable": map[string]any{
				"organizeImportsSort": "ordinal",
			},
		}
		session.Configure(lsutil.ParseUserPreferences(map[string]any{"js/ts": configMap2}))
		actualConfig2 := session.Config()
		expectedPrefs2 := lsutil.NewDefaultUserPreferences()
		expectedPrefs2.UseAliasesForRename = core.TSFalse
		expectedPrefs2.QuotePreference = lsutil.QuotePreferenceDouble
		expectedPrefs2.OrganizeImportsSort = lsutil.OrganizeImportsSortOrdinal

		assert.DeepEqual(t, actualConfig2, expectedPrefs2)
	})

	t.Run("language service for closed files", func(t *testing.T) {
		t.Parallel()

		t.Run("closed file in configured project not yet opened", func(t *testing.T) {
			t.Parallel()
			// Set up a project where a tsconfig exists but no files have been opened.
			// Requesting language service for a file covered by that tsconfig should
			// work even though the file was never opened via didOpen.
			files := map[string]any{
				"/home/projects/TS/p1/tsconfig.json": `{
					"compilerOptions": {
						"noLib": true,
						"strict": true
					},
					"include": ["src"]
				}`,
				"/home/projects/TS/p1/src/index.ts": `export const x: number = 1;`,
			}
			session, _ := projecttestutil.Setup(files)

			// Do NOT open any file. Directly request language service for a closed file
			// that belongs to the configured project.
			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts")
			assert.NilError(t, err)
			assert.Assert(t, ls != nil)
			program := ls.GetProgram()
			assert.Assert(t, program != nil)
			sourceFile := program.GetSourceFile("/home/projects/TS/p1/src/index.ts")
			assert.Assert(t, sourceFile != nil)
			assert.Equal(t, sourceFile.Text(), `export const x: number = 1;`)
		})

		t.Run("closed file with no configured project creates inferred project", func(t *testing.T) {
			t.Parallel()
			// Set up a file that has no tsconfig. Requesting language service for it
			// should create an inferred project even though the file was never opened.
			files := map[string]any{
				"/home/projects/TS/loose/index.ts": `const greeting: string = "hello";`,
			}
			session, _ := projecttestutil.Setup(files)

			// Do NOT open any file. Directly request language service for a closed file
			// that has no configured project.
			ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/loose/index.ts")
			assert.NilError(t, err)
			assert.Assert(t, ls != nil)
			program := ls.GetProgram()
			assert.Assert(t, program != nil)
			sourceFile := program.GetSourceFile("/home/projects/TS/loose/index.ts")
			assert.Assert(t, sourceFile != nil)
			assert.Equal(t, sourceFile.Text(), `const greeting: string = "hello";`)
		})
	})

	t.Run("jsconfig.json used for JS files when tsconfig.json exists in same directory", func(t *testing.T) {
		t.Parallel()
		files := map[string]any{
			"/home/projects/TS/p1/tsconfig.json": `{
				"compilerOptions": {
					"noLib": true,
					"strict": true
				}
			}`,
			"/home/projects/TS/p1/jsconfig.json": `{
				"compilerOptions": {
					"noLib": true,
					"checkJs": true
				}
			}`,
			"/home/projects/TS/p1/index.ts": `export const x: number = 1;`,
			"/home/projects/TS/p1/app.js":   `/** @type {number} */ var y = "not a number";`,
		}
		session, _ := projecttestutil.Setup(files)

		// Open the JS file - it should be assigned to the jsconfig.json project, not tsconfig.json
		session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/app.js", 1, files["/home/projects/TS/p1/app.js"].(string), lsproto.LanguageKindJavaScript)

		snapshot := session.Snapshot()
		jsURI := lsproto.DocumentUri("file:///home/projects/TS/p1/app.js")
		defaultProject := snapshot.GetDefaultProject(jsURI)
		assert.Assert(t, defaultProject != nil, "JS file should have a default project")
		assert.Equal(t, defaultProject.Name(), "/home/projects/TS/p1/jsconfig.json", "JS file should belong to jsconfig.json project, not tsconfig.json")

		// Open the TS file - it should be assigned to tsconfig.json project
		session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/index.ts", 1, files["/home/projects/TS/p1/index.ts"].(string), lsproto.LanguageKindTypeScript)

		snapshot = session.Snapshot()
		tsURI := lsproto.DocumentUri("file:///home/projects/TS/p1/index.ts")
		defaultTSProject := snapshot.GetDefaultProject(tsURI)
		assert.Assert(t, defaultTSProject != nil, "TS file should have a default project")
		assert.Equal(t, defaultTSProject.Name(), "/home/projects/TS/p1/tsconfig.json", "TS file should belong to tsconfig.json project")
	})
}
