using System.Data; using System.Text.Json; using Dapper; using PlotLine.Models; namespace PlotLine.Data; public interface IOnboardingBuildRepository { Task BuildAsync(int userId, OnboardingManuscriptBuildRequest request); Task MarkMarkerSyncSentAsync(int buildId); Task MarkMarkerSyncCompletedAsync(int buildId); Task MarkMarkerSyncFailedAsync(int buildId, string message); Task SaveStructuralChapterSourceAsync(StructuralChapterSourceSaveRequest request); Task SaveSceneManuscriptSourceAsync(SceneManuscriptSourceSaveRequest request); } public sealed class OnboardingBuildRepository(ISqlConnectionFactory connectionFactory) : IOnboardingBuildRepository { public async Task BuildAsync(int userId, OnboardingManuscriptBuildRequest request) { using var connection = connectionFactory.CreateConnection(); using var result = await connection.QueryMultipleAsync( "dbo.Onboarding_ManuscriptBuild_Create", new { UserID = userId, request.PreviewID, request.ProjectID, request.BookID, request.DocumentGuid, BindingVersion = 1, ChaptersJson = JsonSerializer.Serialize(request.Chapters), ScenesJson = JsonSerializer.Serialize(request.Scenes), CharactersJson = JsonSerializer.Serialize(request.Characters) }, commandType: CommandType.StoredProcedure); var header = await result.ReadSingleOrDefaultAsync(); if (header is null) { return null; } var chapters = (await result.ReadAsync()).ToList(); var scenes = (await result.ReadAsync()).ToList(); var characters = (await result.ReadAsync()).ToList(); return new OnboardingManuscriptBuildResult { BuildID = header.BuildID, PreviewID = header.PreviewID, ProjectID = header.ProjectID, BookID = header.BookID, ManuscriptDocumentID = header.ManuscriptDocumentID, Status = header.Status, MarkerStatus = header.MarkerStatus, ChaptersCreated = header.ChaptersCreated, ScenesCreated = header.ScenesCreated, CharactersCreated = header.CharactersCreated, CharactersReused = header.CharactersReused, SceneAppearancesCreated = header.SceneAppearancesCreated, AlreadyBuilt = header.AlreadyBuilt, Message = header.Message, ChapterMappings = chapters, SceneMappings = scenes, CharacterMappings = characters }; } public async Task MarkMarkerSyncSentAsync(int buildId) { using var connection = connectionFactory.CreateConnection(); await connection.ExecuteAsync( "dbo.Onboarding_ManuscriptBuild_MarkerSyncSent", new { BuildID = buildId }, commandType: CommandType.StoredProcedure); } public async Task MarkMarkerSyncCompletedAsync(int buildId) { using var connection = connectionFactory.CreateConnection(); await connection.ExecuteAsync( "dbo.Onboarding_ManuscriptBuild_MarkerSyncCompleted", new { BuildID = buildId }, commandType: CommandType.StoredProcedure); } public async Task MarkMarkerSyncFailedAsync(int buildId, string message) { using var connection = connectionFactory.CreateConnection(); await connection.ExecuteAsync( "dbo.Onboarding_ManuscriptBuild_MarkerSyncFailed", new { BuildID = buildId, Message = message }, commandType: CommandType.StoredProcedure); } public async Task SaveStructuralChapterSourceAsync(StructuralChapterSourceSaveRequest request) { using var connection = connectionFactory.CreateConnection(); await connection.ExecuteAsync( "dbo.ManuscriptStructuralChapterSource_Save", new { request.ChapterID, request.ProjectID, request.BookID, request.SourceText, request.SourceWordCount, request.SourceCharacterCount, request.ParagraphMapJson }, commandType: CommandType.StoredProcedure); } public async Task SaveSceneManuscriptSourceAsync(SceneManuscriptSourceSaveRequest request) { using var connection = connectionFactory.CreateConnection(); await connection.ExecuteAsync( "dbo.SceneManuscriptSource_Save", new { request.SceneID, request.ProjectID, request.BookID, request.ChapterID, request.SourceText, request.SourceWordCount, request.SourceCharacterCount, request.SourceStartParagraph, request.SourceEndParagraph, request.WordStartParagraphIndex, request.WordEndParagraphIndex, request.SourceKind }, commandType: CommandType.StoredProcedure); } } public sealed class OnboardingManuscriptBuildRequest { public Guid PreviewID { get; init; } public int ProjectID { get; init; } public int BookID { get; init; } public Guid DocumentGuid { get; init; } public IReadOnlyList Chapters { get; init; } = []; public IReadOnlyList Scenes { get; init; } = []; public IReadOnlyList Characters { get; init; } = []; } public sealed class OnboardingBuildChapterRequest { public string TemporaryChapterKey { get; init; } = string.Empty; public string Title { get; init; } = string.Empty; public int SortOrder { get; init; } public int WordCount { get; init; } } public sealed class OnboardingBuildSceneRequest { public string TemporarySceneKey { get; init; } = string.Empty; public string TemporaryChapterKey { get; init; } = string.Empty; public string Title { get; init; } = string.Empty; public int SortOrder { get; init; } public int WordCount { get; init; } } public sealed class OnboardingBuildCharacterRequest { public string TemporaryCharacterKey { get; init; } = string.Empty; public string Name { get; init; } = string.Empty; public int? ExistingCharacterID { get; init; } } public sealed class StructuralChapterSourceSaveRequest { public int ChapterID { get; init; } public int ProjectID { get; init; } public int BookID { get; init; } public string SourceText { get; init; } = string.Empty; public int SourceWordCount { get; init; } public int SourceCharacterCount { get; init; } public string? ParagraphMapJson { get; init; } } public sealed class SceneManuscriptSourceSaveRequest { public int SceneID { get; init; } public int ProjectID { get; init; } public int BookID { get; init; } public int ChapterID { get; init; } public string SourceText { get; init; } = string.Empty; public int SourceWordCount { get; init; } public int SourceCharacterCount { get; init; } public int? SourceStartParagraph { get; init; } public int? SourceEndParagraph { get; init; } public int? WordStartParagraphIndex { get; init; } public int? WordEndParagraphIndex { get; init; } public string SourceKind { get; init; } = "StructuralImport"; }