using System.ComponentModel.DataAnnotations; using PlotLine.Models; namespace PlotLine.ViewModels; public sealed class OnboardingWizardViewModel { public string CurrentStep { get; set; } = OnboardingSteps.Welcome; public int StepNumber { get; set; } = 1; public int TotalSteps { get; set; } = 6; public string? WritingJourney { get; set; } public string? WritingSoftware { get; set; } public int? ProjectID { get; set; } public int? BookID { get; set; } public string? SelectedProjectName { get; set; } public string? SelectedBookTitle { get; set; } public bool IsMicrosoftWordPath => string.Equals(WritingSoftware, WritingSoftwareValues.MicrosoftWord, StringComparison.Ordinal); public CompanionPresenceViewModel CompanionPresence { get; set; } = new(); public ManuscriptScanStateViewModel ScanState { get; set; } = new(); public IReadOnlyList Options { get; set; } = []; public IReadOnlyList ProjectOptions { get; set; } = []; public IReadOnlyList BookOptions { get; set; } = []; public ProjectOnboardingForm ProjectForm { get; set; } = new(); public BookOnboardingForm BookForm { get; set; } = new(); } public sealed class OnboardingOptionViewModel { public string Value { get; init; } = string.Empty; public string Title { get; init; } = string.Empty; public string Description { get; init; } = string.Empty; public string? Badge { get; init; } } public sealed class OnboardingJourneyHeaderViewModel { public int CurrentStep { get; init; } public int TotalSteps { get; init; } = 9; public string AriaLabel { get; init; } = "Manuscript import progress"; } public sealed class StoryIntelligencePipelineHeaderViewModel { public string CurrentStage { get; init; } = "Analyse Manuscript"; } public sealed class WritingJourneyOnboardingForm { [Required(ErrorMessage = "Choose the closest writing stage before continuing.")] public string? WritingJourney { get; set; } } public sealed class WritingSoftwareOnboardingForm { [Required(ErrorMessage = "Choose the software you write in before continuing.")] public string? WritingSoftware { get; set; } } public static class OnboardingSelectionModes { public const string Create = "Create"; public const string Select = "Select"; } public sealed class ProjectOnboardingForm { [Required] public string Mode { get; set; } = OnboardingSelectionModes.Create; public int? SelectedProjectID { get; set; } [StringLength(200)] public string? ProjectName { get; set; } public string? Description { get; set; } } public sealed class BookOnboardingForm { [Required] public string Mode { get; set; } = OnboardingSelectionModes.Create; public int? SelectedBookID { get; set; } [StringLength(200)] public string? BookTitle { get; set; } [StringLength(200)] public string? Subtitle { get; set; } [Range(1, int.MaxValue, ErrorMessage = "Target word count must be greater than zero.")] public int? TargetWordCount { get; set; } } public sealed class OnboardingProjectOptionViewModel { public int ProjectID { get; init; } public string ProjectName { get; init; } = string.Empty; public string Description { get; init; } = string.Empty; } public sealed class OnboardingBookOptionViewModel { public int BookID { get; init; } public string DisplayTitle { get; init; } = string.Empty; public string Detail { get; init; } = string.Empty; } public sealed class OnboardingNudgeViewModel { public bool ShouldShow { get; init; } public string ButtonText { get; init; } = "Start setup"; public string Title { get; init; } = "Set up PlotDirector around the way you write"; public string Description { get; init; } = "Answer a few quick questions so PlotDirector can guide your first project."; public bool IsCompanionPresence { get; init; } public bool CompanionConnected { get; init; } } public sealed class StoryIntelligenceOverviewViewModel { public Guid? PreviewID { get; init; } public int? ProjectID { get; init; } public int? BookID { get; init; } public string? SelectedProjectName { get; init; } public string? SelectedBookTitle { get; init; } public int ApprovedChapterCount { get; init; } public int ApprovedWordCount { get; init; } public int MissingChapterTextCount { get; init; } public string? Message { get; init; } public Guid? ExistingBatchID { get; init; } public StoryIntelligenceJobProgress? ExistingJob { get; init; } public bool CanStart { get; init; } } public sealed class StoryIntelligenceProgressViewModel { public StoryIntelligenceJobProgress Job { get; init; } = new(); public Guid? BatchID { get; init; } public Guid? PreviewID { get; init; } public int ProjectID { get; init; } public int BookID { get; init; } public string? ProjectName { get; init; } public string? BookTitle { get; init; } public IReadOnlyList Chapters { get; init; } = []; public StoryIntelligenceCharacterReviewViewModel CharacterReview { get; init; } = new(); public StoryIntelligencePipelineDashboardViewModel PipelineDashboard { get; init; } = new(); public int ChapterCount => Chapters.Count; public int CompletedChapterCount => Chapters.Count(chapter => chapter.IsRunComplete); public int CommittedChapterCount => Chapters.Count(chapter => chapter.HasCompletedCommit); public int FailedChapterCount => Chapters.Count(chapter => chapter.IsFailed); public int TotalDetectedScenes => Chapters.Sum(chapter => chapter.TotalDetectedScenes ?? 0); public int TotalCompletedScenes => Chapters.Sum(chapter => chapter.CompletedScenes ?? 0); public int TotalFailedScenes => Chapters.Sum(chapter => chapter.FailedScenes ?? 0); public int TotalAnalysedWords => Chapters.Sum(chapter => chapter.SourceWordCount ?? 0); public decimal? TotalEstimatedCostUSD => Chapters.Any(chapter => chapter.EstimatedCostUSD.HasValue) ? Chapters.Sum(chapter => chapter.EstimatedCostUSD ?? 0m) : null; public long TotalDurationMs => Chapters.Sum(chapter => chapter.TotalDurationMs ?? 0); public bool HasActiveRuns => Chapters.Any(chapter => chapter.IsActive); public bool AllCommitted => Chapters.Count > 0 && Chapters.All(chapter => chapter.HasCompletedCommit); public bool HasReadyScenesToCreate => Chapters.Any(chapter => chapter.CanCommit); public bool HasCharactersToReview => CharacterReview.Candidates.Count > 0; } public sealed class StoryIntelligencePipelineDashboardViewModel { public int ChaptersAnalysed { get; init; } public int ScenesImported { get; init; } public int CharactersIdentified { get; init; } public int CharactersCreatedOrLinked { get; init; } public bool CharacterStageComplete { get; init; } } public sealed class StoryIntelligenceCharacterReviewViewModel { public bool CanImport { get; init; } public bool HasCommittedScenes { get; init; } public int AlreadyLinkedCount { get; init; } public bool IsComplete { get; init; } public IReadOnlyList Candidates { get; init; } = []; } public sealed class StoryIntelligenceCharacterReviewCandidateViewModel { public string Key { get; init; } = string.Empty; public string CharacterName { get; init; } = string.Empty; public string ImportName { get; init; } = string.Empty; public int AppearsInScenes { get; init; } public string Confidence { get; init; } = "Unknown"; public IReadOnlyList PossibleAliases { get; init; } = []; public string? ExampleFirstAppearance { get; init; } public int? ExistingCharacterID { get; init; } public string? ExistingCharacterName { get; init; } public bool IsExistingMatch => ExistingCharacterID.HasValue; public string ActionLabel => IsExistingMatch ? "Link existing" : "Create"; } public sealed class StoryIntelligenceCharacterImportForm { public Guid BatchID { get; set; } public List Characters { get; set; } = []; } public sealed class StoryIntelligenceCharacterImportChoiceForm { public string Key { get; set; } = string.Empty; public string Action { get; set; } = StoryIntelligenceCharacterImportActions.Approve; public string? ImportName { get; set; } public string? AliasTargetKey { get; set; } } public static class StoryIntelligenceCharacterImportActions { public const string Approve = "Approve"; public const string Ignore = "Ignore"; public const string CreateSeparate = "CreateSeparate"; public const string Alias = "Alias"; } public sealed class StoryIntelligenceCompletionViewModel { public StoryIntelligenceJobProgress Job { get; init; } = new(); public Guid? BatchID { get; init; } public int ProjectID { get; init; } public int BookID { get; init; } public int? FirstChapterID { get; init; } public string? ProjectName { get; init; } public string? BookTitle { get; init; } public int ChaptersCommitted { get; init; } public int ScenesCreated { get; init; } public int CharactersCreatedOrLinked { get; init; } public bool CharacterStageComplete { get; init; } } public sealed class StoryIntelligenceCharacterImportResultViewModel { public Guid BatchID { get; init; } public int ProjectID { get; init; } public int BookID { get; init; } public int CharactersCreated { get; init; } public int CharactersLinked { get; init; } public int CharactersAliased { get; init; } public int CharactersIgnored { get; init; } public int PovLinksResolved { get; init; } public int ScenePeoplePanelsUpdated { get; init; } } public sealed class StoryIntelligenceOnboardingChapterViewModel { public string TemporaryChapterKey { get; init; } = string.Empty; public int ChapterNumber { get; init; } public string ChapterTitle { get; init; } = string.Empty; public int ChapterID { get; init; } public int RunID { get; init; } public string Status { get; init; } = string.Empty; public string? CurrentStage { get; init; } public string? CurrentMessage { get; init; } public int? TotalDetectedScenes { get; init; } public int? CompletedScenes { get; init; } public int? FailedScenes { get; init; } public int? SourceWordCount { get; init; } public int? TotalTokens { get; init; } public long? TotalDurationMs { get; init; } public decimal? EstimatedCostUSD { get; init; } public decimal? EstimatedCostGBP { get; init; } public string? FailureStage { get; init; } public string? ErrorMessage { get; init; } public string? LatestSceneSummary { get; init; } public bool HasCompletedCommit { get; init; } public bool CanCommit { get; init; } public int ScenesCreated { get; init; } public string? CommitMessage { get; init; } public IReadOnlyList Blockers { get; init; } = []; public IReadOnlyList Warnings { get; init; } = []; public IReadOnlyList Scenes { get; init; } = []; public decimal? AverageConfidence => Scenes.Count == 0 ? null : Scenes.Average(scene => scene.Confidence ?? 0m); public bool IsActive => Status is StoryIntelligenceRunStatuses.Pending or StoryIntelligenceRunStatuses.Running; public bool IsRunComplete => Status is StoryIntelligenceRunStatuses.Completed or StoryIntelligenceRunStatuses.CompletedWithWarnings; public bool IsFailed => Status is StoryIntelligenceRunStatuses.Failed or StoryIntelligenceRunStatuses.Cancelled; } public sealed class StoryIntelligenceOnboardingScenePreviewViewModel { public int SceneNumber { get; init; } public string Summary { get; init; } = string.Empty; public string? Pov { get; init; } public string? Setting { get; init; } public decimal? Confidence { get; init; } public bool HasWarnings { get; init; } } public sealed class StoryIntelligenceDashboardViewModel { public bool ShouldShow { get; init; } public string Title { get; init; } = "Story Intelligence available"; public string Description { get; init; } = "Prepare the optional analysis framework for your manuscript."; public string ButtonText { get; init; } = "Set up Story Intelligence"; public StoryIntelligenceJobProgress? Job { get; init; } public Guid? ActiveBatchID { get; init; } public string? ActiveImportBookTitle { get; init; } public string? ActiveImportProgressLabel { get; init; } public IReadOnlyList PipelineBooks { get; init; } = []; public bool HasActiveImport => ActiveBatchID.HasValue; public bool HasPipelineBooks => PipelineBooks.Count > 0; } public sealed class StoryIntelligencePipelineBookStatusViewModel { public int BookID { get; init; } public string BookTitle { get; init; } = string.Empty; public string StatusLabel { get; init; } = "Story Intelligence Needs Review"; public string Description { get; init; } = string.Empty; public bool IsComplete { get; init; } } public sealed class CompanionPresenceViewModel { public bool IsConnected { get; init; } public bool DocumentOpen { get; init; } public string Status { get; init; } = "Offline"; public string? CompanionVersion { get; init; } public string? CurrentDocumentName { get; init; } public DateTime? LastHeartbeatUtc { get; init; } } public sealed class ManuscriptScanStateViewModel { public string Status { get; init; } = ManuscriptScanStatuses.NotStarted; public string ReviewStatus { get; init; } = ManuscriptScanReviewStatuses.ScanComplete; public string Message { get; init; } = string.Empty; public int? PercentComplete { get; init; } public int ChapterCount { get; init; } public int SceneCount { get; init; } public int CharacterCandidateCount { get; init; } public int TotalWordCount { get; init; } public Guid? PreviewID { get; init; } public bool IsRunning => string.Equals(Status, ManuscriptScanStatuses.Running, StringComparison.Ordinal); public bool IsComplete => string.Equals(Status, ManuscriptScanStatuses.Complete, StringComparison.Ordinal); public bool IsFailed => string.Equals(Status, ManuscriptScanStatuses.Failed, StringComparison.Ordinal); } public sealed class ManuscriptScanReviewViewModel { public Guid PreviewID { get; init; } public string ReviewStatus { get; init; } = ManuscriptScanReviewStatuses.ScanComplete; public string DocumentTitle { get; init; } = "Word manuscript"; public string SelectedProjectName { get; init; } = string.Empty; public string SelectedBookTitle { get; init; } = string.Empty; public int TotalWordCount { get; init; } public int ChapterCount { get; init; } public int SceneCount { get; init; } public int CharacterCandidateCount { get; init; } public int SelectedChapterCount { get; init; } public int SelectedSceneCount { get; init; } public int SelectedCharacterCount { get; init; } public int SelectedWordCount { get; init; } public IReadOnlyList Chapters { get; init; } = []; public IReadOnlyList CharacterCandidates { get; init; } = []; public IReadOnlyList ExistingCharacterOptions { get; init; } = []; } public sealed class ManuscriptScanReviewChapterViewModel { public string TemporaryChapterKey { get; init; } = string.Empty; public int ChapterNumber { get; init; } public string Title { get; init; } = string.Empty; public string ReviewTitle { get; init; } = string.Empty; public bool Include { get; init; } public int WordCount { get; init; } public IReadOnlyList Scenes { get; init; } = []; } public sealed class ManuscriptScanReviewSceneViewModel { public string TemporarySceneKey { get; init; } = string.Empty; public string TemporaryChapterKey { get; init; } = string.Empty; public int SceneNumberWithinChapter { get; init; } public string? Title { get; init; } public string? ReviewTitle { get; init; } public bool Include { get; init; } public int WordCount { get; init; } public string? OpeningTextPreview { get; init; } } public sealed class ManuscriptScanReviewCharacterViewModel { public string TemporaryCharacterKey { get; init; } = string.Empty; public string Name { get; init; } = string.Empty; public string ReviewName { get; init; } = string.Empty; public bool Include { get; init; } public int MentionCount { get; init; } public int QualityScore { get; init; } public string Category { get; init; } = "PossibleCharacter"; public string? Reason { get; init; } public int? ExistingCharacterID { get; init; } public string? ExistingCharacterName { get; init; } public bool IsExistingCharacterMatch => ExistingCharacterID.HasValue; } public sealed class ManuscriptScanExistingCharacterOptionViewModel { public int CharacterID { get; init; } public string CharacterName { get; init; } = string.Empty; } public sealed class ManuscriptScanReviewForm { public Guid PreviewID { get; set; } public List Chapters { get; set; } = []; public List Scenes { get; set; } = []; public List Characters { get; set; } = []; } public sealed class ManuscriptScanChapterReviewFormItem { public string TemporaryChapterKey { get; set; } = string.Empty; public bool Include { get; set; } public string Title { get; set; } = string.Empty; public int ChapterNumber { get; set; } } public sealed class ManuscriptScanSceneReviewFormItem { public string TemporarySceneKey { get; set; } = string.Empty; public string TemporaryChapterKey { get; set; } = string.Empty; public bool Include { get; set; } public string? Title { get; set; } public int SceneNumberWithinChapter { get; set; } public bool AllowZeroWords { get; set; } } public sealed class ManuscriptScanCharacterReviewFormItem { public string TemporaryCharacterKey { get; set; } = string.Empty; public bool Include { get; set; } public string Name { get; set; } = string.Empty; public string Category { get; set; } = "PossibleCharacter"; public int? ExistingCharacterID { get; set; } }