using System.Text.RegularExpressions; using LyricFlow.Backend.Api; using LyricFlow.Core.Dtos; using LyricFlow.Core.Engine; using LyricFlow.Core.Services; using LyricFlow.Core.Storage; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http.Json; // MARK: - Application Bootstrap #region Application Bootstrap var builder = WebApplication.CreateSlimBuilder(args); builder.WebHost.UseUrls(builder.Configuration["Backend:Urls"] ?? "http://127.0.0.1:5000"); builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.TypeInfoResolverChain.Insert(0, ApiJsonSerializerContext.Default); }); var dbPath = builder.Configuration["Database:Path"] ?? "data/lyricflow.db"; builder.Services.AddSingleton(new StorageService(dbPath)); var cmuDictPath = NltkPathResolver.ResolveCmudictPath(builder.Configuration["Nltk:CmudictPath"]); var wordNetPath = NltkPathResolver.ResolveWordNetPath(builder.Configuration["Nltk:WordNetPath"]); var processor = new PhoneticProcessor(cmuDictPath); builder.Services.AddSingleton(processor); builder.Services.AddSingleton(new WordNetLexicon(wordNetPath)); builder.Services.AddSingleton(sp => new RhymeEngine(processor, sp.GetRequiredService())); builder.Services.AddSingleton(sp => new SpellcheckEngine(processor, sp.GetRequiredService())); builder.Services.AddSingleton(new ProjectStateService()); builder.Services.AddSingleton(new SessionStoreService(builder.Configuration["State:SessionPath"])); builder.Services.AddSingleton(new CorePreferencesStore(builder.Configuration["State:CorePreferencesPath"])); builder.Services.AddSingleton(new FileService()); var app = builder.Build(); #endregion // MARK: - Health And Analysis Routes #region Health And Analysis Routes app.MapGet("/api/health", (PhoneticProcessor phonetics, WordNetLexicon wordNet) => { var capabilities = new CapabilitiesDto( Analysis: true, Phonetics: true, Spellcheck: true, Synonyms: wordNet.IsAvailable, Autocorrect: true, Projects: true, Sessions: true, Settings: true, Files: true, History: true, Scratchpad: true, HasCmudict: phonetics.Dictionary.Count > 0, HasWordNet: wordNet.IsAvailable ); return Results.Ok(new HealthDto( Status: "healthy", Version: "2.0.0", Ready: phonetics.Dictionary.Count > 0, DictionaryCount: phonetics.Dictionary.Count, Capabilities: capabilities )); }); app.MapGet("/api/analysis/phonemes", (PhoneticProcessor phonetics, [FromQuery] string word) => { return Results.Ok(phonetics.GetPhonemes(word)); }); app.MapGet("/api/analysis/syllables", (RhymeEngine engine, [FromQuery] string word) => { return Results.Ok(new CountDto(engine.CountSyllables(word))); }); app.MapGet("/api/analysis/suggestions", (RhymeEngine engine, [FromQuery] string word, [FromQuery] int? limit) => { return Results.Ok(engine.FindSuggestions(word, limit ?? 20)); }); app.MapGet("/api/analysis/synonyms", (RhymeEngine engine, [FromQuery] string word, [FromQuery] int? limit) => { return Results.Ok(engine.FindSynonyms(word, limit ?? 15)); }); app.MapPost("/api/analysis/rhyme-groups", (RhymeEngine engine, [FromBody] AnalyzeRequest request) => { return Results.Ok(engine.GetRhymeGroups(request.Text)); }); app.MapPost("/api/analysis/line-densities", (RhymeEngine engine, [FromBody] AnalyzeRequest request) => { return Results.Ok(CalculateLineDensities(engine, request.Text)); }); app.MapGet("/api/engine/rhymes", (RhymeEngine engine, [FromQuery] string word, [FromQuery] int? limit) => { return Results.Ok(engine.FindSuggestions(word, limit ?? 20)); }); app.MapGet("/api/engine/syllables", (RhymeEngine engine, [FromQuery] string word) => { return Results.Ok(new CountDto(engine.CountSyllables(word))); }); app.MapPost("/api/engine/analyze", (RhymeEngine engine, [FromBody] AnalyzeRequest request) => { return Results.Ok(engine.GetRhymeGroups(request.Text)); }); app.MapPost("/api/engine/densities", (RhymeEngine engine, [FromBody] AnalyzeRequest request) => { return Results.Ok(CalculateLineDensities(engine, request.Text)); }); #endregion // MARK: - Spellcheck And State Routes #region Spellcheck And State Routes app.MapGet("/api/spellcheck/known", (SpellcheckEngine engine, [FromQuery] string word) => { return Results.Ok(new KnownWordDto(engine.IsKnownWord(word))); }); app.MapGet("/api/spellcheck/suggestions", (SpellcheckEngine engine, [FromQuery] string word, [FromQuery] int? limit) => { return Results.Ok(engine.GetSpellingSuggestions(word, limit ?? 6)); }); app.MapGet("/api/spellcheck/autocorrect", (SpellcheckEngine engine, [FromQuery] string word) => { return Results.Ok(new CandidateDto(engine.GetAutocorrectCandidate(word))); }); app.MapPost("/api/spellcheck/issues", (SpellcheckEngine engine, [FromBody] AnalyzeRequest request, [FromQuery] int? limit) => { return Results.Ok(engine.GetTextSpellingIssues(request.Text, limit ?? 6)); }); app.MapPost("/api/projects/read", (ProjectStateService projects, [FromBody] ProjectReadRequest request) => { return Results.Ok(projects.ReadProject(request.ProjectFile)); }); app.MapPost("/api/projects/write", (ProjectStateService projects, [FromBody] ProjectWriteRequest request) => { projects.WriteProject(request.ProjectFile, request.State); return Results.Ok(new SuccessDto(true)); }); app.MapPost("/api/session/load", (SessionStoreService sessionStore, [FromBody] SessionLoadRequest request) => { return Results.Ok(sessionStore.Load(request.WorkspaceRoot)); }); app.MapPost("/api/session/save", (SessionStoreService sessionStore, [FromBody] SessionSaveRequest request) => { sessionStore.Save(request.WorkspaceRoot, request.Snapshots); return Results.Ok(new SuccessDto(true)); }); app.MapPost("/api/session/clear", (SessionStoreService sessionStore, [FromBody] SessionClearRequest request) => { sessionStore.Clear(request.WorkspaceRoot); return Results.Ok(new SuccessDto(true)); }); app.MapGet("/api/settings", (CorePreferencesStore settingsStore) => { return Results.Ok(settingsStore.Load()); }); app.MapPost("/api/settings", (CorePreferencesStore settingsStore, [FromBody] AppCorePreferencesDto preferences) => { settingsStore.Save(preferences); return Results.Ok(new SuccessDto(true)); }); app.MapPost("/api/files/read", (FileService files, [FromBody] FileReadRequest request) => { return Results.Ok(files.ReadFile(request.Path)); }); app.MapPost("/api/files/write", (FileService files, [FromBody] FileWriteRequest request) => { return Results.Ok(files.WriteFile(request.Path, request.Content)); }); app.MapPost("/api/files/create", (FileService files, [FromBody] FileCreateRequest request) => { return Results.Ok(request.IsDirectory ? files.CreateDirectory(request.Path) : files.CreateFile(request.Path)); }); app.MapPost("/api/files/rename", (FileService files, [FromBody] FileRenameRequest request) => { return Results.Ok(files.Rename(request.OldPath, request.NewPath, request.RootPath)); }); app.MapPost("/api/files/delete", (FileService files, [FromBody] FileDeleteRequest request) => { return Results.Ok(files.Delete(request.Path, request.RootPath)); }); #endregion // MARK: - Storage Routes #region Storage Routes app.MapPost("/api/history/snapshots", async (StorageService storage, [FromBody] SaveSnapshotRequest request) => { await storage.SaveSnapshotAsync(request.FilePath, request.Content); return Results.Ok(); }); app.MapGet("/api/history/snapshots", async (StorageService storage, [FromQuery] string filePath) => { var snapshots = await storage.GetSnapshotsAsync(filePath); return Results.Ok(snapshots.Select(snapshot => snapshot.ToDto()).ToList()); }); app.MapPost("/api/snapshots", async (StorageService storage, [FromBody] SaveSnapshotRequest request) => { await storage.SaveSnapshotAsync(request.FilePath, request.Content); return Results.Ok(); }); app.MapGet("/api/snapshots", async (StorageService storage, [FromQuery] string filePath) => { var snapshots = await storage.GetSnapshotsAsync(filePath); return Results.Ok(snapshots.Select(snapshot => snapshot.ToDto()).ToList()); }); app.MapPost("/api/scratchpad", async (StorageService storage, [FromBody] SaveScratchpadRequest request) => { await storage.SaveScratchpadAsync(request.ProjectId, request.Content); return Results.Ok(); }); app.MapGet("/api/scratchpad/{projectId}", async (StorageService storage, string projectId) => { return Results.Ok(new ScratchpadContentDto(await storage.GetScratchpadAsync(projectId))); }); app.Run(); #endregion // MARK: - Route Utilities #region Route Utilities static List CalculateLineDensities(RhymeEngine engine, string text) { var lines = text.Split('\n'); var groups = engine.GetRhymeGroups(text); using var groupIter = groups.GetEnumerator(); var densities = new List(); foreach (var line in lines) { var stripped = line.Trim(); if (stripped.StartsWith("#") || stripped.StartsWith("@") || stripped.StartsWith(">")) { densities.Add(0.0); continue; } var analysisText = Regex.Replace(line, @"\[.*?\]", ""); var words = Regex.Matches(analysisText, @"\b\w+\b"); if (words.Count == 0) { densities.Add(0.0); continue; } var rhymeCount = 0; for (var index = 0; index < words.Count; index++) { if (groupIter.MoveNext() && groupIter.Current.Group is not null) { rhymeCount++; } } densities.Add((double)rhymeCount / words.Count); } return densities; } #endregion