using Microsoft.VisualStudio.SolutionPersistence.Model;
using SharpIDE.Application.Features.Analysis;
using SharpIDE.Application.Features.Evaluation;
using SharpIDE.Application.Features.Events;
using SharpIDE.Application.Features.FilePersistence;
using SharpIDE.Application.Features.SolutionDiscovery;
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;

namespace SharpIDE.Application.Features.FileWatching;

public enum FileChangeType
{
	IdeSaveToDisk, // Apply to disk
	IdeUnsavedChange, // Apply only in memory
	ExternalChange, // Apply to disk, as well as in memory
	CodeActionChange, // Apply to disk, as well as in memory
	CompletionChange // Apply only in memory, as well as notify tabs of new content
}

public class FileChangedService(RoslynAnalysis roslynAnalysis, IdeOpenTabsFileManager openTabsFileManager)
{
	private readonly RoslynAnalysis _roslynAnalysis = roslynAnalysis;
	private readonly IdeOpenTabsFileManager _openTabsFileManager = openTabsFileManager;

	public SharpIdeSolutionModel SolutionModel { get; set; } = null!;

	public async Task SharpIdeFileRenamed(SharpIdeFile file, string oldFilePath)
	{
		if (file.IsRoslynWorkspaceFile)
		{
			await HandleWorkspaceFileRenamed(file, oldFilePath);
		}
		// TODO: handle csproj moved
	}

	public async Task SharpIdeFileMoved(SharpIdeFile file, string oldFilePath)
	{
		if (file.IsRoslynWorkspaceFile)
		{
			await HandleWorkspaceFileMoved(file, oldFilePath);
		}
		// TODO: handle csproj moved
	}

	public async Task SharpIdeFileAdded(SharpIdeFile file, string content)
	{
		if (file.IsRoslynWorkspaceFile)
		{
			await HandleWorkspaceFileAdded(file, content);
		}
		// TODO: handle csproj added
	}

	public async Task SharpIdeFileRemoved(SharpIdeFile file)
	{
		await file.FileDeleted.InvokeParallelAsync();
		if (file.IsRoslynWorkspaceFile)
		{
			await HandleWorkspaceFileRemoved(file);
		}
	}

	// All file changes should go via this service
	public async Task SharpIdeFileChanged(SharpIdeFile file, string newContents, FileChangeType changeType, SharpIdeFileLinePosition? linePosition = null)
	{
		if (changeType is FileChangeType.ExternalChange)
		{
			// Disk is already up to date
			// Update any open tabs
			// update in memory
			await _openTabsFileManager.UpdateFileTextInMemory(file, newContents);
			file.FileContentsChangedExternally.InvokeParallelFireAndForget(linePosition);
		}
		else if (changeType is FileChangeType.CodeActionChange)
		{
			// update in memory, tabs and save to disk
			await _openTabsFileManager.UpdateInMemoryIfOpenAndSaveAsync(file, newContents);
			file.FileContentsChangedExternally.InvokeParallelFireAndForget(linePosition);
		}
		else if (changeType is FileChangeType.CompletionChange)
		{
			// update in memory, tabs
			await _openTabsFileManager.UpdateFileTextInMemory(file, newContents);
			file.FileContentsChangedExternally.InvokeParallelFireAndForget(linePosition);
		}
		else if (changeType is FileChangeType.IdeSaveToDisk)
		{
			// save to disk
			// We technically don't need to update in memory here. TODO review
			await _openTabsFileManager.UpdateInMemoryIfOpenAndSaveAsync(file, newContents);
		}
		else if (changeType is FileChangeType.IdeUnsavedChange)
		{
			// update in memory only
			await _openTabsFileManager.UpdateFileTextInMemory(file, newContents);
		}
		var afterSaveTask = (file, changeType) switch
		{
			({ IsRoslynWorkspaceFile: true }, _) => HandleWorkspaceFileChanged(file, newContents),
			({ IsCsprojFile: true }, FileChangeType.IdeSaveToDisk or FileChangeType.ExternalChange) => HandleCsprojChanged(file),
			({ IsCsprojFile: true }, _) => Task.CompletedTask,
			_ => throw new InvalidOperationException("Unknown file change type.")
		};
		await afterSaveTask;
	}

	private CancellationTokenSource _updateSolutionDiagnosticsCts = new();
	private async Task HandleCsprojChanged(SharpIdeFile file)
	{
		var project = SolutionModel.AllProjects.SingleOrDefault(p => p.FilePath == file.Path);
		if (project is null) return;
		var newCts = new CancellationTokenSource();
		var oldCts = Interlocked.Exchange(ref _updateSolutionDiagnosticsCts, newCts);
		await oldCts.CancelAsync();
		oldCts.Dispose();
		await ProjectEvaluation.ReloadProject(file.Path);
		await _roslynAnalysis.ReloadProject(project, CancellationToken.None);
		GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
		await _roslynAnalysis.UpdateSolutionDiagnostics(newCts.Token);
	}

	private async Task HandleWorkspaceFileChanged(SharpIdeFile file, string newContents)
	{
		var newCts = new CancellationTokenSource();
		var oldCts = Interlocked.Exchange(ref _updateSolutionDiagnosticsCts, newCts);
		await oldCts.CancelAsync();
		oldCts.Dispose();
		await _roslynAnalysis.UpdateDocument(file, newContents);
		GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
		await _roslynAnalysis.UpdateSolutionDiagnostics(newCts.Token);
	}

	private async Task HandleWorkspaceFileAdded(SharpIdeFile file, string contents)
	{
		var newCts = new CancellationTokenSource();
		var oldCts = Interlocked.Exchange(ref _updateSolutionDiagnosticsCts, newCts);
		await oldCts.CancelAsync();
		oldCts.Dispose();
		await _roslynAnalysis.AddDocument(file, contents);
		GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
		await _roslynAnalysis.UpdateSolutionDiagnostics(newCts.Token);
	}

	private async Task HandleWorkspaceFileRemoved(SharpIdeFile file)
	{
		var newCts = new CancellationTokenSource();
		var oldCts = Interlocked.Exchange(ref _updateSolutionDiagnosticsCts, newCts);
		await oldCts.CancelAsync();
		oldCts.Dispose();
		await _roslynAnalysis.RemoveDocument(file);
		GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
		await _roslynAnalysis.UpdateSolutionDiagnostics(newCts.Token);
	}

	private async Task HandleWorkspaceFileMoved(SharpIdeFile file, string oldFilePath)
	{
		var newCts = new CancellationTokenSource();
		var oldCts = Interlocked.Exchange(ref _updateSolutionDiagnosticsCts, newCts);
		await oldCts.CancelAsync();
		oldCts.Dispose();
		await _roslynAnalysis.MoveDocument(file, oldFilePath);
		GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
		await _roslynAnalysis.UpdateSolutionDiagnostics(newCts.Token);
	}

	private async Task HandleWorkspaceFileRenamed(SharpIdeFile file, string oldFilePath)
	{
		var newCts = new CancellationTokenSource();
		var oldCts = Interlocked.Exchange(ref _updateSolutionDiagnosticsCts, newCts);
		await oldCts.CancelAsync();
		oldCts.Dispose();
		await _roslynAnalysis.RenameDocument(file, oldFilePath);
		GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
		await _roslynAnalysis.UpdateSolutionDiagnostics(newCts.Token);
	}
}
