package project

import (
	"time"

	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project/logging"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs"
)

var _ compiler.CompilerHost = (*compilerHost)(nil)

type compilerHost struct {
	configFilePath   tspath.Path
	currentDirectory string
	sessionOptions   *SessionOptions

	fs                 *snapshotFSBuilder
	compilerFS         *compilerFS
	configFileRegistry *ConfigFileRegistry
	seenFiles          *collections.SyncSet[tspath.Path]

	project *Project
	builder *projectCollectionBuilder
	logger  *logging.LogTree
}

type builderFileSource struct {
	seenFiles         *collections.SyncSet[tspath.Path]
	snapshotFSBuilder *snapshotFSBuilder
}

func (c *builderFileSource) GetFile(fileName string) FileHandle {
	path := c.snapshotFSBuilder.toPath(fileName)
	c.seenFiles.Add(path)
	return c.snapshotFSBuilder.GetFileByPath(fileName, path)
}

func (c *builderFileSource) FS() vfs.FS {
	return c.snapshotFSBuilder.FS()
}

func newCompilerHost(
	currentDirectory string,
	project *Project,
	builder *projectCollectionBuilder,
	logger *logging.LogTree,
) *compilerHost {
	seenFiles := &collections.SyncSet[tspath.Path]{}
	compilerFS := &compilerFS{
		source: &builderFileSource{
			seenFiles:         seenFiles,
			snapshotFSBuilder: builder.fs,
		},
	}

	return &compilerHost{
		configFilePath:   project.configFilePath,
		currentDirectory: currentDirectory,
		sessionOptions:   builder.sessionOptions,

		compilerFS: compilerFS,
		seenFiles:  seenFiles,

		fs:      builder.fs,
		project: project,
		builder: builder,
		logger:  logger,
	}
}

// freeze clears references to mutable state to make the compilerHost safe for use
// after the snapshot has been finalized. See the usage in snapshot.go for more details.
func (c *compilerHost) freeze(snapshotFS *snapshotFS, configFileRegistry *ConfigFileRegistry) {
	if c.builder == nil {
		panic("freeze can only be called once")
	}
	c.compilerFS.source = snapshotFS
	c.configFileRegistry = configFileRegistry
	c.fs = nil
	c.builder = nil
	c.project = nil
	c.logger = nil
}

func (c *compilerHost) ensureAlive() {
	if c.builder == nil || c.project == nil {
		panic("method must not be called after snapshot initialization")
	}
}

// DefaultLibraryPath implements compiler.CompilerHost.
func (c *compilerHost) DefaultLibraryPath() string {
	return c.sessionOptions.DefaultLibraryPath
}

// FS implements compiler.CompilerHost.
func (c *compilerHost) FS() vfs.FS {
	return c.compilerFS
}

// GetCurrentDirectory implements compiler.CompilerHost.
func (c *compilerHost) GetCurrentDirectory() string {
	return c.currentDirectory
}

// GetResolvedProjectReference implements compiler.CompilerHost.
func (c *compilerHost) GetResolvedProjectReference(fileName string, path tspath.Path) *tsoptions.ParsedCommandLine {
	if c.builder == nil {
		return c.configFileRegistry.GetConfig(path)
	} else {
		c.seenFiles.Add(path)
		return c.builder.configFileRegistryBuilder.acquireConfigForProject(fileName, path, c.project, c.logger)
	}
}

// GetSourceFile implements compiler.CompilerHost. GetSourceFile increments
// the ref count of source files it acquires in the parseCache. There should
// be a corresponding release for each call made.
func (c *compilerHost) GetSourceFile(opts ast.SourceFileParseOptions) *ast.SourceFile {
	c.ensureAlive()
	c.seenFiles.Add(opts.Path)
	if fh := c.fs.GetFileByPath(opts.FileName, opts.Path); fh != nil {
		return c.builder.parseCache.Acquire(fh, opts, fh.Kind())
	}
	return nil
}

// Trace implements compiler.CompilerHost.
func (c *compilerHost) Trace(msg string) {
	panic("unimplemented")
}

var _ vfs.FS = (*compilerFS)(nil)

type compilerFS struct {
	source FileSource
}

// DirectoryExists implements vfs.FS.
func (fs *compilerFS) DirectoryExists(path string) bool {
	return fs.source.FS().DirectoryExists(path)
}

// FileExists implements vfs.FS.
func (fs *compilerFS) FileExists(path string) bool {
	if fh := fs.source.GetFile(path); fh != nil {
		return true
	}
	return fs.source.FS().FileExists(path)
}

// GetAccessibleEntries implements vfs.FS.
func (fs *compilerFS) GetAccessibleEntries(path string) vfs.Entries {
	return fs.source.FS().GetAccessibleEntries(path)
}

// ReadFile implements vfs.FS.
func (fs *compilerFS) ReadFile(path string) (contents string, ok bool) {
	if fh := fs.source.GetFile(path); fh != nil {
		return fh.Content(), true
	}
	return "", false
}

// Realpath implements vfs.FS.
func (fs *compilerFS) Realpath(path string) string {
	return fs.source.FS().Realpath(path)
}

// Stat implements vfs.FS.
func (fs *compilerFS) Stat(path string) vfs.FileInfo {
	return fs.source.FS().Stat(path)
}

// UseCaseSensitiveFileNames implements vfs.FS.
func (fs *compilerFS) UseCaseSensitiveFileNames() bool {
	return fs.source.FS().UseCaseSensitiveFileNames()
}

// WalkDir implements vfs.FS.
func (fs *compilerFS) WalkDir(root string, walkFn vfs.WalkDirFunc) error {
	panic("unimplemented")
}

// WriteFile implements vfs.FS.
func (fs *compilerFS) WriteFile(path string, data string, writeByteOrderMark bool) error {
	panic("unimplemented")
}

// Remove implements vfs.FS.
func (fs *compilerFS) Remove(path string) error {
	panic("unimplemented")
}

// Chtimes implements vfs.FS.
func (fs *compilerFS) Chtimes(path string, atime time.Time, mtime time.Time) error {
	panic("unimplemented")
}
