using System.Text.Json; using System.Text.Json.Serialization; namespace PlotLine.Models; public sealed class PlotLineImportPackage { public string? PackageVersion { get; set; } public string? Source { get; set; } public string? CreatedBy { get; set; } public string? CreatedDate { get; set; } public string? Description { get; set; } public ImportProjectDto Project { get; set; } = new(); public ImportAliasesDto Aliases { get; set; } = new(); public List Characters { get; set; } = []; public List Locations { get; set; } = []; public List PlotLines { get; set; } = []; public List StoryAssets { get; set; } = []; public List SceneDependencies { get; set; } = []; public List Books { get; set; } = []; [JsonExtensionData] public Dictionary? ExtensionData { get; set; } } public sealed class ImportAliasesDto { public Dictionary Characters { get; set; } = new(StringComparer.OrdinalIgnoreCase); public Dictionary Locations { get; set; } = new(StringComparer.OrdinalIgnoreCase); } public sealed class ProjectBackupRelationshipImportLookups { public IReadOnlyList RevisionStatuses { get; init; } = []; public IReadOnlyList TimeModes { get; init; } = []; public IReadOnlyList DurationUnits { get; init; } = []; public IReadOnlyList TimeConfidences { get; init; } = []; public IReadOnlyList LocationTypes { get; init; } = []; public IReadOnlyList LocationRelationshipTypes { get; init; } = []; public IReadOnlyList PlotLineTypes { get; init; } = []; public IReadOnlyList PlotImportance { get; init; } = []; public IReadOnlyList ThreadTypes { get; init; } = []; public IReadOnlyList ThreadStatuses { get; init; } = []; public IReadOnlyList ThreadEventTypes { get; init; } = []; public IReadOnlyList PlotEventTypes { get; init; } = []; public IReadOnlyList AssetKinds { get; init; } = []; public IReadOnlyList AssetStates { get; init; } = []; public IReadOnlyList AssetEventTypes { get; init; } = []; public IReadOnlyList AssetDependencyTypes { get; init; } = []; public IReadOnlyList AssetCustodyEventTypes { get; init; } = []; public IReadOnlyList CustodyRoles { get; init; } = []; public IReadOnlyList CharacterRoleTypes { get; init; } = []; public IReadOnlyList PresenceTypes { get; init; } = []; public IReadOnlyList KnowledgeStates { get; init; } = []; public IReadOnlyList SceneDependencyTypes { get; init; } = []; public IReadOnlyList SceneNoteTypes { get; init; } = []; public IReadOnlyList SceneAttachmentTypes { get; init; } = []; public IReadOnlyList ScenePurposeTypes { get; init; } = []; public IReadOnlyList SceneMetricTypes { get; init; } = []; public IReadOnlyList RelationshipTypes { get; init; } = []; public IReadOnlyList RelationshipStates { get; init; } = []; } public sealed class ProjectBackupLookupRow { public int LookupID { get; set; } public string LookupName { get; set; } = string.Empty; } public sealed class ProjectBackupRelationshipImportDiagnostics { public int CharacterCount { get; set; } public int RelationshipCount { get; set; } public int RelationshipEventCount { get; set; } public int MapRelationshipCount { get; set; } public int MissingSourceCharacterCount { get; set; } public int MissingTargetCharacterCount { get; set; } public int MissingRelationshipTypeCount { get; set; } public int MissingInitialStateCount { get; set; } public int MissingEventRelationshipCount { get; set; } public int MissingEventSceneCount { get; set; } public int MissingEventStateCount { get; set; } public int ArchivedRelationshipCount { get; set; } public int ArchivedCharacterEndpointCount { get; set; } } public sealed class ImportProjectDto { public string Title { get; set; } = string.Empty; public string? Description { get; set; } public string? Notes { get; set; } } public sealed class ImportBookDto { public string Title { get; set; } = string.Empty; public string? Subtitle { get; set; } public int SeriesOrder { get; set; } public bool DependsOnPreviousBook { get; set; } public string? Description { get; set; } public string? Notes { get; set; } public List Chapters { get; set; } = []; } public sealed class ImportCharacterDto { public string Name { get; set; } = string.Empty; public string? DisplayName { get; set; } public string? ShortName { get; set; } public string? Role { get; set; } [JsonConverter(typeof(ImportImportanceJsonConverter))] public int? Importance { get; set; } public string? Description { get; set; } public string? Notes { get; set; } } public sealed class ImportLocationDto { public string Name { get; set; } = string.Empty; public string? DisplayName { get; set; } public string? Type { get; set; } public string? ParentLocationName { get; set; } public string? Description { get; set; } public string? Notes { get; set; } } public sealed class ImportPlotLineDto { public string Name { get; set; } = string.Empty; public string? DisplayName { get; set; } public string? Type { get; set; } public int? Importance { get; set; } public string? Description { get; set; } public string? Notes { get; set; } public List Threads { get; set; } = []; } public sealed class ImportPlotThreadDto { public string Name { get; set; } = string.Empty; public string? DisplayName { get; set; } public string? Type { get; set; } public string? Status { get; set; } public int? Importance { get; set; } public string? Description { get; set; } public string? Notes { get; set; } public List Events { get; set; } = []; } public sealed class ImportThreadEventDto { public string? SceneRef { get; set; } public int? BookOrder { get; set; } public int? ChapterOrder { get; set; } public int? SceneOrder { get; set; } public string? Title { get; set; } public string? EventType { get; set; } public string? Summary { get; set; } public string? Impact { get; set; } public int? Importance { get; set; } public string? Notes { get; set; } } public sealed class ImportStoryAssetDto { public string Name { get; set; } = string.Empty; public string? DisplayName { get; set; } public string? Type { get; set; } public int? Importance { get; set; } public string? Description { get; set; } public string? Notes { get; set; } public string? InitialOwnerCharacterName { get; set; } public string? InitialLocationName { get; set; } public List States { get; set; } = []; public List Events { get; set; } = []; } public sealed class ImportAssetStateDto { public string? SceneRef { get; set; } public int? BookOrder { get; set; } public int? ChapterOrder { get; set; } public int? SceneOrder { get; set; } public string? StateName { get; set; } public string? Description { get; set; } public string? Notes { get; set; } } public sealed class ImportAssetEventDto { public string? SceneRef { get; set; } public int? BookOrder { get; set; } public int? ChapterOrder { get; set; } public int? SceneOrder { get; set; } public string? EventType { get; set; } public string? Title { get; set; } public string? Summary { get; set; } public string? CharacterName { get; set; } public string? LocationName { get; set; } public string? Notes { get; set; } } public sealed class ImportSceneDependencyDto { public ImportSceneReferenceDto? FromScene { get; set; } public ImportSceneReferenceDto? ToScene { get; set; } public string? DependencyType { get; set; } public string? Strength { get; set; } public string? Description { get; set; } public string? Notes { get; set; } } public sealed class ImportSceneReferenceDto { public string? SceneRef { get; set; } public int? BookOrder { get; set; } public int? ChapterOrder { get; set; } public int? SceneOrder { get; set; } } public sealed class ImportChapterDto { public decimal ChapterNumber { get; set; } public string Title { get; set; } = string.Empty; public int Order { get; set; } public string? Summary { get; set; } public string? Notes { get; set; } public List Scenes { get; set; } = []; } public sealed class ImportSceneDto { public decimal SceneNumber { get; set; } public string Title { get; set; } = string.Empty; public int Order { get; set; } public string? Summary { get; set; } public string? PovCharacterName { get; set; } public string? LocationName { get; set; } public string? DateTimeText { get; set; } public string? PurposeText { get; set; } public string? OutcomeText { get; set; } public string? Notes { get; set; } public List SceneCharacters { get; set; } = []; } public sealed class ImportSceneCharacterDto { public string CharacterName { get; set; } = string.Empty; public string? RoleInScene { get; set; } public int? Importance { get; set; } public string? Notes { get; set; } } public sealed class ImportValidationResult { public bool IsValid => Errors.Count == 0; public List Errors { get; } = []; public List Warnings { get; } = []; public List AutoCorrections { get; } = []; public List SkippedItems { get; } = []; public List UnresolvedReferences { get; } = []; } public sealed class ImportPreview { public PlotLineImportPackage? Package { get; init; } public ImportValidationResult Validation { get; init; } = new(); public bool HasDuplicateProjectTitle { get; init; } public string? DuplicateImportProjectTitle { get; init; } public IReadOnlyList BookPreviews { get; init; } = []; public IReadOnlyList UnmatchedPovNames { get; init; } = []; public IReadOnlyList UnmatchedLocationNames { get; init; } = []; public IReadOnlyList UnmatchedSceneCharacterNames { get; init; } = []; public IReadOnlyList UnresolvedThreadEventReferences { get; init; } = []; public IReadOnlyList UnresolvedAssetReferences { get; init; } = []; public IReadOnlyList UnmatchedAssetOwnerNames { get; init; } = []; public IReadOnlyList UnmatchedAssetLocationNames { get; init; } = []; public IReadOnlyList UnresolvedSceneDependencyReferences { get; init; } = []; public IReadOnlyList AliasWarnings { get; init; } = []; public IReadOnlyList AutoCorrections { get; init; } = []; public IReadOnlyList SkippedItems { get; init; } = []; public int ErrorCount => Validation.Errors.Count; public int WarningCount => Validation.Warnings.Count; public int AutoCorrectionCount => AutoCorrections.Count + Validation.AutoCorrections.Count; public int SkippedItemCount => SkippedItems.Count + Validation.SkippedItems.Count; public int UnresolvedReferenceCount => UnresolvedThreadEventReferences.Count + UnresolvedAssetReferences.Count + UnresolvedSceneDependencyReferences.Count; public int BookCount => Package?.Books.Count ?? 0; public int CharacterCount => Package?.Characters.Count ?? 0; public int LocationCount => Package?.Locations.Count ?? 0; public int PlotLineCount => Package?.PlotLines.Count ?? 0; public int PlotThreadCount => Package?.PlotLines.Sum(plotLine => plotLine.Threads.Count) ?? 0; public int ThreadEventCount => Package?.PlotLines.Sum(plotLine => plotLine.Threads.Sum(thread => thread.Events.Count)) ?? 0; public int ResolvedThreadEventCount => Math.Max(0, ThreadEventCount - UnresolvedThreadEventReferences.Count); public int StoryAssetCount => Package?.StoryAssets.Count ?? 0; public int AssetStateCount => Package?.StoryAssets.Sum(asset => asset.States.Count) ?? 0; public int AssetEventCount => Package?.StoryAssets.Sum(asset => asset.Events.Count) ?? 0; public int AssetCustodyCount => Package?.StoryAssets.Count(asset => !string.IsNullOrWhiteSpace(asset.InitialOwnerCharacterName)) ?? 0; public int ResolvedAssetStateCount => Math.Max(0, AssetStateCount - UnresolvedAssetReferences.Count(x => x.Contains("state", StringComparison.OrdinalIgnoreCase))); public int ResolvedAssetEventCount => Math.Max(0, AssetEventCount - UnresolvedAssetReferences.Count(x => x.Contains("event", StringComparison.OrdinalIgnoreCase))); public int ResolvedAssetCustodyCount => Math.Max(0, AssetCustodyCount - Package?.StoryAssets.Count(asset => !string.IsNullOrWhiteSpace(asset.InitialOwnerCharacterName) && UnmatchedAssetOwnerNames.Contains(asset.InitialOwnerCharacterName.Trim(), StringComparer.OrdinalIgnoreCase)) ?? 0); public int SceneDependencyCount => Package?.SceneDependencies.Count ?? 0; public int ResolvedSceneDependencyCount => Math.Max(0, SceneDependencyCount - UnresolvedSceneDependencyReferences.Count); public int ChapterCount => Package?.Books.Sum(book => book.Chapters.Count) ?? 0; public int SceneCount => Package?.Books.Sum(book => book.Chapters.Sum(chapter => chapter.Scenes.Count)) ?? 0; public int SceneCharacterAppearanceCount => Package?.Books.Sum(book => book.Chapters.Sum(chapter => chapter.Scenes.Sum(scene => scene.SceneCharacters.Count + AutoPovAppearanceCount(scene)))) ?? 0; private static int AutoPovAppearanceCount(ImportSceneDto scene) => !string.IsNullOrWhiteSpace(scene.PovCharacterName) && scene.SceneCharacters.All(x => !string.Equals(x.CharacterName?.Trim(), scene.PovCharacterName.Trim(), StringComparison.OrdinalIgnoreCase)) ? 1 : 0; } public sealed class ImportBookPreview { public ImportBookDto Book { get; init; } = new(); public int ChapterCount => Book.Chapters.Count; public int SceneCount => Book.Chapters.Sum(chapter => chapter.Scenes.Count); public IReadOnlyList ChapterPreviews { get; init; } = []; } public sealed class ImportChapterPreview { public ImportChapterDto Chapter { get; init; } = new(); public int SceneCount => Chapter.Scenes.Count; } public sealed record ImportResult { public bool Succeeded { get; init; } public int? ProjectID { get; init; } public string ProjectName { get; init; } = string.Empty; public int BooksCreated { get; init; } public int ChaptersCreated { get; init; } public int ScenesCreated { get; init; } public int CharactersCreated { get; init; } public int LocationsCreated { get; init; } public int SceneCharacterAppearancesCreated { get; init; } public int PlotLinesCreated { get; init; } public int PlotThreadsCreated { get; init; } public int ThreadEventsCreated { get; init; } public int StoryAssetsCreated { get; init; } public int AssetStatesCreated { get; init; } public int AssetEventsCreated { get; init; } public int AssetCustodyEventsCreated { get; init; } public int SceneDependenciesCreated { get; init; } public bool IsDryRun { get; init; } public int ErrorCount { get; init; } public int WarningCount { get; init; } public int AutoCorrectionCount { get; init; } public int SkippedItemCount { get; init; } public int UnresolvedReferenceCount { get; init; } public IReadOnlyList AutoCorrections { get; init; } = []; public IReadOnlyList SkippedOptionalFields { get; init; } = []; public IReadOnlyList UnresolvedWarnings { get; init; } = []; public string? TechnicalDetail { get; init; } public string Message { get; init; } = string.Empty; } public sealed class ImportImportanceJsonConverter : JsonConverter { public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) { return null; } if (reader.TokenType == JsonTokenType.Number && reader.TryGetInt32(out var numeric)) { return Math.Clamp(numeric, 1, 10); } if (reader.TokenType != JsonTokenType.String) { return null; } var text = reader.GetString()?.Trim(); if (string.IsNullOrWhiteSpace(text)) { return null; } if (int.TryParse(text, out var parsed)) { return Math.Clamp(parsed, 1, 10); } var normalized = text.ToLowerInvariant(); if (normalized.Contains("major") || normalized.Contains("main") || normalized.Contains("protagonist")) { return 10; } if (normalized.Contains("support") || normalized.Contains("secondary")) { return 6; } if (normalized.Contains("minor") || normalized.Contains("background") || normalized.Contains("cameo")) { return 2; } return null; } public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options) { if (value.HasValue) { writer.WriteNumberValue(value.Value); } else { writer.WriteNullValue(); } } }