PlotDirector/PlotLine/Models/ImportModels.cs

421 lines
18 KiB
C#

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<ImportCharacterDto> Characters { get; set; } = [];
public List<ImportLocationDto> Locations { get; set; } = [];
public List<ImportPlotLineDto> PlotLines { get; set; } = [];
public List<ImportStoryAssetDto> StoryAssets { get; set; } = [];
public List<ImportSceneDependencyDto> SceneDependencies { get; set; } = [];
public List<ImportBookDto> Books { get; set; } = [];
[JsonExtensionData]
public Dictionary<string, object>? ExtensionData { get; set; }
}
public sealed class ImportAliasesDto
{
public Dictionary<string, string> Characters { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public Dictionary<string, string> Locations { get; set; } = new(StringComparer.OrdinalIgnoreCase);
}
public sealed class ProjectBackupRelationshipImportLookups
{
public IReadOnlyList<ProjectBackupLookupRow> RevisionStatuses { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> TimeModes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> DurationUnits { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> TimeConfidences { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> LocationTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> LocationRelationshipTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> PlotLineTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> PlotImportance { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> ThreadTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> ThreadStatuses { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> ThreadEventTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> PlotEventTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> AssetKinds { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> AssetStates { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> AssetEventTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> AssetDependencyTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> AssetCustodyEventTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> CustodyRoles { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> CharacterRoleTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> PresenceTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> KnowledgeStates { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> SceneDependencyTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> SceneNoteTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> SceneAttachmentTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> ScenePurposeTypes { get; init; } = [];
public IReadOnlyList<ProjectBackupLookupRow> SceneMetricTypes { get; init; } = [];
public IReadOnlyList<RelationshipType> RelationshipTypes { get; init; } = [];
public IReadOnlyList<RelationshipState> 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<ImportChapterDto> 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<ImportPlotThreadDto> 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<ImportThreadEventDto> 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<ImportAssetStateDto> States { get; set; } = [];
public List<ImportAssetEventDto> 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<ImportSceneDto> 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<ImportSceneCharacterDto> 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<string> Errors { get; } = [];
public List<string> Warnings { get; } = [];
public List<string> AutoCorrections { get; } = [];
public List<string> SkippedItems { get; } = [];
public List<string> 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<ImportBookPreview> BookPreviews { get; init; } = [];
public IReadOnlyList<string> UnmatchedPovNames { get; init; } = [];
public IReadOnlyList<string> UnmatchedLocationNames { get; init; } = [];
public IReadOnlyList<string> UnmatchedSceneCharacterNames { get; init; } = [];
public IReadOnlyList<string> UnresolvedThreadEventReferences { get; init; } = [];
public IReadOnlyList<string> UnresolvedAssetReferences { get; init; } = [];
public IReadOnlyList<string> UnmatchedAssetOwnerNames { get; init; } = [];
public IReadOnlyList<string> UnmatchedAssetLocationNames { get; init; } = [];
public IReadOnlyList<string> UnresolvedSceneDependencyReferences { get; init; } = [];
public IReadOnlyList<string> AliasWarnings { get; init; } = [];
public IReadOnlyList<string> AutoCorrections { get; init; } = [];
public IReadOnlyList<string> 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<ImportChapterPreview> 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<string> AutoCorrections { get; init; } = [];
public IReadOnlyList<string> SkippedOptionalFields { get; init; } = [];
public IReadOnlyList<string> UnresolvedWarnings { get; init; } = [];
public string? TechnicalDetail { get; init; }
public string Message { get; init; } = string.Empty;
}
public sealed class ImportImportanceJsonConverter : JsonConverter<int?>
{
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();
}
}
}