From 16ea855a3ec3b1ee513786ebe8ebfc7ddb11d7ba Mon Sep 17 00:00:00 2001 From: Nick Beckley Date: Fri, 3 Jul 2026 23:19:51 +0100 Subject: [PATCH] Phase 20F Build the approved manuscript structure into a real PlotDirector project. --- PlotLine/Controllers/OnboardingController.cs | 7 + PlotLine/Data/OnboardingBuildRepository.cs | 99 ++++++ PlotLine/Hubs/WordCompanionFollowHub.cs | 32 +- PlotLine/Models/ManuscriptScanModels.cs | 53 ++++ PlotLine/Program.cs | 1 + .../Services/ManuscriptScanPreviewStore.cs | 45 +++ PlotLine/Services/OnboardingService.cs | 82 +++++ ...113_Phase20F_OnboardingManuscriptBuild.sql | 296 ++++++++++++++++++ .../Views/Onboarding/BuildComplete.cshtml | 43 +++ PlotLine/Views/Onboarding/Index.cshtml | 9 +- PlotLine/wwwroot/js/word-companion-host.js | 40 +++ .../wwwroot/js/word-companion-host.min.js | 2 +- .../wwwroot/js/word-companion-presence.js | 36 +++ 13 files changed, 742 insertions(+), 3 deletions(-) create mode 100644 PlotLine/Data/OnboardingBuildRepository.cs create mode 100644 PlotLine/Sql/113_Phase20F_OnboardingManuscriptBuild.sql create mode 100644 PlotLine/Views/Onboarding/BuildComplete.cshtml diff --git a/PlotLine/Controllers/OnboardingController.cs b/PlotLine/Controllers/OnboardingController.cs index 0d654c1..92b1f4d 100644 --- a/PlotLine/Controllers/OnboardingController.cs +++ b/PlotLine/Controllers/OnboardingController.cs @@ -24,6 +24,13 @@ public sealed class OnboardingController(IOnboardingService onboarding) : Contro return model is null ? NotFound() : View(model); } + [HttpGet("build-complete")] + public async Task BuildComplete(Guid previewId) + { + var model = await onboarding.GetBuildResultAsync(previewId); + return model is null ? NotFound() : View(model); + } + [HttpPost("scan-review")] [ValidateAntiForgeryToken] public async Task SaveScanReview(ManuscriptScanReviewForm form, string intent = "save") diff --git a/PlotLine/Data/OnboardingBuildRepository.cs b/PlotLine/Data/OnboardingBuildRepository.cs new file mode 100644 index 0000000..eab12be --- /dev/null +++ b/PlotLine/Data/OnboardingBuildRepository.cs @@ -0,0 +1,99 @@ +using System.Data; +using System.Text.Json; +using Dapper; +using PlotLine.Models; + +namespace PlotLine.Data; + +public interface IOnboardingBuildRepository +{ + Task BuildAsync(int userId, OnboardingManuscriptBuildRequest 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 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; } +} diff --git a/PlotLine/Hubs/WordCompanionFollowHub.cs b/PlotLine/Hubs/WordCompanionFollowHub.cs index 6ebefb2..47753e2 100644 --- a/PlotLine/Hubs/WordCompanionFollowHub.cs +++ b/PlotLine/Hubs/WordCompanionFollowHub.cs @@ -11,7 +11,8 @@ namespace PlotLine.Hubs; public sealed class WordCompanionFollowHub( IWordCompanionPresenceService presence, IOnboardingRepository onboarding, - IManuscriptScanPreviewStore scanStore) : Hub + IManuscriptScanPreviewStore scanStore, + IOnboardingService onboardingService) : Hub { public override async Task OnConnectedAsync() { @@ -126,6 +127,35 @@ public sealed class WordCompanionFollowHub( return scanState; } + public async Task StartOnboardingProjectBuild(Guid previewId) + { + var userId = RequireUserId(); + await Clients.Group(PresenceGroup(userId)).SendAsync("OnboardingBuildProgress", new OnboardingBuildProgress + { + Status = "Running", + Message = "Creating chapters...", + PercentComplete = 0, + PreviewID = previewId + }); + + var result = await onboardingService.BuildApprovedStructureAsync( + previewId, + progress => Clients.Group(PresenceGroup(userId)).SendAsync("OnboardingBuildProgress", progress)); + if (result is null) + { + throw new HubException("The reviewed scan could not be found."); + } + + var companionConnectionId = await presence.GetCompanionConnectionIdAsync(userId); + if (!string.IsNullOrWhiteSpace(companionConnectionId)) + { + await Clients.Client(companionConnectionId).SendAsync("UpdateOnboardingBuildMarkers", result); + } + + await Clients.Group(PresenceGroup(userId)).SendAsync("OnboardingBuildCompleted", result); + return result; + } + public override async Task OnDisconnectedAsync(Exception? exception) { var status = await presence.DisconnectAsync(Context.ConnectionId); diff --git a/PlotLine/Models/ManuscriptScanModels.cs b/PlotLine/Models/ManuscriptScanModels.cs index d529f41..03a873b 100644 --- a/PlotLine/Models/ManuscriptScanModels.cs +++ b/PlotLine/Models/ManuscriptScanModels.cs @@ -153,3 +153,56 @@ public sealed class ManuscriptScanCharacterReviewDecision public string Category { get; init; } = "PossibleCharacter"; public int? ExistingCharacterID { get; init; } } + +public sealed class OnboardingManuscriptBuildResult +{ + public int BuildID { get; init; } + public Guid PreviewID { get; init; } + public int ProjectID { get; init; } + public int BookID { get; init; } + public int? ManuscriptDocumentID { get; init; } + public string Status { get; init; } = "Built"; + public string MarkerStatus { get; init; } = "Pending"; + public int ChaptersCreated { get; init; } + public int ScenesCreated { get; init; } + public int CharactersCreated { get; init; } + public int CharactersReused { get; init; } + public int SceneAppearancesCreated { get; init; } + public bool AlreadyBuilt { get; init; } + public string Message { get; init; } = string.Empty; + public IReadOnlyList ChapterMappings { get; init; } = []; + public IReadOnlyList SceneMappings { get; init; } = []; + public IReadOnlyList CharacterMappings { get; init; } = []; + public string? MarkerWarning { get; init; } +} + +public sealed class OnboardingBuildChapterMap +{ + public string TemporaryChapterKey { get; init; } = string.Empty; + public int ChapterID { get; init; } +} + +public sealed class OnboardingBuildSceneMap +{ + public string TemporarySceneKey { get; init; } = string.Empty; + public int SceneID { get; init; } +} + +public sealed class OnboardingBuildCharacterMap +{ + public string TemporaryCharacterKey { get; init; } = string.Empty; + public int CharacterID { get; init; } + public bool Created { get; init; } +} + +public sealed class OnboardingBuildProgress +{ + public string Status { get; init; } = "Running"; + public string Message { get; init; } = string.Empty; + public int? PercentComplete { get; init; } + public int ChaptersCreated { get; init; } + public int ScenesCreated { get; init; } + public int CharactersCreated { get; init; } + public int SceneAppearancesCreated { get; init; } + public Guid? PreviewID { get; init; } +} diff --git a/PlotLine/Program.cs b/PlotLine/Program.cs index b1c4644..1e1da91 100644 --- a/PlotLine/Program.cs +++ b/PlotLine/Program.cs @@ -132,6 +132,7 @@ public class Program builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/PlotLine/Services/ManuscriptScanPreviewStore.cs b/PlotLine/Services/ManuscriptScanPreviewStore.cs index 42cd94a..6eb9746 100644 --- a/PlotLine/Services/ManuscriptScanPreviewStore.cs +++ b/PlotLine/Services/ManuscriptScanPreviewStore.cs @@ -15,6 +15,9 @@ public interface IManuscriptScanPreviewStore Task GetPreviewAsync(int userId, Guid previewId); Task GetReviewAsync(int userId, Guid previewId); Task SaveReviewAsync(int userId, ManuscriptScanReviewDecision review); + Task BuildProgressAsync(int userId, Guid previewId, string message, int percentComplete, OnboardingManuscriptBuildResult? result = null); + Task GetBuildResultAsync(int userId, Guid previewId); + Task SaveBuildResultAsync(int userId, OnboardingManuscriptBuildResult result); } public sealed class ManuscriptScanPreviewStore : IManuscriptScanPreviewStore @@ -22,6 +25,8 @@ public sealed class ManuscriptScanPreviewStore : IManuscriptScanPreviewStore private readonly ConcurrentDictionary<(int UserId, int OnboardingId), ScanSession> sessions = new(); private readonly ConcurrentDictionary previews = new(); private readonly ConcurrentDictionary reviews = new(); + private readonly ConcurrentDictionary buildResults = new(); + private readonly ConcurrentDictionary buildProgress = new(); public Task StartAsync(int userId, int onboardingId, int projectId, int bookId) { @@ -173,6 +178,46 @@ public sealed class ManuscriptScanPreviewStore : IManuscriptScanPreviewStore return Task.FromResult(stored); } + public Task BuildProgressAsync(int userId, Guid previewId, string message, int percentComplete, OnboardingManuscriptBuildResult? result = null) + { + if (!previews.TryGetValue(previewId, out var preview) || preview.UserID != userId) + { + return Task.FromResult(new OnboardingBuildProgress { Status = "Failed", Message = "The scan preview could not be found." }); + } + + var progress = new OnboardingBuildProgress + { + Status = result is null ? "Running" : "Complete", + Message = message, + PercentComplete = percentComplete, + ChaptersCreated = result?.ChaptersCreated ?? 0, + ScenesCreated = result?.ScenesCreated ?? 0, + CharactersCreated = (result?.CharactersCreated ?? 0) + (result?.CharactersReused ?? 0), + SceneAppearancesCreated = result?.SceneAppearancesCreated ?? 0, + PreviewID = previewId + }; + buildProgress[previewId] = progress; + return Task.FromResult(progress); + } + + public Task GetBuildResultAsync(int userId, Guid previewId) + => Task.FromResult(previews.TryGetValue(previewId, out var preview) + && preview.UserID == userId + && buildResults.TryGetValue(previewId, out var result) + ? result + : null); + + public Task SaveBuildResultAsync(int userId, OnboardingManuscriptBuildResult result) + { + if (!previews.TryGetValue(result.PreviewID, out var preview) || preview.UserID != userId) + { + return Task.FromResult(null); + } + + buildResults[result.PreviewID] = result; + return Task.FromResult(result); + } + private static ManuscriptScanState ToState(ScanSession session) => new() { diff --git a/PlotLine/Services/OnboardingService.cs b/PlotLine/Services/OnboardingService.cs index 8679553..53fdfdd 100644 --- a/PlotLine/Services/OnboardingService.cs +++ b/PlotLine/Services/OnboardingService.cs @@ -10,6 +10,8 @@ public interface IOnboardingService Task GetWizardAsync(); Task GetScanReviewAsync(Guid? previewId = null); Task SaveScanReviewAsync(ManuscriptScanReviewForm form, bool readyToImport); + Task BuildApprovedStructureAsync(Guid previewId, Func? progress = null); + Task GetBuildResultAsync(Guid previewId); Task ShouldShowOnboardingAsync(); Task GetDashboardNudgeAsync(); Task ShouldShowWordCompanionHeaderAsync(); @@ -31,6 +33,7 @@ public sealed class OnboardingService( IWordCompanionPresenceService companionPresence, IManuscriptScanPreviewStore scanPreviews, ICharacterRepository characters, + IOnboardingBuildRepository builds, ICurrentUserService currentUser) : IOnboardingService { private static readonly HashSet WritingJourneys = new(StringComparer.Ordinal) @@ -184,6 +187,85 @@ public sealed class OnboardingService( return saved is null ? null : BuildScanReviewViewModel((userId, preview, saved, project, book, existingCharacters)); } + public async Task BuildApprovedStructureAsync(Guid previewId, Func? progress = null) + { + var context = await GetScanReviewContextAsync(previewId); + if (context is null) + { + return null; + } + + var (userId, preview, review, _, _, _) = context.Value; + if (!string.Equals(review.Status, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal)) + { + throw new InvalidOperationException("Review the scan and choose Save and continue before building the project structure."); + } + + await PublishBuildProgress(userId, preview.PreviewID, "Creating chapters...", 10, progress); + ValidateScanReview(review, preview); + + var chapterDecisions = review.Chapters.Where(chapter => chapter.Include).OrderBy(chapter => chapter.ChapterNumber).ToList(); + var chapterKeys = chapterDecisions.Select(chapter => chapter.TemporaryChapterKey).ToHashSet(StringComparer.Ordinal); + var sceneDecisions = review.Scenes + .Where(scene => scene.Include && chapterKeys.Contains(scene.TemporaryChapterKey)) + .OrderBy(scene => chapterDecisions.FindIndex(chapter => chapter.TemporaryChapterKey == scene.TemporaryChapterKey)) + .ThenBy(scene => scene.SceneNumberWithinChapter) + .ToList(); + var characterDecisions = review.Characters + .Where(character => character.Include && !string.Equals(character.Category, "Excluded", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + await PublishBuildProgress(userId, preview.PreviewID, "Creating scenes...", 35, progress); + var request = new OnboardingManuscriptBuildRequest + { + PreviewID = preview.PreviewID, + ProjectID = preview.ProjectID, + BookID = preview.BookID, + DocumentGuid = Guid.TryParse(preview.CompanionDocumentIdentifier, out var documentGuid) ? documentGuid : Guid.NewGuid(), + Chapters = chapterDecisions.Select((chapter, index) => new OnboardingBuildChapterRequest + { + TemporaryChapterKey = chapter.TemporaryChapterKey, + Title = chapter.Title, + SortOrder = (index + 1) * 10, + WordCount = preview.Chapters.FirstOrDefault(item => item.TemporaryChapterKey == chapter.TemporaryChapterKey)?.WordCount ?? 0 + }).ToList(), + Scenes = sceneDecisions.Select((scene, index) => new OnboardingBuildSceneRequest + { + TemporarySceneKey = scene.TemporarySceneKey, + TemporaryChapterKey = scene.TemporaryChapterKey, + Title = string.IsNullOrWhiteSpace(scene.Title) ? $"Scene {scene.SceneNumberWithinChapter}" : scene.Title!, + SortOrder = (index + 1) * 10, + WordCount = preview.Scenes.FirstOrDefault(item => item.TemporarySceneKey == scene.TemporarySceneKey)?.WordCount ?? 0 + }).ToList(), + Characters = characterDecisions.Select(character => new OnboardingBuildCharacterRequest + { + TemporaryCharacterKey = character.TemporaryCharacterKey, + Name = character.Name, + ExistingCharacterID = character.ExistingCharacterID + }).ToList() + }; + + await PublishBuildProgress(userId, preview.PreviewID, "Creating characters...", 60, progress); + var result = await builds.BuildAsync(userId, request) + ?? throw new InvalidOperationException("The project structure could not be built. Check the review choices and try again."); + await scanPreviews.SaveBuildResultAsync(userId, result); + await PublishBuildProgress(userId, preview.PreviewID, "Finalising project...", 90, progress); + await PublishBuildProgress(userId, preview.PreviewID, result.Message, 100, progress, result); + return result; + } + + public async Task GetBuildResultAsync(Guid previewId) + => await scanPreviews.GetBuildResultAsync(RequireUserId(), previewId); + + private async Task PublishBuildProgress(int userId, Guid previewId, string message, int percent, Func? publish, OnboardingManuscriptBuildResult? result = null) + { + var state = await scanPreviews.BuildProgressAsync(userId, previewId, message, percent, result); + if (publish is not null) + { + await publish(state); + } + } + private async Task<(int UserId, ManuscriptScanPreview Preview, ManuscriptScanReviewDecision Review, Project Project, Book Book, IReadOnlyList ExistingCharacters)?> GetScanReviewContextAsync(Guid? previewId) { var userId = RequireUserId(); diff --git a/PlotLine/Sql/113_Phase20F_OnboardingManuscriptBuild.sql b/PlotLine/Sql/113_Phase20F_OnboardingManuscriptBuild.sql new file mode 100644 index 0000000..bbef12d --- /dev/null +++ b/PlotLine/Sql/113_Phase20F_OnboardingManuscriptBuild.sql @@ -0,0 +1,296 @@ +SET ANSI_NULLS ON; +GO +SET QUOTED_IDENTIFIER ON; +GO + +IF OBJECT_ID(N'dbo.OnboardingManuscriptBuilds', N'U') IS NULL +BEGIN + CREATE TABLE dbo.OnboardingManuscriptBuilds + ( + OnboardingManuscriptBuildID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_OnboardingManuscriptBuilds PRIMARY KEY, + PreviewID uniqueidentifier NOT NULL, + UserID int NOT NULL, + ProjectID int NOT NULL, + BookID int NOT NULL, + ManuscriptDocumentID int NULL, + Status nvarchar(40) NOT NULL CONSTRAINT DF_OnboardingManuscriptBuilds_Status DEFAULT N'Built', + MarkerStatus nvarchar(40) NOT NULL CONSTRAINT DF_OnboardingManuscriptBuilds_MarkerStatus DEFAULT N'Pending', + ChaptersCreated int NOT NULL CONSTRAINT DF_OnboardingManuscriptBuilds_ChaptersCreated DEFAULT 0, + ScenesCreated int NOT NULL CONSTRAINT DF_OnboardingManuscriptBuilds_ScenesCreated DEFAULT 0, + CharactersCreated int NOT NULL CONSTRAINT DF_OnboardingManuscriptBuilds_CharactersCreated DEFAULT 0, + CharactersReused int NOT NULL CONSTRAINT DF_OnboardingManuscriptBuilds_CharactersReused DEFAULT 0, + SceneAppearancesCreated int NOT NULL CONSTRAINT DF_OnboardingManuscriptBuilds_SceneAppearancesCreated DEFAULT 0, + CreatedUtc datetime2 NOT NULL CONSTRAINT DF_OnboardingManuscriptBuilds_CreatedUtc DEFAULT SYSUTCDATETIME(), + UpdatedUtc datetime2 NOT NULL CONSTRAINT DF_OnboardingManuscriptBuilds_UpdatedUtc DEFAULT SYSUTCDATETIME(), + CONSTRAINT UQ_OnboardingManuscriptBuilds_PreviewID UNIQUE (PreviewID), + CONSTRAINT FK_OnboardingManuscriptBuilds_AppUser FOREIGN KEY (UserID) REFERENCES dbo.AppUser(UserID), + CONSTRAINT FK_OnboardingManuscriptBuilds_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID), + CONSTRAINT FK_OnboardingManuscriptBuilds_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID), + CONSTRAINT FK_OnboardingManuscriptBuilds_ManuscriptDocuments FOREIGN KEY (ManuscriptDocumentID) REFERENCES dbo.ManuscriptDocuments(ManuscriptDocumentID) + ); +END; +GO + +IF OBJECT_ID(N'dbo.OnboardingManuscriptBuildChapterMap', N'U') IS NULL +BEGIN + CREATE TABLE dbo.OnboardingManuscriptBuildChapterMap + ( + OnboardingManuscriptBuildID int NOT NULL, + TemporaryChapterKey nvarchar(80) NOT NULL, + ChapterID int NOT NULL, + CONSTRAINT PK_OnboardingManuscriptBuildChapterMap PRIMARY KEY (OnboardingManuscriptBuildID, TemporaryChapterKey), + CONSTRAINT FK_OnboardingManuscriptBuildChapterMap_Build FOREIGN KEY (OnboardingManuscriptBuildID) REFERENCES dbo.OnboardingManuscriptBuilds(OnboardingManuscriptBuildID), + CONSTRAINT FK_OnboardingManuscriptBuildChapterMap_Chapters FOREIGN KEY (ChapterID) REFERENCES dbo.Chapters(ChapterID) + ); +END; +GO + +IF OBJECT_ID(N'dbo.OnboardingManuscriptBuildSceneMap', N'U') IS NULL +BEGIN + CREATE TABLE dbo.OnboardingManuscriptBuildSceneMap + ( + OnboardingManuscriptBuildID int NOT NULL, + TemporarySceneKey nvarchar(80) NOT NULL, + SceneID int NOT NULL, + CONSTRAINT PK_OnboardingManuscriptBuildSceneMap PRIMARY KEY (OnboardingManuscriptBuildID, TemporarySceneKey), + CONSTRAINT FK_OnboardingManuscriptBuildSceneMap_Build FOREIGN KEY (OnboardingManuscriptBuildID) REFERENCES dbo.OnboardingManuscriptBuilds(OnboardingManuscriptBuildID), + CONSTRAINT FK_OnboardingManuscriptBuildSceneMap_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID) + ); +END; +GO + +IF OBJECT_ID(N'dbo.OnboardingManuscriptBuildCharacterMap', N'U') IS NULL +BEGIN + CREATE TABLE dbo.OnboardingManuscriptBuildCharacterMap + ( + OnboardingManuscriptBuildID int NOT NULL, + TemporaryCharacterKey nvarchar(80) NOT NULL, + CharacterID int NOT NULL, + Created bit NOT NULL, + CONSTRAINT PK_OnboardingManuscriptBuildCharacterMap PRIMARY KEY (OnboardingManuscriptBuildID, TemporaryCharacterKey), + CONSTRAINT FK_OnboardingManuscriptBuildCharacterMap_Build FOREIGN KEY (OnboardingManuscriptBuildID) REFERENCES dbo.OnboardingManuscriptBuilds(OnboardingManuscriptBuildID), + CONSTRAINT FK_OnboardingManuscriptBuildCharacterMap_Characters FOREIGN KEY (CharacterID) REFERENCES dbo.Characters(CharacterID) + ); +END; +GO + +CREATE OR ALTER PROCEDURE dbo.Onboarding_ManuscriptBuild_Create + @UserID int, + @PreviewID uniqueidentifier, + @ProjectID int, + @BookID int, + @DocumentGuid uniqueidentifier, + @BindingVersion int = 1, + @ChaptersJson nvarchar(max), + @ScenesJson nvarchar(max), + @CharactersJson nvarchar(max) +AS +BEGIN + SET NOCOUNT ON; + SET XACT_ABORT ON; + + IF @DocumentGuid IS NULL OR @DocumentGuid = '00000000-0000-0000-0000-000000000000' + SET @DocumentGuid = NEWID(); + + DECLARE @ExistingBuildID int; + SELECT @ExistingBuildID = OnboardingManuscriptBuildID + FROM dbo.OnboardingManuscriptBuilds + WHERE PreviewID = @PreviewID AND UserID = @UserID; + + IF @ExistingBuildID IS NOT NULL + BEGIN + SELECT OnboardingManuscriptBuildID AS BuildID, PreviewID, ProjectID, BookID, ManuscriptDocumentID, Status, MarkerStatus, + ChaptersCreated, ScenesCreated, CharactersCreated, CharactersReused, SceneAppearancesCreated, + CAST(1 AS bit) AS AlreadyBuilt, + N'This manuscript has already been built into PlotDirector.' AS Message + FROM dbo.OnboardingManuscriptBuilds + WHERE OnboardingManuscriptBuildID = @ExistingBuildID; + + SELECT TemporaryChapterKey, ChapterID FROM dbo.OnboardingManuscriptBuildChapterMap WHERE OnboardingManuscriptBuildID = @ExistingBuildID ORDER BY TemporaryChapterKey; + SELECT TemporarySceneKey, SceneID FROM dbo.OnboardingManuscriptBuildSceneMap WHERE OnboardingManuscriptBuildID = @ExistingBuildID ORDER BY TemporarySceneKey; + SELECT TemporaryCharacterKey, CharacterID, Created FROM dbo.OnboardingManuscriptBuildCharacterMap WHERE OnboardingManuscriptBuildID = @ExistingBuildID ORDER BY TemporaryCharacterKey; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.Books b + INNER JOIN dbo.Projects p ON p.ProjectID = b.ProjectID + INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = p.ProjectID + WHERE b.BookID = @BookID + AND b.ProjectID = @ProjectID + AND b.IsArchived = 0 + AND p.IsArchived = 0 + AND pua.UserID = @UserID + AND pua.IsActive = 1 + ) + RETURN; + + DECLARE @Chapters table (TemporaryChapterKey nvarchar(80) NOT NULL PRIMARY KEY, Title nvarchar(200) NOT NULL, SortOrder int NOT NULL, WordCount int NOT NULL); + DECLARE @Scenes table (TemporarySceneKey nvarchar(80) NOT NULL PRIMARY KEY, TemporaryChapterKey nvarchar(80) NOT NULL, Title nvarchar(200) NOT NULL, SortOrder int NOT NULL, WordCount int NOT NULL); + DECLARE @Characters table (TemporaryCharacterKey nvarchar(80) NOT NULL PRIMARY KEY, Name nvarchar(200) NOT NULL, ExistingCharacterID int NULL); + + INSERT @Chapters (TemporaryChapterKey, Title, SortOrder, WordCount) + SELECT LEFT(JSON_VALUE(value, '$.TemporaryChapterKey'), 80), + LEFT(LTRIM(RTRIM(JSON_VALUE(value, '$.Title'))), 200), + TRY_CONVERT(int, JSON_VALUE(value, '$.SortOrder')), + COALESCE(TRY_CONVERT(int, JSON_VALUE(value, '$.WordCount')), 0) + FROM OPENJSON(@ChaptersJson) + WHERE NULLIF(JSON_VALUE(value, '$.TemporaryChapterKey'), N'') IS NOT NULL + AND NULLIF(LTRIM(RTRIM(JSON_VALUE(value, '$.Title'))), N'') IS NOT NULL; + + INSERT @Scenes (TemporarySceneKey, TemporaryChapterKey, Title, SortOrder, WordCount) + SELECT LEFT(JSON_VALUE(value, '$.TemporarySceneKey'), 80), + LEFT(JSON_VALUE(value, '$.TemporaryChapterKey'), 80), + LEFT(LTRIM(RTRIM(JSON_VALUE(value, '$.Title'))), 200), + TRY_CONVERT(int, JSON_VALUE(value, '$.SortOrder')), + COALESCE(TRY_CONVERT(int, JSON_VALUE(value, '$.WordCount')), 0) + FROM OPENJSON(@ScenesJson) + WHERE NULLIF(JSON_VALUE(value, '$.TemporarySceneKey'), N'') IS NOT NULL + AND NULLIF(JSON_VALUE(value, '$.TemporaryChapterKey'), N'') IS NOT NULL + AND NULLIF(LTRIM(RTRIM(JSON_VALUE(value, '$.Title'))), N'') IS NOT NULL; + + INSERT @Characters (TemporaryCharacterKey, Name, ExistingCharacterID) + SELECT LEFT(JSON_VALUE(value, '$.TemporaryCharacterKey'), 80), + LEFT(LTRIM(RTRIM(JSON_VALUE(value, '$.Name'))), 200), + TRY_CONVERT(int, JSON_VALUE(value, '$.ExistingCharacterID')) + FROM OPENJSON(@CharactersJson) + WHERE NULLIF(JSON_VALUE(value, '$.TemporaryCharacterKey'), N'') IS NOT NULL + AND NULLIF(LTRIM(RTRIM(JSON_VALUE(value, '$.Name'))), N'') IS NOT NULL; + + IF NOT EXISTS (SELECT 1 FROM @Chapters) OR EXISTS (SELECT 1 FROM @Scenes s WHERE NOT EXISTS (SELECT 1 FROM @Chapters c WHERE c.TemporaryChapterKey = s.TemporaryChapterKey)) + RETURN; + + IF EXISTS (SELECT 1 FROM @Chapters WHERE SortOrder IS NULL OR SortOrder <= 0 OR WordCount < 0) + RETURN; + IF EXISTS (SELECT 1 FROM @Scenes WHERE SortOrder IS NULL OR SortOrder <= 0 OR WordCount < 0) + RETURN; + + DECLARE @PlannedRevisionStatusID int = (SELECT TOP (1) RevisionStatusID FROM dbo.RevisionStatuses WHERE StatusName = N'Planned' ORDER BY SortOrder); + DECLARE @DefaultTimeModeID int = (SELECT TOP (1) TimeModeID FROM dbo.TimeModes ORDER BY CASE WHEN TimeModeName = N'Unknown' THEN 0 ELSE 1 END, SortOrder, TimeModeID); + DECLARE @DefaultTimeConfidenceID int = (SELECT TOP (1) TimeConfidenceID FROM dbo.TimeConfidences ORDER BY CASE WHEN TimeConfidenceName = N'Unknown' THEN 0 ELSE 1 END, SortOrder, TimeConfidenceID); + + DECLARE @ChapterMap table (TemporaryChapterKey nvarchar(80) NOT NULL PRIMARY KEY, ChapterID int NOT NULL); + DECLARE @SceneMap table (TemporarySceneKey nvarchar(80) NOT NULL PRIMARY KEY, SceneID int NOT NULL); + DECLARE @CharacterMap table (TemporaryCharacterKey nvarchar(80) NOT NULL PRIMARY KEY, CharacterID int NOT NULL, Created bit NOT NULL); + DECLARE @BuildID int; + DECLARE @ManuscriptDocumentID int; + DECLARE @LinkedUtc datetime2 = SYSUTCDATETIME(); + + BEGIN TRANSACTION; + + IF EXISTS (SELECT 1 FROM dbo.ManuscriptDocuments WHERE DocumentGuid = @DocumentGuid AND IsActive = 1 AND BookID <> @BookID) + THROW 51000, 'This manuscript document is already linked to another book.', 1; + + SELECT @ManuscriptDocumentID = ManuscriptDocumentID FROM dbo.ManuscriptDocuments WHERE BookID = @BookID AND IsActive = 1; + IF @ManuscriptDocumentID IS NULL + BEGIN + INSERT dbo.ManuscriptDocuments (BookID, ProjectID, DocumentGuid, BindingVersion, CreatedByUserID, LastOpenedUtc, LastSyncUtc) + VALUES (@BookID, @ProjectID, @DocumentGuid, @BindingVersion, @UserID, @LinkedUtc, @LinkedUtc); + SET @ManuscriptDocumentID = CAST(SCOPE_IDENTITY() AS int); + END + ELSE + BEGIN + UPDATE dbo.ManuscriptDocuments + SET DocumentGuid = @DocumentGuid, ProjectID = @ProjectID, BindingVersion = @BindingVersion, LastOpenedUtc = @LinkedUtc, LastSyncUtc = @LinkedUtc + WHERE ManuscriptDocumentID = @ManuscriptDocumentID; + END; + + INSERT dbo.OnboardingManuscriptBuilds (PreviewID, UserID, ProjectID, BookID, ManuscriptDocumentID) + VALUES (@PreviewID, @UserID, @ProjectID, @BookID, @ManuscriptDocumentID); + SET @BuildID = CAST(SCOPE_IDENTITY() AS int); + + DECLARE @TempChapterKey nvarchar(80), @Title nvarchar(200), @SortOrder int, @WordCount int, @ChapterID int, @ChapterNumber decimal(9,2); + DECLARE chapter_cursor CURSOR LOCAL FAST_FORWARD FOR SELECT TemporaryChapterKey, Title, SortOrder, WordCount FROM @Chapters ORDER BY SortOrder, TemporaryChapterKey; + OPEN chapter_cursor; + FETCH NEXT FROM chapter_cursor INTO @TempChapterKey, @Title, @SortOrder, @WordCount; + WHILE @@FETCH_STATUS = 0 + BEGIN + SET @ChapterNumber = (SELECT COUNT(*) + 1 FROM @ChapterMap); + INSERT dbo.Chapters (BookID, ChapterNumber, ChapterTitle, Summary, SortOrder, RevisionStatusID, IsLinkedToManuscript, ManuscriptDocumentID, ExternalReference, LinkedUtc) + VALUES (@BookID, @ChapterNumber, @Title, NULL, @SortOrder, @PlannedRevisionStatusID, 1, @ManuscriptDocumentID, @TempChapterKey, @LinkedUtc); + SET @ChapterID = CAST(SCOPE_IDENTITY() AS int); + INSERT @ChapterMap VALUES (@TempChapterKey, @ChapterID); + INSERT dbo.OnboardingManuscriptBuildChapterMap VALUES (@BuildID, @TempChapterKey, @ChapterID); + FETCH NEXT FROM chapter_cursor INTO @TempChapterKey, @Title, @SortOrder, @WordCount; + END; + CLOSE chapter_cursor; DEALLOCATE chapter_cursor; + + DECLARE @TempSceneKey nvarchar(80), @TempSceneChapterKey nvarchar(80), @SceneID int, @SceneNumber decimal(9,2); + DECLARE scene_cursor CURSOR LOCAL FAST_FORWARD FOR + SELECT TemporarySceneKey, TemporaryChapterKey, Title, SortOrder, WordCount FROM @Scenes ORDER BY TemporaryChapterKey, SortOrder, TemporarySceneKey; + OPEN scene_cursor; + FETCH NEXT FROM scene_cursor INTO @TempSceneKey, @TempSceneChapterKey, @Title, @SortOrder, @WordCount; + WHILE @@FETCH_STATUS = 0 + BEGIN + SELECT @ChapterID = ChapterID FROM @ChapterMap WHERE TemporaryChapterKey = @TempSceneChapterKey; + SET @SceneNumber = (SELECT COUNT(*) + 1 FROM @SceneMap sm INNER JOIN @Scenes s ON s.TemporarySceneKey = sm.TemporarySceneKey WHERE s.TemporaryChapterKey = @TempSceneChapterKey); + IF @SceneNumber = 1 + BEGIN + SELECT TOP (1) @SceneID = SceneID FROM dbo.Scenes WHERE ChapterID = @ChapterID AND IsArchived = 0 ORDER BY SortOrder, SceneID; + UPDATE dbo.Scenes + SET SceneNumber = @SceneNumber, SceneTitle = @Title, SortOrder = @SortOrder, IsLinkedToManuscript = 1, + ManuscriptDocumentID = @ManuscriptDocumentID, ExternalReference = @TempSceneKey, LinkedUtc = @LinkedUtc, UpdatedDate = SYSUTCDATETIME() + WHERE SceneID = @SceneID; + END + ELSE + BEGIN + INSERT dbo.Scenes (ChapterID, SceneNumber, SceneTitle, Summary, SortOrder, TimeModeID, TimeConfidenceID, RevisionStatusID, IsLinkedToManuscript, ManuscriptDocumentID, ExternalReference, LinkedUtc) + VALUES (@ChapterID, @SceneNumber, @Title, NULL, @SortOrder, @DefaultTimeModeID, @DefaultTimeConfidenceID, @PlannedRevisionStatusID, 1, @ManuscriptDocumentID, @TempSceneKey, @LinkedUtc); + SET @SceneID = CAST(SCOPE_IDENTITY() AS int); + END; + IF EXISTS (SELECT 1 FROM dbo.SceneWorkflow WHERE SceneID = @SceneID) + UPDATE dbo.SceneWorkflow SET ActualWordCount = @WordCount, UpdatedDate = SYSUTCDATETIME() WHERE SceneID = @SceneID; + ELSE + INSERT dbo.SceneWorkflow (SceneID, ActualWordCount) VALUES (@SceneID, @WordCount); + INSERT @SceneMap VALUES (@TempSceneKey, @SceneID); + INSERT dbo.OnboardingManuscriptBuildSceneMap VALUES (@BuildID, @TempSceneKey, @SceneID); + FETCH NEXT FROM scene_cursor INTO @TempSceneKey, @TempSceneChapterKey, @Title, @SortOrder, @WordCount; + END; + CLOSE scene_cursor; DEALLOCATE scene_cursor; + + DECLARE @TempCharacterKey nvarchar(80), @ExistingCharacterID int, @CharacterID int, @Created bit; + DECLARE character_cursor CURSOR LOCAL FAST_FORWARD FOR SELECT TemporaryCharacterKey, Name, ExistingCharacterID FROM @Characters ORDER BY Name, TemporaryCharacterKey; + OPEN character_cursor; + FETCH NEXT FROM character_cursor INTO @TempCharacterKey, @Title, @ExistingCharacterID; + WHILE @@FETCH_STATUS = 0 + BEGIN + SET @Created = 0; + SELECT @CharacterID = CharacterID FROM dbo.Characters WHERE CharacterID = @ExistingCharacterID AND ProjectID = @ProjectID AND IsArchived = 0; + IF @CharacterID IS NULL + SELECT TOP (1) @CharacterID = CharacterID FROM dbo.Characters WHERE ProjectID = @ProjectID AND IsArchived = 0 AND UPPER(LTRIM(RTRIM(CharacterName))) = UPPER(@Title); + IF @CharacterID IS NULL + BEGIN + INSERT dbo.Characters (ProjectID, CharacterName, ShortName, CharacterImportance, ShowInQuickAddBar, DefaultDescription) + VALUES (@ProjectID, @Title, NULL, 8, 1, NULL); + SET @CharacterID = CAST(SCOPE_IDENTITY() AS int); + SET @Created = 1; + END; + INSERT @CharacterMap VALUES (@TempCharacterKey, @CharacterID, @Created); + INSERT dbo.OnboardingManuscriptBuildCharacterMap VALUES (@BuildID, @TempCharacterKey, @CharacterID, @Created); + SET @CharacterID = NULL; + FETCH NEXT FROM character_cursor INTO @TempCharacterKey, @Title, @ExistingCharacterID; + END; + CLOSE character_cursor; DEALLOCATE character_cursor; + + UPDATE dbo.OnboardingManuscriptBuilds + SET ChaptersCreated = (SELECT COUNT(*) FROM @ChapterMap), + ScenesCreated = (SELECT COUNT(*) FROM @SceneMap), + CharactersCreated = (SELECT COUNT(*) FROM @CharacterMap WHERE Created = 1), + CharactersReused = (SELECT COUNT(*) FROM @CharacterMap WHERE Created = 0), + UpdatedUtc = SYSUTCDATETIME() + WHERE OnboardingManuscriptBuildID = @BuildID; + + COMMIT TRANSACTION; + + SELECT OnboardingManuscriptBuildID AS BuildID, PreviewID, ProjectID, BookID, ManuscriptDocumentID, Status, MarkerStatus, + ChaptersCreated, ScenesCreated, CharactersCreated, CharactersReused, SceneAppearancesCreated, + CAST(0 AS bit) AS AlreadyBuilt, + N'Your project structure has been created.' AS Message + FROM dbo.OnboardingManuscriptBuilds WHERE OnboardingManuscriptBuildID = @BuildID; + SELECT TemporaryChapterKey, ChapterID FROM dbo.OnboardingManuscriptBuildChapterMap WHERE OnboardingManuscriptBuildID = @BuildID ORDER BY TemporaryChapterKey; + SELECT TemporarySceneKey, SceneID FROM dbo.OnboardingManuscriptBuildSceneMap WHERE OnboardingManuscriptBuildID = @BuildID ORDER BY TemporarySceneKey; + SELECT TemporaryCharacterKey, CharacterID, Created FROM dbo.OnboardingManuscriptBuildCharacterMap WHERE OnboardingManuscriptBuildID = @BuildID ORDER BY TemporaryCharacterKey; +END; +GO diff --git a/PlotLine/Views/Onboarding/BuildComplete.cshtml b/PlotLine/Views/Onboarding/BuildComplete.cshtml new file mode 100644 index 0000000..245588d --- /dev/null +++ b/PlotLine/Views/Onboarding/BuildComplete.cshtml @@ -0,0 +1,43 @@ +@model OnboardingManuscriptBuildResult +@{ + ViewData["Title"] = "Project structure created"; +} + +
+
+
+

Build Project

+

Your project structure has been created.

+

@(Model.AlreadyBuilt ? "This manuscript has already been built into PlotDirector." : "PlotDirector has created the approved chapters, scenes and characters.")

+ @if (!string.IsNullOrWhiteSpace(Model.MarkerWarning)) + { +

@Model.MarkerWarning

+ } +
+ +
+
+ Chapters + @Model.ChaptersCreated +
+
+ Scenes + @Model.ScenesCreated +
+
+ Characters + @(Model.CharactersCreated + Model.CharactersReused) +
+
+ Scene appearances + @Model.SceneAppearancesCreated +
+
+ + +
+
diff --git a/PlotLine/Views/Onboarding/Index.cshtml b/PlotLine/Views/Onboarding/Index.cshtml index e1f0697..57f84bb 100644 --- a/PlotLine/Views/Onboarding/Index.cshtml +++ b/PlotLine/Views/Onboarding/Index.cshtml @@ -315,7 +315,14 @@ @if (string.Equals(Model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal)) { -

Next: import into PlotDirector.

+

Next: build project structure.

+ +

} diff --git a/PlotLine/wwwroot/js/word-companion-host.js b/PlotLine/wwwroot/js/word-companion-host.js index 2c4e621..1e8b042 100644 --- a/PlotLine/wwwroot/js/word-companion-host.js +++ b/PlotLine/wwwroot/js/word-companion-host.js @@ -199,6 +199,7 @@ let firstRunAnalysis = null; let firstRunImportStructure = null; let firstRunCharacterCandidatesModel = []; + let lastOnboardingScanPreview = null; let firstRunStructureImported = false; let firstRunCharacterStepActive = false; let firstRunCharacterStepActivatedAt = 0; @@ -1254,6 +1255,9 @@ companionPresenceConnection.on("ScanCurrentDocument", (command) => { void runOnboardingDocumentScan(command); }); + companionPresenceConnection.on("UpdateOnboardingBuildMarkers", (result) => { + void updateOnboardingBuildMarkers(result); + }); companionPresenceConnection.onclose(() => { if (companionPresenceHeartbeatTimer) { window.clearInterval(companionPresenceHeartbeatTimer); @@ -2112,6 +2116,7 @@ preview.characterCandidateCount = 0; } await reportOnboardingScanProgress(command, "Preparing preview", 95, preview); + lastOnboardingScanPreview = preview; await companionPresenceConnection.invoke("CompleteOnboardingScan", preview); } catch (error) { console.error("Unable to complete onboarding scan.", error); @@ -2119,6 +2124,41 @@ } }; + const updateOnboardingBuildMarkers = async (result) => { + if (!lastOnboardingScanPreview || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") { + throw new Error("Word document access is unavailable."); + } + + const chapterMap = new Map((result?.chapterMappings || result?.ChapterMappings || []) + .map((item) => [item.temporaryChapterKey || item.TemporaryChapterKey, item.chapterID || item.ChapterID])); + const sceneMap = new Map((result?.sceneMappings || result?.SceneMappings || []) + .map((item) => [item.temporarySceneKey || item.TemporarySceneKey, item.sceneID || item.SceneID])); + + await window.Word.run(async (context) => { + const paragraphs = context.document.body.paragraphs; + paragraphs.load("items/text,items/style,items/styleBuiltIn"); + await context.sync(); + + for (const chapter of lastOnboardingScanPreview.chapters || []) { + const chapterId = chapterMap.get(chapter.temporaryChapterKey); + const index = Number.parseInt(chapter.startPosition, 10); + if (chapterId && Number.isInteger(index) && paragraphs.items[index]) { + ensureAnchorControl(paragraphs.items[index], "PlotDirector Chapter", `PD-CHAPTER-${chapterId}`, "PD-CHAPTER"); + } + } + + for (const scene of lastOnboardingScanPreview.scenes || []) { + const sceneId = sceneMap.get(scene.temporarySceneKey); + const index = Number.parseInt(scene.startPosition, 10); + if (sceneId && Number.isInteger(index) && paragraphs.items[index]) { + ensureAnchorControl(paragraphs.items[index], "PlotDirector Scene", `PD-SCENE-${sceneId}`, "PD-SCENE"); + } + } + + await context.sync(); + }); + }; + const friendlyScanError = (error) => { const message = errorText(error); if (/No chapters were detected/i.test(message)) { diff --git a/PlotLine/wwwroot/js/word-companion-host.min.js b/PlotLine/wwwroot/js/word-companion-host.min.js index 3a7b7a2..0d51bcb 100644 --- a/PlotLine/wwwroot/js/word-companion-host.min.js +++ b/PlotLine/wwwroot/js/word-companion-host.min.js @@ -1 +1 @@ -(()=>{const e="plotdirector.word.projectId",t="plotdirector.word.bookId",n="plotdirector.word.activeTab",r={documentGuid:"plotdirector.word.documentGuid",bookId:"plotdirector.word.boundBookId",projectId:"plotdirector.word.boundProjectId",bindingVersion:"plotdirector.word.bindingVersion"},o=new Set(["scene","review","maintenance","diagnostics"]),a=document.querySelector(".word-companion-shell"),c=document.querySelector(".word-companion-tabs"),i=[...document.querySelectorAll("[data-tab-button]")],s=[...document.querySelectorAll("[data-tab-panel]")],d=document.querySelector("[data-linked-manuscript-card]"),l=document.querySelector("[data-linked-project-title]"),u=document.querySelector("[data-linked-book-title]"),p=document.querySelector("[data-first-run-wizard]"),h=document.querySelector("[data-first-run-project-select]"),m=document.querySelector("[data-first-run-book-select]"),g=document.querySelector("[data-first-run-new-book-title]"),y=document.querySelector("[data-first-run-create-book]"),w=document.querySelector("[data-first-run-scan]"),f=document.querySelector("[data-first-run-cancel]"),I=document.querySelector("[data-first-run-status]"),b=document.querySelector("[data-first-run-summary]"),S=document.querySelector("[data-first-run-character-section]"),C=document.querySelector("[data-first-run-character-status]"),k=document.querySelector("[data-first-run-character-candidates]"),v=document.querySelector("[data-first-run-character-actions]"),x=document.querySelector("[data-first-run-create-characters]"),N=document.querySelector("[data-first-run-skip-characters]"),E=document.querySelector("[data-first-run-continue]"),D=document.querySelector("[data-first-run-import-actions]"),P=document.querySelector("[data-first-run-import]"),L=document.querySelector("[data-first-run-import-cancel]"),q=document.querySelector("[data-first-run-replace-dialog]"),A=document.querySelector("[data-first-run-replace-confirm]"),$=document.querySelector("[data-first-run-replace-cancel]"),T=document.querySelector("[data-unlink-word-document-dialog]"),j=document.querySelector("[data-unlink-word-document-confirm]"),W=document.querySelector("[data-unlink-word-document-cancel]"),R=document.querySelector("[data-office-status]"),U=document.querySelector("[data-connection-status]"),M=document.querySelector("[data-summary-connection]"),O=document.querySelector("[data-summary-project]"),B=document.querySelector("[data-summary-book]"),H=document.querySelector("[data-project-select]"),G=document.querySelector("[data-book-select]"),F=document.querySelector("[data-runtime-book-selectors]"),V=document.querySelector("[data-project-message]"),K=document.querySelector("[data-book-message]"),Y=document.querySelector("[data-refresh-current-scene]"),J=document.querySelector("[data-refresh-document]"),_=document.querySelector("[data-current-chapter]"),z=document.querySelector("[data-current-scene]"),Q=document.querySelector("[data-current-word-count]"),X=document.querySelector("[data-scene-tracking-status]"),Z=document.querySelector("[data-document-message]"),ee=document.querySelector("[data-sync-note]"),te=document.querySelector("[data-document-outline]"),ne=document.querySelector("[data-analyse-manuscript-structure]"),re=document.querySelector("[data-apply-structure-sync]"),oe=document.querySelector("[data-structure-preview-status]"),ae=document.querySelector("[data-structure-preview-results]"),ce=document.querySelector("[data-refresh-plotdirector-scene]"),ie=document.querySelector("[data-plotdirector-scene-status]"),se=document.querySelector("[data-anchor-status]"),de=document.querySelector("[data-anchor-book-id]"),le=document.querySelector("[data-anchor-chapter-id]"),ue=document.querySelector("[data-anchor-scene-id]"),pe=document.querySelector("[data-attach-plotdirector-ids]"),he=document.querySelector("[data-unlink-word-document]"),me=document.querySelector("[data-plotdirector-scene-details]"),ge=document.querySelector("[data-plotdirector-scene-title]"),ye=document.querySelector("[data-plotdirector-revision-status]"),we=document.querySelector("[data-plotdirector-estimated-words]"),fe=document.querySelector("[data-plotdirector-actual-words]"),Ie=document.querySelector("[data-plotdirector-blocked-status]"),be=document.querySelector("[data-plotdirector-blocked-reason-row]"),Se=document.querySelector("[data-plotdirector-blocked-reason]"),Ce=document.querySelector("[data-runtime-last-sync]"),ke=document.querySelector("[data-sync-scene-progress]"),ve=document.querySelector("[data-sync-word-count]"),xe=document.querySelector("[data-sync-plotdirector-count]"),Ne=document.querySelector("[data-sync-last-synced]"),Ee=document.querySelector("[data-sync-status]"),De=document.querySelector("[data-writing-brief-block]"),Pe=document.querySelector("[data-writing-brief]"),Le=document.querySelector("[data-plotdirector-link-groups]"),qe=document.querySelector("[data-character-review-summary]"),Ae=document.querySelector("[data-asset-review-summary]"),$e=document.querySelector("[data-location-review-summary]"),Te=document.querySelector("[data-character-chips]"),je=document.querySelector("[data-asset-chips]"),We=(document.querySelector("[data-location-chips]"),document.querySelector("[data-primary-location-select]")),Re=document.querySelector("[data-save-scene-links]"),Ue=document.querySelector("[data-scene-links-status]"),Me=document.querySelector("[data-links-editing-scene]"),Oe=document.querySelector("[data-chapter-create-link]"),Be=document.querySelector("[data-chapter-create-link-chapter]"),He=document.querySelector("[data-create-plotdirector-chapter]"),Ge=document.querySelector("[data-link-existing-chapter]"),Fe=document.querySelector("[data-chapter-create-link-status]"),Ve=document.querySelector("[data-scene-create-link]"),Ke=document.querySelector("[data-create-link-chapter]"),Ye=document.querySelector("[data-create-link-scene]"),Je=document.querySelector("[data-create-plotdirector-scene]"),_e=document.querySelector("[data-link-existing-scene]"),ze=document.querySelector("[data-create-link-status]"),Qe=document.querySelector("[data-link-existing-dialog]"),Xe=document.querySelector("[data-link-scene-search]"),Ze=document.querySelector("[data-link-scene-options]"),et=document.querySelector("[data-link-selected-scene]"),tt=document.querySelector("[data-link-dialog-cancel]"),nt=document.querySelector("[data-link-dialog-status]"),rt=document.querySelector("[data-create-scene-dialog]"),ot=document.querySelector("[data-create-dialog-scene]"),at=document.querySelector("[data-create-dialog-chapter]"),ct=document.querySelector("[data-create-dialog-confirm]"),it=document.querySelector("[data-create-dialog-cancel]"),st=document.querySelector("[data-link-existing-chapter-dialog]"),dt=document.querySelector("[data-link-chapter-search]"),lt=document.querySelector("[data-link-chapter-options]"),ut=document.querySelector("[data-link-selected-chapter]"),pt=document.querySelector("[data-link-chapter-dialog-cancel]"),ht=document.querySelector("[data-link-chapter-dialog-status]"),mt=document.querySelector("[data-create-chapter-dialog]"),gt=document.querySelector("[data-create-chapter-dialog-chapter]"),yt=document.querySelector("[data-create-chapter-dialog-book]"),wt=document.querySelector("[data-create-chapter-dialog-confirm]"),ft=document.querySelector("[data-create-chapter-dialog-cancel]"),It=document.querySelector("[data-apply-structure-sync-dialog]"),bt=document.querySelector("[data-apply-structure-sync-summary]"),St=document.querySelector("[data-apply-structure-sync-confirm]"),Ct=document.querySelector("[data-apply-structure-sync-cancel]"),kt=document.querySelector("[data-resolve-project-id]"),vt=document.querySelector("[data-resolve-project-title]"),xt=document.querySelector("[data-resolve-book-id]"),Nt=document.querySelector("[data-resolve-book-title]"),Et=document.querySelector("[data-resolve-detected-chapter]"),Dt=document.querySelector("[data-resolve-detected-scene]"),Pt=document.querySelector("[data-resolve-request-chapter]"),Lt=document.querySelector("[data-resolve-request-scene]"),qt=document.querySelector("[data-resolve-result]"),At=document.querySelector("[data-resolve-chapter-id]"),$t=document.querySelector("[data-resolve-scene-id]"),Tt=document.querySelector("[data-run-diagnostics]"),jt=document.querySelector("[data-diagnostic-host]"),Wt=document.querySelector("[data-diagnostic-platform]"),Rt=document.querySelector("[data-diagnostic-ready]"),Ut=document.querySelector("[data-diagnostic-word-api]"),Mt=document.querySelector("[data-diagnostic-last-scan]"),Ot=document.querySelector("[data-diagnostic-last-error]"),Bt=document.querySelector("[data-diagnostic-anchor-type]"),Ht=document.querySelector("[data-diagnostic-stored-scene-id]"),Gt=document.querySelector("[data-diagnostic-stored-chapter-id]"),Ft=document.querySelector("[data-diagnostic-resolution-method]"),Vt=document.querySelector("[data-diagnostic-paragraphs]"),Kt=document.querySelector("[data-diagnostic-heading1]"),Yt=document.querySelector("[data-diagnostic-heading2]"),Jt="true"===a?.dataset.authenticated,_t=Number.parseInt(a?.dataset.userId||"",10),zt=String(a?.dataset.companionVersion||"20C").trim();let Qt=[],Xt=[],Zt=!1,en=!1,tn="Unknown",nn="Unknown",rn="",on="",an=null,cn=null,sn=null,dn=null,ln=null,un=null,pn="",hn="Title Match",mn=!1,gn=[],yn=null,wn=null,fn=[],In=null,bn=null,Sn=null,Cn=null,kn=null,vn=null,xn=null,Nn=null,En=null,Dn=null,Pn=[],Ln=!1,qn=!1,An=0,$n=!1,Tn=!1,jn=!1,Wn=null,Rn="Manual",Un=!1,Mn=null,On=!1,Bn=0,Hn=!1,Gn=0,Fn=null;const Vn=1200;let Kn=!1,Yn=!1,Jn=null,_n=!1,zn=!1,Qn=null,Xn=null,Zn=null,er=!1,tr=!1,nr=null,rr="",or=[],ar=null,cr="",ir=null,sr="",dr=[],lr=null,ur="",pr=null,hr="",mr=[],gr=null,yr="",wr="",fr="",Ir="",br=0,Sr=null,Cr=null,kr={characters:[],assets:[],locations:[],primaryLocationId:null};const vr=e=>{const t=Date.now();t-br<1e3||(br=t,console.debug(`[Word Companion Runtime] ${e}`))},xr=e=>{console.debug(`[Word Companion Timing] ${e}`)},Nr=e=>{R&&(R.textContent=e)},Er=e=>"links"===e?"review":"structure"===e?"maintenance":e,Dr=(e,t=!0)=>{const r=Er(e),a=o.has(r)&&i.some(e=>e.dataset.tabButton===r)?r:"scene";for(const e of i){const t=e.dataset.tabButton===a;e.classList.toggle("is-active",t),e.setAttribute("aria-selected",t?"true":"false")}for(const e of s){const t=e.dataset.tabPanel===a;e.classList.toggle("is-active",t),e.hidden=!t}t&&((e,t)=>{try{t?window.localStorage.setItem(e,String(t)):window.localStorage.removeItem(e)}catch{}})(n,a)},Pr=()=>{const e=Er((e=>{try{return window.localStorage.getItem(e)||""}catch{return""}})(n));Dr(o.has(e)?e:"scene",!1)},Lr=e=>{U&&(U.textContent=e),M&&(M.textContent=e)},qr=e=>{V&&(V.textContent=e)},Ar=e=>{K&&(K.textContent=e)},$r=e=>{Z&&(Z.textContent=e)},Tr=e=>{ie&&(ie.textContent=e)},jr=e=>{const t=t=>Object.prototype.hasOwnProperty.call(e,t);t("anchorType")&&No(Bt,Zr(e.anchorType)),t("storedSceneId")&&No(Ht,Zr(e.storedSceneId)),t("storedChapterId")&&No(Gt,Zr(e.storedChapterId)),t("resolutionMethod")&&No(Ft,Zr(e.resolutionMethod))},Wr=()=>{const e=Ro(),t=Number.isInteger(ln)&&ln>0&&dn===ln,n=!Number.isInteger(un)||un<=0||sn===un,r=t&&n;No(se,r?"Attached to PlotDirector":"Not attached"),No(de,e?.bookId?String(e.bookId):"-"),No(le,Number.isInteger(un)&&un>0?String(un):"-"),No(ue,Number.isInteger(ln)&&ln>0?String(ln):"-"),pe&&(Mr(pe,r&&!mn),pe.disabled=!ln||r&&!mn,pe.textContent=dn&&!r?"Reattach PlotDirector IDs":"Attach PlotDirector IDs"),jr({anchorType:hn||"Title Match",storedSceneId:dn,storedChapterId:sn,resolutionMethod:pn})},Rr=(e,t,n,r=null,o=null,a=null)=>{const c=e||"",i=t||"",s=Number.isInteger(n)?n:null,d=Number.isInteger(a)&&a>0?a:null,l=Number.isInteger(r)&&r>0?r:null,u=Number.isInteger(o)&&o>0?o:null,p=rn===c&&on===i&&an===s&&cn===d&&sn===l&&dn===u;rn=e||"",on=t||"",an=Number.isInteger(n)?n:null,cn=Number.isInteger(a)&&a>0?a:null,sn=Number.isInteger(r)&&r>0?r:null,dn=Number.isInteger(o)&&o>0?o:null,p||(_&&(_.textContent=e||"Not detected"),z&&(z.textContent=t||"Not detected"),Q&&(Q.textContent=Number.isInteger(n)?String(n):"-"),No(ve,Number.isInteger(n)?String(n):"-"),Wr())},Ur=()=>{Jn&&(window.clearTimeout(Jn),Jn=null)},Mr=(e,t)=>{e&&(e.hidden=t)},Or=()=>{Mr(he,!Cn)},Br=e=>{No(Ee,e)},Hr=e=>{No(oe,e)},Gr=(e="Select a project and book to analyse manuscript structure.")=>{if(Sn=null,ae){ae.innerHTML="";const e=document.createElement("p");e.textContent="No preview generated.",ae.append(e)}Hr(e),Vr()},Fr=(e=Sn)=>((e=Sn)=>xa.flatMap(t=>Array.isArray(e?.[t.key])?e[t.key]:[]))(e).filter(e=>"couldLink"===e.category||"wouldCreateChapters"===e.category||"wouldCreateScenes"===e.category),Vr=()=>{re&&(re.disabled=jn||0===Fr().length)},Kr=()=>{ne&&(ne.disabled=jn||!Wo()||!Ro()),Vr()},Yr=()=>{Me&&(Me.textContent=ln?`Editing links for: ${fr||on||"Current scene"}`:"Link a PlotDirector scene first.")},Jr=()=>{const e=Un||!ln,t=e||!un||_n;ke&&(ke.disabled=t),Re&&(Re.disabled=e),We&&(We.disabled=e);for(const t of[Te,je])for(const n of t?[...t.querySelectorAll("input[type='checkbox']")]:[])n.disabled=e},_r=e=>{"Live"!==e&&"Manual"!==e||(Rn=e),No(X,e||Rn||"Manual")},zr=(e,t="")=>{Un=!!e,Un&&(Ur(),Ic()),No(X,Un?"Stale":Rn),Jr(),t&&($r(t),oo(t))},Qr=(e,t=null,n="")=>{ln=Number.isInteger(e)&&e>0?e:null,un=Number.isInteger(t)&&t>0?t:null,pn=n||"",ln!==Qn&&(Xn=null),ln||(Ur(),Ic()),Jr(),Br(Un?"Refresh Current Scene before syncing.":ln?"Ready to sync.":"No resolved PlotDirector scene."),oo(Un?"Refresh Current Scene before saving.":ln?"Ready to save.":"Link a PlotDirector scene first."),Yr(),Wr()},Xr=e=>{Y&&(Y.disabled=e,Y.textContent=e?"Refreshing...":"Refresh Current Scene")},Zr=e=>null==e||""===e?"-":String(e),eo=e=>{if(!e)return"-";const t=e instanceof Date?e:new Date(e);if(Number.isNaN(t.getTime()))return"-";const n=new Date;return`Last synced: ${t.toDateString()===n.toDateString()?"Today":t.toLocaleDateString()} ${t.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}`},to=e=>{kn=e||null;const t=kn?.lastSyncUtc||kn?.manuscriptDocument?.lastSyncUtc;No(Ce,eo(t)),No(Ne,eo(t)),jo()},no=()=>{vn=null,xn=null,Nn=null,Gn+=1,Fn=null,bc(),Sc(),Cc(),to(null)},ro=()=>{xn=null,Nn=null},oo=e=>{No(Ue,e)},ao=e=>{No(ze,e)},co=e=>{No(Fe,e)},io=()=>!!Ro()&&!!rn&&!un,so=()=>!!Ro()&&!!on&&Number.isInteger(un)&&un>0,lo=()=>{Mr(Oe,!0),co(""),He&&(He.disabled=!0,He.textContent="Create Chapter in PlotDirector"),Ge&&(Ge.disabled=!0,Ge.textContent="Link to Existing Chapter")},uo=(e="")=>{No(Be,Zr(rn)),Mr(Oe,!1),co(e);const t=io();He&&(He.disabled=!t,He.textContent="Create Chapter in PlotDirector"),Ge&&(Ge.disabled=!t,Ge.textContent="Link to Existing Chapter")},po=()=>{Mr(Ve,!0),ao(""),Je&&(Je.disabled=!0,Je.textContent="Create Scene in PlotDirector"),_e&&(_e.disabled=!0,_e.textContent="Link to Existing Scene")},ho=(e="")=>{No(Ke,Zr(rn)),No(Ye,Zr(on)),Mr(Ve,!1),ao(e);const t=so();Je&&(Je.disabled=!t,Je.textContent="Create Scene in PlotDirector"),_e&&(_e.disabled=!t,_e.textContent="Link to Existing Scene")},mo=e=>{Tr(e),Mr(me,!0),Mr(De,!0),Mr(Le,!1),lo(),po(),pn="",fr="",Ir="",hn=dn?"SceneID Anchor":sn?"ChapterID Anchor":"Title Match",mn=!1,kr={characters:[],assets:[],locations:[],primaryLocationId:null},yo(Te,[],"character"),yo(je,[],"asset"),wo([],null,!1),oa(),Qr(null),No(xe,"-"),No(ge,"-"),No(ye,"-"),No(we,"-"),No(fe,"-"),No(Ie,"-"),No(Se,"-"),Mr(be,!0),Wr(),Yr()},go=(e,t)=>"asset"===t?e?.assetId||e?.AssetId||e?.storyAssetId||e?.StoryAssetId||0:"location"===t?e?.locationId||e?.LocationId||0:e?.characterId||e?.CharacterId||0,yo=(e,t,n,r=!1)=>{if(!e)return;e.innerHTML="";const o=Array.isArray(t)?t:[];if(0===o.length){const t=document.createElement("p");return t.className="word-companion-empty-list",t.textContent="None",void e.append(t)}for(const t of o){const o=Number.parseInt(go(t,n),10);if(!Number.isInteger(o)||o<=0)continue;const a=document.createElement("label");a.className="word-companion-check-item";const c=document.createElement("input");c.type="checkbox",c.value=String(o),c.checked=!!t.linked,c.disabled=!r;const i=document.createElement("span");i.textContent=t.name||"Unnamed",a.append(c,i),e.append(a)}},wo=(e,t,n=!1)=>{if(!We)return;We.innerHTML="";const r=document.createElement("option");r.value="",r.textContent="None",We.append(r);const o=Number.parseInt(t||"",10);for(const t of Array.isArray(e)?e:[]){const e=Number.parseInt(go(t,"location"),10);if(!Number.isInteger(e)||e<=0)continue;const n=document.createElement("option");n.value=String(e),n.textContent=t.name||"Unnamed",n.selected=e===o,We.append(n)}We.disabled=!n},fo=e=>String(e||"").trim().replace(/\s+/g," "),Io=(e,t)=>{const n=fo(e);if(!n)return"";const r=new RegExp(`^${"scene"===t?"scene":"chapter"}\\s+(?:\\d+|[ivxlcdm]+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)\\s*(?::|-|\\u2013|\\u2014)\\s*`,"i");return fo(n.replace(r,""))||n},bo=e=>{const t=t=>Object.prototype.hasOwnProperty.call(e,t);t("projectId")&&No(kt,Zr(e.projectId)),t("projectTitle")&&No(vt,Zr(e.projectTitle)),t("bookId")&&No(xt,Zr(e.bookId)),t("bookTitle")&&No(Nt,Zr(e.bookTitle)),t("detectedChapter")&&No(Et,Zr(e.detectedChapter)),t("detectedScene")&&No(Dt,Zr(e.detectedScene)),t("requestChapter")&&No(Pt,Zr(e.requestChapter)),t("requestScene")&&No(Lt,Zr(e.requestScene)),t("result")&&No(qt,Zr(e.result)),t("chapterId")&&No(At,Zr(e.chapterId)),t("sceneId")&&No($t,Zr(e.sceneId))},So=async e=>{if(Nn===e&&Array.isArray(xn?.chapters))return xn.chapters;const t=await Qa(`/api/word-companion/books/${e}/structure`);return Nn=e,xn=t||null,Array.isArray(t?.chapters)?t.chapters:[]},Co=async e=>{const t=await Qa(`/api/word-companion/books/${e}/chapters`);return Array.isArray(t?.chapters)?t.chapters:[]},ko=e=>{if(Number.isInteger(un)&&un>0){const t=e.find(e=>e.chapterId===un);if(t)return t}const t=Io(rn,"chapter").toLocaleLowerCase();return t&&e.find(e=>Io(e.title,"chapter").toLocaleLowerCase()===t)||null},vo=async()=>{const e=Ro();return e?ko(await So(e.bookId)):null},xo=async(e,t,n,r,o)=>{const a=await Qa(`/api/word-companion/scenes/${t}/companion`);try{a.revisionStatus=await(async(e,t)=>{const n=await Qa(`/api/word-companion/books/${e}/structure`),r=Array.isArray(n?.chapters)?n.chapters:[];for(const e of r){const n=(Array.isArray(e.scenes)?e.scenes:[]).find(e=>e.sceneId===t);if(n)return n.revisionStatus||""}return""})(e,t)}catch{a.revisionStatus=""}var c;hn=o||"Title Match",mn=!1,zr(!1),Tr("Linked to PlotDirector"),$r(""),lo(),po(),Qr(t,n,r),c=a,fr=c?.sceneTitle||on||"",Ir=c?.chapterTitle||rn||"",No(ge,Zr(c?.sceneTitle)),No(ye,Zr(c?.revisionStatus||c?.revisionStatusName||"Not provided")),No(we,Zr(c?.estimatedWords)),No(fe,Zr(c?.actualWords)),No(xe,Zr(c?.actualWords)),No(Ie,c?.blocked?"Blocked":"Not blocked"),c?.blockedReason?(No(Se,c.blockedReason),Mr(be,!1)):(No(Se,"-"),Mr(be,!0)),Pe&&(Pe.textContent=c?.writingBrief||"No writing brief has been added for this scene."),kr={characters:Array.isArray(c?.characters)?c.characters:[],assets:Array.isArray(c?.assets)?c.assets:[],locations:Array.isArray(c?.locations)?c.locations:[],primaryLocationId:c?.primaryLocationId??null},yo(Te,kr.characters,"character",!!ln&&!Un),yo(je,kr.assets,"asset",!!ln&&!Un),wo(kr.locations,kr.primaryLocationId,!!ln&&!Un),oa(),Yr(),oo(Un?"Refresh Current Scene before saving.":ln?"Ready to save.":"No resolved PlotDirector scene."),Mr(me,!1),Mr(De,!1),Mr(Le,!1),ec(t,n),fc(),Pc()},No=(e,t)=>{if(e){const n=null==t?"":String(t);e.textContent!==n&&(e.textContent=n)}},Eo=e=>{const t=t=>Object.prototype.hasOwnProperty.call(e,t);t("host")&&No(jt,e.host),t("platform")&&No(Wt,e.platform),t("ready")&&No(Rt,e.ready),t("wordApi")&&No(Ut,e.wordApi),t("lastScan")&&No(Mt,e.lastScan),t("lastError")&&No(Ot,e.lastError),t("paragraphs")&&No(Vt,e.paragraphs),t("heading1")&&No(Kt,e.heading1),t("heading2")&&No(Yt,e.heading2)},Do=e=>{const t=[];return e?.name&&t.push(e.name),e?.code&&e.code!==e.name&&t.push(e.code),e?.message&&t.push(e.message),e?.debugInfo?.errorLocation&&t.push(`Location: ${e.debugInfo.errorLocation}`),t.length>0?t.join(": "):String(e)},Po=async()=>{if(!window.Office||"function"!=typeof window.Office.onReady)return Zt=!1,en=!1,tn="Unavailable",nn="Unavailable",Eo({host:tn,platform:nn,ready:"No",wordApi:"No"}),null;const e=await window.Office.onReady();return Zt=!0,tn=e?.host||"Unknown",nn=e?.platform||"Unknown",en=!!window.Word&&!!window.Office.HostType&&e?.host===window.Office.HostType.Word,Eo({host:tn,platform:nn,ready:"Yes",wordApi:en?"Yes":"No"}),e},Lo=e=>{try{const t=window.localStorage.getItem(e),n=Number.parseInt(t||"",10);return Number.isInteger(n)&&n>0?n:null}catch{return null}},qo=(e,t)=>{try{t?window.localStorage.setItem(e,String(t)):window.localStorage.removeItem(e)}catch{}},Ao=(e,t)=>{if(!e)return;e.innerHTML="";const n=document.createElement("option");n.value="",n.textContent=t,e.append(n),e.disabled=!0},$o=(e,t,n,r,o)=>{if(!e)return;e.innerHTML="";const a=document.createElement("option");a.value="",a.textContent=t,e.append(a);for(const t of n){const n=document.createElement("option");n.value=String(t[r]),n.textContent=t[o],e.append(n)}e.disabled=0===n.length},To=e=>{const t=String(e?.displayTitle||"").trim();if(t)return t;const n=String(e?.title||"").trim(),r=String(e?.subtitle||"").trim();return r?`${n}: ${r}`:n},jo=()=>{const e=!!Cn;Mr(d,!e),Mr(F,e),e&&(No(l,kn?.projectTitle||Wo()?.title||"Project"),No(u,kn?.bookTitle||To(Ro())||"Book"))},Wo=()=>{const e=Number.parseInt(H?.value||"",10);return Qt.find(t=>t.projectId===e)||null},Ro=()=>{const e=Number.parseInt(G?.value||"",10);return Xt.find(t=>t.bookId===e)||null},Uo=()=>{const e=Number.parseInt(h?.value||"",10);return Qt.find(t=>t.projectId===e)||null},Mo=()=>{const e=Number.parseInt(m?.value||"",10);return Xt.find(t=>t.bookId===e)||null},Oo=()=>{const t=Cn?.projectId||Uo()?.projectId||Wo()?.projectId||Lo(e);return Number.isInteger(t)&&t>0?t:null},Bo=()=>{const e=Cn?.bookId||Mo()?.bookId||Ro()?.bookId||Lo(t);return Number.isInteger(e)&&e>0?e:null},Ho=()=>{const e=String(window.Office?.context?.document?.url||"").trim();if(!e)return"";const t=e.split(/[\\/]/).filter(Boolean).pop()||"";try{return decodeURIComponent(t)}catch{return t}},Go=()=>({userID:Number.isInteger(_t)&&_t>0?_t:null,companionVersion:zt,machineName:"",documentOpen:!!en,currentDocumentName:Ho(),linkedProjectID:Oo(),linkedBookID:Bo()}),Fo=()=>{(async()=>{Sr&&Sr.state===signalR.HubConnectionState.Connected&&await Sr.invoke("CompanionHeartbeat",Go())})().catch(e=>{console.debug("Word Companion heartbeat unavailable.",e)})},Vo=async()=>{Jt&&window.signalR&&!Sr&&(Sr=(new signalR.HubConnectionBuilder).withUrl("/hubs/word-companion-follow").withAutomaticReconnect().build(),Sr.onreconnected(Fo),Sr.on("ScanCurrentDocument",e=>{ba(e)}),Sr.onclose(()=>{Cr&&(window.clearInterval(Cr),Cr=null)}),await Sr.start(),await Sr.invoke("RegisterCompanion",Go()),Cr=window.setInterval(Fo,25e3))};window.addEventListener("beforeunload",()=>{Cr&&(window.clearInterval(Cr),Cr=null),Sr&&Sr.stop().catch(()=>{})});const Ko=e=>No(I,e),Yo=()=>{const e=Uo(),t=Mo(),n=qn&&Date.now()-An>=750;w&&(w.disabled=!e||!t||$n),y&&(y.disabled=!e||!String(g?.value||"").trim()||$n),P&&(P.disabled=!En?.canImport||!Dn||$n),x&&(x.disabled=!n||Tn||0===Qo().length)},Jo=e=>{Mr(p,!e),Mr(c,e),Or(),jo();for(const t of s)Mr(t,!!e||"scene"!==t.dataset.tabPanel&&!t.classList.contains("is-active"));e||Pr()},_o=()=>{if(!h)return;if(0===Qt.length)return void Ao(h,"No projects available.");$o(h,"Choose a project",Qt,"projectId","title");const e=Number.parseInt(H?.value||"",10);Number.isInteger(e)&&e>0&&(h.value=String(e))},zo=(e="Select a Project and Book to begin.")=>{En=null,Dn=null,Pn=[],Ln=!1,qn=!1,An=0,Mr(b,!0),Mr(D,!0),Mr(S,!0),Mr(v,!0),b&&(b.innerHTML=""),k&&(k.innerHTML=""),No(C,""),Ko(e),Yo()},Qo=()=>k?[...k.querySelectorAll("input[type='checkbox']:checked")].map(e=>String(e.value||"").trim()).filter(Boolean):[],Xo=e=>{if(Pn=Array.isArray(e)?e:[],S&&k){if(k.innerHTML="",Mr(S,!1),Mr(v,!Ln||0===Pn.length),Mr(x,!1),Mr(N,!1),Mr(E,!0),0===Pn.length)return No(C,"No likely major characters found."),void Yo();No(C,`${Pn.length} likely major characters found.`);for(const e of Pn){const t=document.createElement("label");t.className="word-companion-character-candidate";const n=document.createElement("input");n.type="checkbox",n.value=e.text||"",n.checked="high"===String(e.confidence||"").trim().toLowerCase(),n.addEventListener("change",Yo);const r=document.createElement("strong");r.textContent=e.text||"";const o=document.createElement("em"),a=String(e.reason||"").trim();o.textContent=a?`${Number(e.mentionCount||0).toLocaleString()} mentions · ${a}`:`${Number(e.mentionCount||0).toLocaleString()} mentions`;const c=document.createElement("span");c.className="word-companion-character-candidate-text",c.append(r,o),t.append(n,c),k.append(t)}Yo()}},Zo=()=>{const e=window.Office?.context?.document?.settings;if(!e)return null;const t=String(e.get(r.documentGuid)||"").trim(),n=Number.parseInt(e.get(r.bookId)||"",10),o=Number.parseInt(e.get(r.projectId)||"",10),a=Number.parseInt(e.get(r.bindingVersion)||"",10);return!t||!Number.isInteger(n)||n<=0||!Number.isInteger(o)||o<=0?null:{documentGuid:t,bookId:n,projectId:o,bindingVersion:Number.isInteger(a)&&a>0?a:1}},ea=()=>new Promise((e,t)=>{const n=window.Office?.context?.document?.settings;if(n){for(const e of Object.values(r))"function"==typeof n.remove?n.remove(e):n.set(e,"");n.saveAsync(n=>{n.status===window.Office.AsyncResultStatus.Succeeded?e():t(n.error||new Error("Unable to remove Word document metadata."))})}else t(new Error("Word document settings are unavailable."))}),ta=()=>Cn?.documentGuid?Cn.documentGuid:window.crypto?.randomUUID?window.crypto.randomUUID():"10000000-1000-4000-8000-100000000000".replace(/[018]/g,e=>(Number(e)^16*Math.random()>>Number(e)/4).toString(16)),na=()=>Cn?.documentGuid||Zo()?.documentGuid||"",ra=()=>{const e=Wo(),t=Ro();O&&(O.textContent=e?.title||"(Not connected)"),B&&(B.textContent=t?To(t):"(Not connected)"),ee&&(ee.textContent=t?"":"Select a PlotDirector book before refreshing."),Kr(),jo()},oa=()=>{const e=Array.isArray(kr.characters)?kr.characters.length:0,t=Array.isArray(kr.assets)?kr.assets.length:0,n=Array.isArray(kr.locations)?kr.locations.length:0;No(qe,`Characters (${e})`),No(Ae,`Assets (${t})`),No($e,`Locations (${n})`)},aa=e=>String(e?.styleBuiltIn||e?.style||"").replace(/\s+/g,"").toLowerCase(),ca=(e,t)=>{const n=aa(e);return n===`heading${t}`||n.endsWith(`.heading${t}`)||n.includes(`heading${t}`)},ia=e=>String(e||"").trim().split(/\s+/).filter(Boolean).length,sa=(e,t)=>{const n=String(e||"").match(new RegExp(`^${t}-(\\d+)$`,"i"));if(!n)return null;const r=Number.parseInt(n[1],10);return Number.isInteger(r)&&r>0?r:null},da=e=>{try{return Array.isArray(e?.contentControls?.items)?e.contentControls.items:[]}catch(e){return console.warn("Word paragraph content controls were not available.",e),[]}},la=(e,t)=>{const n=da(e);for(const e of n){const n=sa(e.tag,t);if(n)return n}return null},ua=(e,t)=>{const n=[];let r=0,o=0,a=null,c=null;e.forEach((e,t)=>{const i=String(e.text||"").trim();if(i){if(ca(e,1))return r+=1,a={index:n.length+1,paragraphIndex:t,title:i,anchorId:la(e,"PD-CHAPTER"),scenes:[]},n.push(a),void(c=null);if(ca(e,2)){if(o+=1,!a)return;return c={index:a.scenes.length+1,paragraphIndex:t,title:i,anchorId:la(e,"PD-SCENE"),wordCount:0},void a.scenes.push(c)}c&&(c.wordCount+=ia(i))}});const i=t.find(e=>String(e.text||"").trim()),s=i?e.findIndex(e=>{return t=e,n=i,String(t?.text||"").trim()===String(n?.text||"").trim()&&aa(t)===aa(n);var t,n}):-1,d=s>=0&&n.filter(e=>e.paragraphIndex<=s).at(-1)||null,l=d&&s>=0&&d.scenes.filter(e=>e.paragraphIndex<=s).at(-1)||null;return{chapters:n,currentChapter:d,currentScene:l,paragraphCount:e.length,heading1Count:r,heading2Count:o}},pa=new Set(["***","###"]),ha=(e,t)=>{const n=String(e?.text||"").trim();return{index:t,text:n,styleBuiltIn:e?.styleBuiltIn||"",style:e?.style||"",wordCount:ia(n),chapterAnchorId:null,sceneAnchorId:null}},ma=(e,t)=>{e&&(e.endParagraphIndex=t)},ga=(e,t)=>{e&&(e.endParagraphIndex=t,ma(e.scenes.at(-1),t))},ya=(e,t,n)=>e?.[t]??e?.[n]??null,wa=(e,t,n)=>e?.[t]??e?.[n]??null,fa=async(e,t,n,r={})=>{Sr&&Sr.state===signalR.HubConnectionState.Connected&&await Sr.invoke("ReportOnboardingScanProgress",{userID:wa(e,"userID","UserID"),onboardingID:wa(e,"onboardingID","OnboardingID"),message:t,percentComplete:n,chapterCount:r.chapterCount||0,sceneCount:r.sceneCount||0,characterCandidateCount:r.characterCandidateCount||0,totalWordCount:r.totalWordCount||0})},Ia=async(e,t)=>{Sr&&Sr.state===signalR.HubConnectionState.Connected&&await Sr.invoke("FailOnboardingScan",{userID:wa(e,"userID","UserID"),onboardingID:wa(e,"onboardingID","OnboardingID"),message:t})},ba=async e=>{var t;if(en&&window.Word)try{await fa(e,"Preparing document scan",5);const n=((e,t)=>{const n=[],r=[],o=[];let a=null,c=null,i=0,s=0;const d=()=>{c=null},l=(e,t,r=!1)=>(d(),a={temporaryChapterKey:`chapter-${n.length+1}`,chapterNumber:n.length+1,title:t,wordCount:0,startPosition:e?.index??null,existingChapterID:r?null:la(e,"PD-CHAPTER")},n.push(a),u(e,null),a),u=(e,t=null)=>a?(d(),c={temporarySceneKey:`scene-${r.length+1}`,temporaryChapterKey:a.temporaryChapterKey,sceneNumberWithinChapter:r.filter(e=>e.temporaryChapterKey===a.temporaryChapterKey).length+1,title:t,wordCount:0,openingTextPreview:"",startPosition:e?.index??null,existingSceneID:e?la(e,"PD-SCENE"):null},r.push(c),c):null,p=(e,t)=>{!a||!c||t<=0||(a.wordCount+=t,c.wordCount+=t,c.openingTextPreview||!e||pa.has(e)||(c.openingTextPreview=e.length>180?`${e.slice(0,177)}...`:e))};if(e.forEach((e,t)=>{e.index=t;const n=String(e.text||"").trim();if(!n)return;o.push(n);const r=ia(n);return ca(e,1)?(s+=1,l(e,n),i+=r,void(a.wordCount+=r)):(a||l(e,"Opening pages",!0),ca(e,2)?(u(e,n),i+=r,void p(n,r)):void(pa.has(n)?u(e,null):(i+=r,p(n,r))))}),0===i)throw new Error("The document appears to be empty. Add manuscript text, then try again.");if(0===s)throw new Error("No chapters were detected. Make sure your chapter headings use Heading 1, or add clear chapter headings.");const h=o.join("\n");return{previewID:"00000000-0000-0000-0000-000000000000",userID:t.userID||t.UserID||0,onboardingID:t.onboardingID||t.OnboardingID||0,projectID:t.projectID||t.ProjectID||0,bookID:t.bookID||t.BookID||0,source:"WordCompanion",documentTitle:Ho()||"Word manuscript",companionDocumentIdentifier:na(),totalWordCount:i,chapterCount:n.length,sceneCount:r.length,characterCandidateCount:0,createdUtc:(new Date).toISOString(),chapters:n,scenes:r,characterCandidates:[],documentTextForDiscovery:h}})(await window.Word.run(async t=>{const n=t.document.body.paragraphs;n.load("items/text,items/style,items/styleBuiltIn"),await t.sync(),await fa(e,"Detecting chapters",20);for(const e of n.items)(ca(e,1)||ca(e,2))&&e.contentControls.load("items/tag,title");return await t.sync(),n.items}),e);await fa(e,"Detecting scene breaks",50,n),await fa(e,`Scene ${n.sceneCount} of ${n.sceneCount} scanned`,70,n),await fa(e,"Finding character candidates",85,n);const r=n.documentTextForDiscovery||"";delete n.documentTextForDiscovery;try{const e=await Xa("/api/word-companion/manuscript/discover-characters",{projectId:n.projectID,documentText:r,includeExcluded:!0});n.characterCandidates=(t=e?.candidates,(Array.isArray(t)?t:[]).map((e,t)=>({temporaryCharacterKey:`character-${t+1}`,name:ya(e,"text","Text")||ya(e,"name","Name")||"",mentionCount:Number.parseInt(ya(e,"mentionCount","MentionCount")||"0",10)||0,qualityScore:Number.parseInt(ya(e,"qualityScore","QualityScore")||"0",10)||0,category:ya(e,"category","Category")||"PossibleCharacter",reason:ya(e,"reason","Reason")||"",isExistingCharacterMatch:!!ya(e,"isExistingCharacterMatch","IsExistingCharacterMatch"),suggestedImportance:null})).filter(e=>e.name)),n.characterCandidateCount=n.characterCandidates.filter(e=>"excluded"!==String(e.category||"").toLowerCase()).length}catch(e){console.warn("Unable to refine onboarding character candidates.",e),n.characterCandidates=[],n.characterCandidateCount=0}await fa(e,"Preparing preview",95,n),await Sr.invoke("CompleteOnboardingScan",n)}catch(t){console.error("Unable to complete onboarding scan.",t),await Ia(e,Sa(t))}else await Ia(e,"Open your manuscript in Microsoft Word, then try the scan again.")},Sa=e=>{const t=Do(e);return/No chapters were detected/i.test(t)?"No chapters were detected. Make sure your chapter headings use Heading 1, or add clear chapter headings.":/empty/i.test(t)?"The document appears to be empty. Add manuscript text, then try again.":/network|disconnect|connection/i.test(t)?"The Word Companion disconnected before the scan finished. Reopen Word and try again.":`The scan could not finish: ${t}`},Ca=(e,t)=>{const n=(t||[]).find(e=>String(e.text||"").trim());if(!e||!n)return e?.currentParagraphIndex??-1;const r=e.paragraphs.filter(e=>((e,t)=>String(e?.text||"").trim()===String(t?.text||"").trim()&&aa(e)===aa(t))(e,n)).map(e=>e.index);return 0===r.length?e.currentParagraphIndex??-1:Number.isInteger(e.currentParagraphIndex)?r.reduce((t,n)=>Math.abs(n-e.currentParagraphIndex){if(!vn)return!1;const t=((e,t)=>!e||!Number.isInteger(t)||t<0?null:e.chapters.filter(e=>e.startParagraphIndex<=t).at(-1)||null)(vn,e),n=((e,t)=>!e||!Number.isInteger(t)||t<0?null:e.scenes.filter(e=>e.startParagraphIndex<=t).at(-1)||null)(t,e),r=t?.startParagraphIndex!==vn.currentChapter?.startParagraphIndex||n?.startParagraphIndex!==vn.currentScene?.startParagraphIndex||t?.anchorId!==vn.currentChapter?.anchorId||n?.anchorId!==vn.currentScene?.anchorId;return vn.currentParagraphIndex=e,vn.currentChapter=t,vn.currentScene=n,!!r&&(Rr(t?.title,n?.title,n?.wordCount,t?.anchorId,n?.anchorId,t?.index),r)},va=e=>{if(!te)return;if(te.innerHTML="",0===e.chapters.length){const e=document.createElement("p");return e.textContent="No chapters detected. Use Heading 1 for chapter titles.",void te.append(e)}if(!e.chapters.some(e=>e.scenes.length>0)){const e=document.createElement("p");e.textContent="No scenes detected. Use Heading 2 for scene titles.",te.append(e)}for(const t of e.chapters){const e=document.createElement("div");e.className="word-companion-outline-chapter";const n=document.createElement("strong");if(n.textContent=t.title,e.append(n),t.scenes.length>0){const n=document.createElement("ul");for(const e of t.scenes){const t=document.createElement("li");t.textContent=e.title,n.append(t)}e.append(n)}te.append(e)}},xa=[{key:"alreadyLinked",title:"Already Linked"},{key:"couldLink",title:"Could Link"},{key:"wouldCreateChapters",title:"Would Create Chapters"},{key:"wouldCreateScenes",title:"Would Create Scenes"},{key:"manualReview",title:"Manual Review Required"}],Na={alreadyLinked:"Already Linked",couldLink:"Could Link",wouldCreateChapter:"Would Create Chapter",wouldCreateScene:"Would Create Scene",manualReview:"Manual Review Required"},Ea={alreadyLinked:"✓",couldLink:"~",wouldCreateChapter:"+",wouldCreateScene:"+",manualReview:"?"},Da=(e,t)=>Io(e,t).toLocaleLowerCase(),Pa=({category:e,action:t,type:n,wordTitle:r,match:o="",anchorStatus:a="None",matches:c=[],pdChapter:i=null,pdScene:s=null,wordChapter:d=null,wordScene:l=null,parentItem:u=null})=>({id:`${n}-${d?.index||0}-${l?.index||0}-${d?.paragraphIndex??l?.paragraphIndex??0}`,category:e,action:t,type:n,wordTitle:r,match:o,anchorStatus:a,matches:c,pdChapter:i,pdScene:s,wordChapter:d,wordScene:l,parentItem:u,result:"Pending",error:""}),La=(e,t)=>{Array.isArray(e[t.category])&&e[t.category].push(t)},qa=e=>Array.isArray(e?.scenes)?e.scenes:[],Aa=(e,t,n)=>{const r=e.anchorId?`PD-CHAPTER-${e.anchorId}`:"None";if(e.anchorId){const o=t.find(t=>t.chapterId===e.anchorId);if(o){const t=Pa({category:"alreadyLinked",action:"alreadyLinked",type:"Chapter",wordTitle:e.title,match:`Chapter ${o.chapterId}: ${o.title}`,anchorStatus:r,pdChapter:o,wordChapter:e});return La(n,t),t}const a=Pa({category:"manualReview",action:"manualReview",type:"Chapter",wordTitle:e.title,anchorStatus:`${r} not found`,matches:[],pdChapter:null,wordChapter:e});return La(n,a),a}const o=t.filter(t=>Da(t.title,"chapter")===Da(e.title,"chapter"));if(1===o.length){const t=Pa({category:"couldLink",action:"couldLink",type:"Chapter",wordTitle:e.title,match:`Chapter ${o[0].chapterId}: ${o[0].title}`,anchorStatus:r,pdChapter:o[0],wordChapter:e});return La(n,t),t}if(o.length>1){const t=Pa({category:"manualReview",action:"manualReview",type:"Chapter",wordTitle:e.title,anchorStatus:r,matches:o.map(e=>`Chapter ${e.chapterId}: ${e.title}`),pdChapter:null,wordChapter:e});return La(n,t),t}const a=Pa({category:"wouldCreateChapters",action:"wouldCreateChapter",type:"Chapter",wordTitle:e.title,anchorStatus:r,pdChapter:null,wordChapter:e});return La(n,a),a},$a=(e,t,n,r)=>{const o=e.anchorId?`PD-SCENE-${e.anchorId}`:"None";if(e.anchorId){const a=((e,t)=>{for(const n of e){const e=qa(n).find(e=>e.sceneId===t);if(e)return{chapter:n,scene:e}}return null})(n,e.anchorId);return a?void La(r,Pa({category:"alreadyLinked",action:"alreadyLinked",type:"Scene",wordTitle:e.title,match:`Scene ${a.scene.sceneId}: ${a.scene.title}`,anchorStatus:o,pdChapter:a.chapter,pdScene:a.scene,wordChapter:t?.wordChapter,wordScene:e,parentItem:t})):void La(r,Pa({category:"manualReview",action:"manualReview",type:"Scene",wordTitle:e.title,anchorStatus:`${o} not found`,wordChapter:t?.wordChapter,wordScene:e,parentItem:t}))}if(!t?.pdChapter&&"manualReview"===t?.category)return void La(r,Pa({category:"manualReview",action:"manualReview",type:"Scene",wordTitle:e.title,anchorStatus:o,matches:["Parent chapter requires manual review."],wordChapter:t?.wordChapter,wordScene:e,parentItem:t}));if(!t?.pdChapter)return void La(r,Pa({category:"wouldCreateScenes",action:"wouldCreateScene",type:"Scene",wordTitle:e.title,anchorStatus:o,wordChapter:t?.wordChapter,wordScene:e,parentItem:t}));const a=qa(t.pdChapter).filter(t=>Da(t.title,"scene")===Da(e.title,"scene"));1!==a.length?a.length>1?La(r,Pa({category:"manualReview",action:"manualReview",type:"Scene",wordTitle:e.title,anchorStatus:o,matches:a.map(e=>`Scene ${e.sceneId}: ${e.title}`),wordChapter:t.wordChapter,wordScene:e,parentItem:t})):La(r,Pa({category:"wouldCreateScenes",action:"wouldCreateScene",type:"Scene",wordTitle:e.title,anchorStatus:o,pdChapter:t.pdChapter,wordChapter:t.wordChapter,wordScene:e,parentItem:t})):La(r,Pa({category:"couldLink",action:"couldLink",type:"Scene",wordTitle:e.title,match:`Scene ${a[0].sceneId}: ${a[0].title}`,anchorStatus:o,pdChapter:t.pdChapter,pdScene:a[0],wordChapter:t.wordChapter,wordScene:e,parentItem:t}))},Ta=e=>{const t=document.createElement("article");t.className="word-companion-preview-item";const n=document.createElement("strong");n.textContent=`${Ea[e.action]||""} ${e.type}: ${e.wordTitle}`,t.append(n);const r=document.createElement("span");if(r.textContent=`Anchor: ${e.anchorStatus||"None"}`,t.append(r),e.match){const n=document.createElement("span");n.textContent=`Match: ${e.match}`,t.append(n)}if(Array.isArray(e.matches)&&e.matches.length>0){const n=document.createElement("div");n.className="word-companion-preview-matches";const r=document.createElement("span");r.textContent="Matches:",n.append(r);const o=document.createElement("ul");for(const t of e.matches){const e=document.createElement("li");e.textContent=t,o.append(e)}n.append(o),t.append(n)}const o=document.createElement("span");if(o.textContent=`Action: ${Na[e.action]||e.action}`,t.append(o),e.result&&"Pending"!==e.result){const n=document.createElement("span");n.textContent=e.error?`Result: ${e.result} - ${e.error}`:`Result: ${e.result}`,t.append(n)}return t},ja=e=>{if(ae){ae.innerHTML="";for(const t of xa){const n=document.createElement("section");n.className="word-companion-preview-group";const r=document.createElement("strong");r.textContent=t.title,n.append(r);const o=Array.isArray(e?.[t.key])?e[t.key]:[];if(0===o.length){const e=document.createElement("p");e.className="word-companion-empty-list",e.textContent="None",n.append(e)}else for(const e of o)n.append(Ta(e));ae.append(n)}Vr()}},Wa=async e=>{const t=e.document.body.paragraphs,n=e.document.getSelection().paragraphs;t.load("text,styleBuiltIn,style"),n.load("text,styleBuiltIn,style"),await e.sync();try{for(const e of t.items)(ca(e,1)||ca(e,2))&&e.contentControls.load("items/tag,title");await e.sync()}catch(e){console.warn("Unable to load Word heading content controls. Continuing without PlotDirector anchors.",e)}const r=ua(t.items,n.items);return r.bodyParagraphs=t.items,r},Ra=async(e,t)=>{const n=e.document.body.paragraphs,r=e.document.getSelection().paragraphs;n.load("text,styleBuiltIn,style"),r.load("text,styleBuiltIn,style"),await e.sync();const o=((e,t)=>{const n=e.map(ha),r=[];let o=0,a=0,c=null,i=null,s=0;const d=(e,t=null,n=null)=>c?(ma(i,e.index-1),i={index:c.scenes.length+1,paragraphIndex:e.index,startParagraphIndex:e.index,endParagraphIndex:e.index,title:t||`Scene ${c.scenes.length+1}`,anchorId:n,wordCount:0},c.scenes.push(i),i):null;for(const e of n)e.text&&(ca(e,1)?(ga(c,e.index-1),o+=1,c={index:r.length+1,paragraphIndex:e.index,startParagraphIndex:e.index,endParagraphIndex:e.index,title:e.text,anchorId:e.chapterAnchorId,wordCount:0,scenes:[]},r.push(c),i=null,d(e,"Scene 1",e.sceneAnchorId),s+=e.wordCount):c&&(t&&pa.has(e.text)?(a+=1,d(e)):(c.wordCount+=e.wordCount,i&&(i.wordCount+=e.wordCount),s+=e.wordCount)));return ga(c,n.length-1),{paragraphs:n,chapters:r,usesExplicitScenes:!!t,paragraphCount:n.length,heading1Count:o,heading2Count:a,documentWordCount:s,currentParagraphIndex:null,currentChapter:null,currentScene:null}})(n.items,t),a=Ca(o,r.items);return vn=o,ka(a),vr("Cache rebuilt."),o},Ua=()=>window.performance&&"function"==typeof window.performance.now?window.performance.now():Date.now(),Ma=e=>!!e&&!e.stale&&e.generation===Gn&&e.documentGuid===na()&&e.sceneId===ln,Oa=async(e={})=>{const t=!!e.includeSelection,n=!1!==e.updateDisplay,r=Gn,o=Ua(),a=Wo(),c=Ro(),i=vn?.currentScene;if(!i||!Number.isInteger(i.startParagraphIndex)||!Number.isInteger(i.endParagraphIndex))return null;if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return null;const s=Ua(),d=await window.Word.run(async e=>{const n=e.document.body.paragraphs;n.load("text");let r=null;return t&&(r=e.document.getSelection().paragraphs,r.load("text,styleBuiltIn,style")),await e.sync(),{bodyTexts:n.items.map(e=>String(e.text||"")),selectionParagraphs:t&&r?r.items.map(e=>({text:String(e.text||""),styleBuiltIn:e.styleBuiltIn,style:e.style})):[]}}),l=Math.round(Ua()-s);if(r!==Gn)return xr(`Office snapshot: ${l}ms stale`),{stale:!0,generation:r};let u=!1;if(t){const e=Ca(vn,d.selectionParagraphs);u=ka(e)}const p=vn?.currentScene;if(!p||!Number.isInteger(p.startParagraphIndex)||!Number.isInteger(p.endParagraphIndex))return null;const h=[];let m=0;const g=Math.min(p.endParagraphIndex,d.bodyTexts.length-1),y=vn.currentChapter?.startParagraphIndex;for(let e=p.startParagraphIndex;e<=g;e+=1){const t=String(d.bodyTexts[e]||"").trim();t&&!pa.has(t)&&e!==y&&(h.push(t),m+=ia(t))}p.wordCount=m,n&&Rr(vn.currentChapter?.title,p.title,m,vn.currentChapter?.anchorId,p.anchorId,vn.currentChapter?.index);const w={documentGuid:na(),projectId:a?.projectId||null,bookId:c?.bookId||null,chapterId:un,sceneId:ln,chapterTitle:vn.currentChapter?.title||"",sceneTitle:p.title||"",sceneText:h.join("\n"),wordCount:m,selectionSignature:`${vn.currentParagraphIndex??-1}:${p.startParagraphIndex}:${p.endParagraphIndex}`,selectionChanged:u,generation:r};return Fn=w,xr(`Office snapshot: ${l}ms; scene words: ${m}; total snapshot: ${Math.round(Ua()-o)}ms`),w},Ba=async()=>Ma(Fn)?Fn:await Oa(),Ha=async()=>{if($r(""),!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return Rr(null,null,null),$r("Word document access is unavailable."),Eo({lastScan:"Failure",lastError:"Word document access is unavailable."}),!1;J&&(J.disabled=!0,J.textContent="Scanning..."),$r("Scanning document structure...");try{const e=await window.Word.run(async e=>{const t=Ro();return t?await Ra(e,!!t.usesExplicitScenes):(vn=null,await Wa(e))});return vn||Rr(e.currentChapter?.title,e.currentScene?.title,e.currentScene?.wordCount,e.currentChapter?.anchorId,e.currentScene?.anchorId,e.currentChapter?.index),mo("Not detected"),va(e),$r(""),Eo({lastScan:"Success",lastError:"-",paragraphs:String(e.paragraphCount),heading1:String(e.heading1Count),heading2:String(e.heading2Count)}),!0}catch(e){const t=Do(e);return console.error("Unable to scan the Word document.",e),Rr(null,null,null),$r(`Unable to scan the Word document: ${t}`),Eo({lastScan:"Failure",lastError:t}),!1}finally{J&&(J.disabled=!1,J.textContent="Refresh Document Structure")}},Ga=async()=>{const e=Ro();if(!Wo()||!e)return Hr("Select a project and book to analyse manuscript structure."),!1;if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return Hr("Word document access is unavailable."),Eo({lastScan:"Failure",lastError:"Word document access is unavailable."}),!1;ne&&(ne.disabled=!0,ne.textContent="Analysing..."),Hr("Analysing manuscript structure...");try{const t=await window.Word.run(async e=>await Wa(e)),n=await Qa(`/api/word-companion/books/${e.bookId}/structure`);return Rr(t.currentChapter?.title,t.currentScene?.title,t.currentScene?.wordCount,t.currentChapter?.anchorId,t.currentScene?.anchorId,t.currentChapter?.index),va(t),Sn=((e,t)=>{const n={alreadyLinked:[],couldLink:[],wouldCreateChapters:[],wouldCreateScenes:[],manualReview:[]},r=Array.isArray(t?.chapters)?t.chapters:[];for(const t of Array.isArray(e?.chapters)?e.chapters:[]){const e=Aa(t,r,n);for(const o of qa(t))$a(o,e,r,n)}return n})(t,n),ja(Sn),Hr("Preview generated. No changes have been applied."),Eo({lastScan:"Success",lastError:"-",paragraphs:String(t.paragraphCount),heading1:String(t.heading1Count),heading2:String(t.heading2Count)}),!0}catch(e){return console.error("Unable to analyse manuscript structure.",e),Hr("Unable to analyse manuscript structure."),Eo({lastScan:"Failure",lastError:Do(e)}),!1}finally{ne&&(ne.textContent="Analyse Manuscript Structure",Kr())}},Fa=e=>{Wn&&(Wn(e),Wn=null),It?.close?It.close():It&&(It.hidden=!0)},Va=()=>{const e=((e=Sn)=>{const t=Fr(e);return{linkChapters:t.filter(e=>"couldLink"===e.category&&"Chapter"===e.type).length,linkScenes:t.filter(e=>"couldLink"===e.category&&"Scene"===e.type).length,createChapters:t.filter(e=>"wouldCreateChapters"===e.category).length,createScenes:t.filter(e=>"wouldCreateScenes"===e.category).length,manualReview:Array.isArray(e?.manualReview)?e.manualReview.length:0}})();return(e=>{if(!bt)return;bt.innerHTML="";const t=[`Link ${e.linkChapters+e.linkScenes} chapters/scenes`,`Create ${e.createChapters} chapters`,`Create ${e.createScenes} scenes`],n=document.createElement("ul");for(const e of t){const t=document.createElement("li");t.textContent=e,n.append(t)}bt.append(n)})(e),It?new Promise(e=>{Wn=e,It.showModal?It.showModal():It.hidden=!1}):Promise.resolve(window.confirm(`Apply Structure Sync?\n\nThis will:\n\n- Link ${e.linkChapters+e.linkScenes} chapters/scenes\n- Create ${e.createChapters} chapters\n- Create ${e.createScenes} scenes\n\nManual review items will be skipped.`))},Ka=(e,t,n="")=>{e.result=t,e.error=n},Ya=e=>Number.isInteger(e?.wordChapter?.index)&&e.wordChapter.index>0?10*e.wordChapter.index:null,Ja=e=>Number.isInteger(e?.wordScene?.index)&&e.wordScene.index>0?10*e.wordScene.index:null,_a=async(e,t)=>{const n=await Xa(`/api/word-companion/books/${e}/chapters`,{title:fo(t.wordTitle),sortOrder:Ya(t)});if(!n?.chapterId)throw new Error("Chapter creation did not return a chapter ID.");ro(),t.pdChapter={chapterId:n.chapterId,title:n.title||t.wordTitle,sortOrder:n.sortOrder||Ya(t)||0,scenes:[]},t.match=`Chapter ${t.pdChapter.chapterId}: ${t.pdChapter.title}`},za=async(e,t)=>{const n=t.pdChapter||t.parentItem?.pdChapter;if(!n?.chapterId)throw new Error("Scene parent chapter is not available.");const r=await Xa(`/api/word-companion/books/${e}/chapters/${n.chapterId}/scenes`,{sceneTitle:fo(t.wordTitle),sortOrder:Ja(t)});if(!r?.sceneId)throw new Error("Scene creation did not return a scene ID.");ro(),t.pdChapter=n,t.pdScene={sceneId:r.sceneId,title:t.wordTitle,sortOrder:Ja(t)||0},t.match=`Scene ${t.pdScene.sceneId}: ${t.pdScene.title}`},Qa=async(e,t={})=>{const n=await window.fetch(e,{credentials:"same-origin",...t,headers:{Accept:"application/json",...t.headers||{}}});if(!n.ok)throw new Error(`Request failed: ${n.status}`);return await n.json()},Xa=(e,t)=>Qa(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}),Za=async(e="")=>{qn=!1,An=0,Pn=[],k&&(k.innerHTML=""),Mr(S,!0),Jo(!1),Ko(""),Mr(v,!0),e&&$r(e);const t=await tc();e&&t&&rn&&$r(e)},ec=async(e=ln,t=un)=>{const n=Wo(),r=Ro(),o=na(),a=Number.parseInt(e||"",10),c=Number.parseInt(t||"",10);if(!n||!r||!o||!Number.isInteger(a)||a<=0||!Number.isInteger(c)||c<=0)return;const i=`${n.projectId}:${r.bookId}:${a}`;if(wr!==i){wr=i;try{await Xa("/api/word-companion/runtime/current-scene",{documentGuid:o,projectId:n.projectId,bookId:r.bookId,chapterId:c,sceneId:a}),vr("Current scene follow event sent.")}catch(e){wr="",console.debug("Unable to notify PlotDirector of current scene.",e)}}},tc=async()=>{const e=Ro();if(!(e&&Zt&&en&&window.Word&&"function"==typeof window.Word.run))return!1;$r("Reading manuscript position...");try{ro(),await Promise.all([kc(!0),vc(!0),xc(!0)]),await So(e.bookId);const t=await window.Word.run(async t=>await Ra(t,!!e.usesExplicitScenes));return va(t),Eo({lastScan:"Success",lastError:"-",paragraphs:String(t.paragraphCount),heading1:String(t.heading1Count),heading2:String(t.heading2Count)}),rn?(on?await qc():await Lc(),zr(!1),_r("Live"),$r(""),!0):(mo("Not detected"),zr(!1),_r("Live"),$r("Manuscript linked. Click inside a chapter to show the current scene."),!0)}catch(e){return console.error("Unable to initialise bound manuscript runtime.",e),vn=null,_r("Manual"),$r(`Unable to read manuscript position: ${Do(e)}`),Eo({lastScan:"Failure",lastError:Do(e)}),!1}},nc=async n=>{Cn=null,Tc(),Ur(),no(),mo("Not detected"),_r("Manual"),Lr("Connected"),qo(e,null),qo(t,null),H&&(H.value=""),Ao(G,"Select a project first"),_o(),h&&(h.value=""),Ao(m,"Select a project first"),zo(n||"Select a Project and Book to begin."),$r(n||"Manuscript unlinked."),Br("No resolved PlotDirector scene."),Jo(!0),Or()},rc=e=>e?[...e.querySelectorAll("input[type='checkbox']:checked")].map(e=>Number.parseInt(e.value||"",10)).filter(e=>Number.isInteger(e)&&e>0):[],oc=()=>{const e=Number.parseInt(We?.value||"",10);return Number.isInteger(e)&&e>0?e:null},ac=(e,t,n,r)=>{const o=((e,t)=>da(e).find(e=>sa(e.tag,t))||null)(e,r),a=o||e.getRange().insertContentControl();return a.title=t,a.tag=n,a.appearance="Hidden",a},cc=async(e="PlotDirector IDs attached successfully.")=>{if(!ln||!un)return $r("No resolved PlotDirector scene."),!1;if((dn&&dn!==ln||sn&&sn!==un)&&!window.confirm("This scene is already linked. Replace the existing PlotDirector link?"))return!1;if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return $r("Word document access is unavailable."),!1;pe&&(pe.disabled=!0,pe.textContent="Attaching...");try{return await window.Word.run(async e=>{const t=await Wa(e);if(!t.currentChapter||!t.currentScene)throw new Error("No scene detected in Word. Use Heading 2 for scenes.");const n=t.bodyParagraphs[t.currentChapter.paragraphIndex],r=t.bodyParagraphs[t.currentScene.paragraphIndex];if(!n||!r)throw new Error("Unable to locate the current Word headings.");ac(n,"PlotDirector Chapter",`PD-CHAPTER-${un}`,"PD-CHAPTER"),ac(r,"PlotDirector Scene",`PD-SCENE-${ln}`,"PD-SCENE"),await e.sync()}),sn=un,dn=ln,mn=!1,hn="SceneID Anchor",pn="Anchor",$r(e),Eo({lastError:"-"}),Wr(),!0}catch(e){return console.error("Unable to attach PlotDirector IDs.",e),$r("Unable to attach PlotDirector IDs."),Eo({lastError:Do(e)}),Wr(),!1}finally{pe&&(pe.textContent=dn===ln?"Attach PlotDirector IDs":"Reattach PlotDirector IDs",Wr())}},ic=async e=>{if(!un)return co("No resolved PlotDirector chapter."),!1;if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return co("Word document access is unavailable."),!1;try{return await window.Word.run(async e=>{const t=await Wa(e);if(!t.currentChapter)throw new Error("No chapter detected in Word. Use Heading 1 for chapters.");const n=t.bodyParagraphs[t.currentChapter.paragraphIndex];if(!n)throw new Error("Unable to locate the current Word chapter heading.");ac(n,"PlotDirector Chapter",`PD-CHAPTER-${un}`,"PD-CHAPTER"),await e.sync()}),sn=un,mn=!1,hn="ChapterID Anchor",pn="Chapter anchor",co(e),$r(e),Eo({lastError:"-"}),Wr(),!0}catch(e){return console.error("Unable to attach PlotDirector chapter ID.",e),co("Unable to attach PlotDirector chapter ID."),Eo({lastError:Do(e)}),Wr(),!1}},sc=async e=>{lo(),await qc(),$r(e),co(e)},dc=e=>{bn&&(bn(e),bn=null),mt?.close?mt.close():mt&&(mt.hidden=!0)},lc=()=>{if(!lt)return;const e=fo(dt?.value||"").toLocaleLowerCase(),t=fn.filter(t=>!e||String(t.title||"").toLocaleLowerCase().includes(e));if(lt.innerHTML="",In=null,ut&&(ut.disabled=!0),0===t.length){const e=document.createElement("p");return e.className="word-companion-empty-list",e.textContent="No chapters found.",void lt.append(e)}for(const e of t){const t=Number.parseInt(e.chapterId||"",10);if(!Number.isInteger(t)||t<=0)continue;const n=document.createElement("label");n.className="word-companion-scene-option";const r=document.createElement("input");r.type="radio",r.name="word-companion-link-chapter",r.value=String(t),r.addEventListener("change",()=>{In=t,ut&&(ut.disabled=!1)});const o=document.createElement("span");o.textContent=e.title||"Untitled chapter",n.append(r,o),lt.append(n)}},uc=()=>{st?.close?st.close():st&&(st.hidden=!0)},pc=async(e,t,n)=>{const r=Ro();if(!r)return ao("Select a PlotDirector book first."),!1;await xo(r.bookId,e,t,"Manual link","Title Match");return!!await cc(n)&&(ao(n),po(),!0)},hc=e=>{wn&&(wn(e),wn=null),rt?.close?rt.close():rt&&(rt.hidden=!0)},mc=()=>{if(!Ze)return;const e=fo(Xe?.value||"").toLocaleLowerCase(),t=gn.filter(t=>!e||String(t.title||"").toLocaleLowerCase().includes(e));if(Ze.innerHTML="",yn=null,et&&(et.disabled=!0),0===t.length){const e=document.createElement("p");return e.className="word-companion-empty-list",e.textContent="No scenes found.",void Ze.append(e)}for(const e of t){const t=Number.parseInt(e.sceneId||"",10);if(!Number.isInteger(t)||t<=0)continue;const n=document.createElement("label");n.className="word-companion-scene-option";const r=document.createElement("input");r.type="radio",r.name="word-companion-link-scene",r.value=String(t),r.addEventListener("change",()=>{yn=t,et&&(et.disabled=!1)});const o=document.createElement("span");o.textContent=e.title||"Untitled scene",n.append(r,o),Ze.append(n)}},gc=()=>{Qe?.close?Qe.close():Qe&&(Qe.hidden=!0)},yc=()=>{_n=!1,Jr(),zn&&(zn=!1,fc(1e3))},wc=async(e={})=>{const t=e.source||"manual",n=Ua();if(!ln)return void Br("No resolved PlotDirector scene.");if(!un)return void Br("No resolved PlotDirector chapter.");if(Un)return void Br("Refresh Current Scene before syncing.");const r=Ro(),o=na();if(!r||!o)return void Br("Bound manuscript metadata is unavailable.");if(_n)return zn=!0,void vr("Scene sync deferred; sync already running.");Ur(),_n=!0,zn=!1;let a=e.snapshot||null;try{a=Ma(a)?a:await Ba()}catch(e){return Br("Sync failed"),Eo({lastError:Do(e)}),vr("Scene sync failed."),void yc()}if(!Ma(a))return Br("Waiting for typing to pause"),vr("Scene sync skipped (stale snapshot)."),void yc();const c=a.wordCount;if(!Number.isInteger(c)||c<0)return Br("Sync failed"),vr("Scene sync failed."),void yc();if(Qn===a.sceneId&&Xn===c)return Br("Synced"),vr("Sync skipped (count unchanged)."),void yc();vr("Scene word count changed."),ke&&(ke.disabled=!0,ke.textContent="Syncing..."),Br("Syncing..."),vr(`Scene sync started (${t}).`);try{const e=Ua(),t=await Xa("/api/word-companion/runtime/scene-word-count",{documentGuid:a.documentGuid||o,bookId:a.bookId||r.bookId,chapterId:a.chapterId,sceneId:a.sceneId,wordCount:c});if(xr(`Word count sync API: ${Math.round(Ua()-e)}ms`),!Ma(a))return void vr("Scene sync result ignored (stale snapshot).");const i=t?.actualWords??c,s=new Date;Qn=a.sceneId,Xn=i,No(xe,Zr(i)),No(fe,Zr(i)),No(Ne,eo(s)),No(Ce,eo(s)),Br("Synced"),vr("Scene sync succeeded."),xr(`Total word count sync: ${Math.round(Ua()-n)}ms`)}catch(e){Br("Sync failed"),Eo({lastError:Do(e)}),vr("Scene sync failed.")}finally{ke&&(ke.textContent="Sync Current Scene"),yc()}},fc=(e=4e3)=>{Ur(),ln&&un&&!Un&&!_n&&(Jn=window.setTimeout(()=>{Jn=null,wc({source:"automatic"})},e))},Ic=()=>{Zn&&(window.clearTimeout(Zn),Zn=null)},bc=()=>{nr=null,rr="",or=[],ar=null,cr="",Ic()},Sc=()=>{ir=null,sr="",dr=[],lr=null,ur=""},Cc=()=>{pr=null,hr="",mr=[],gr=null,yr=""},kc=async(e=!1)=>{const t=Wo(),n=na();if(!t||!n)return or=[],[];if(!e&&nr===t.projectId&&rr===n&&or.length>0)return or;const r=await Qa(`/api/word-companion/runtime/characters?projectId=${encodeURIComponent(t.projectId)}&documentGuid=${encodeURIComponent(n)}`);return nr=t.projectId,rr=n,or=Array.isArray(r?.characters)?r.characters:[],vr("Character alias cache refreshed."),or},vc=async(e=!1)=>{const t=Wo(),n=na();if(!t||!n)return dr=[],[];if(!e&&ir===t.projectId&&sr===n&&dr.length>0)return dr;const r=await Qa(`/api/word-companion/runtime/assets?projectId=${encodeURIComponent(t.projectId)}&documentGuid=${encodeURIComponent(n)}`);return ir=t.projectId,sr=n,dr=Array.isArray(r?.assets)?r.assets:[],vr(`Asset cache loaded: ${dr.length} aliases.`),dr},xc=async(e=!1)=>{const t=Wo(),n=na();if(!t||!n)return mr=[],[];if(!e&&pr===t.projectId&&hr===n&&mr.length>0)return mr;const r=await Qa(`/api/word-companion/runtime/locations?projectId=${encodeURIComponent(t.projectId)}&documentGuid=${encodeURIComponent(n)}`);return pr=t.projectId,hr=n,mr=Array.isArray(r?.locations)?r.locations:[],vr(`Location cache loaded: ${mr.length} aliases.`),mr},Nc=(e,t)=>{const n=String(t||"").trim();if(!n)return!1;const r=(o=n,String(o||"").replace(/[.*+?^${}()|[\]\\]/g,"\\$&")).replace(/\s+/g,"\\s+");var o;return new RegExp(`(^|[^\\p{L}\\p{N}_])${r}(?=$|[^\\p{L}\\p{N}_])`,"iu").test(e)},Ec=e=>{if(e?.excludeFromCompanionDetection)return!1;const t=Number.parseInt(e?.detectionPriority||"0",10);return n=e?.matchText,String(n||"").trim().split(/\s+/).filter(Boolean).length>=2||Number.isInteger(t)&&t>=75;var n},Dc=async()=>{if(Zn=null,!ln||Un||!na())return;if(er)return tr=!0,void vr("Character suggestion detection deferred; detection already running.");er=!0,tr=!1;const e=Ua();try{const t=await Ba();if(!Ma(t))return void vr("Runtime suggestions skipped (stale snapshot).");const[n,r,o]=await Promise.all([kc(),vc(),xc()]);if(!Ma(t))return void vr("Runtime suggestions skipped after cache load (stale snapshot).");const a=t.sceneText||"",c=Ua(),i=((e,t)=>{if(!e||!Array.isArray(t)||0===t.length)return[];const n=new Map;for(const r of t){const t=Number.parseInt(r.characterId||"",10);!Number.isInteger(t)||t<=0||n.has(t)||Nc(e,r.matchText)&&n.set(t,r.name||r.matchText||"Character")}return[...n.entries()].map(([e,t])=>({characterId:e,name:t}))})(a,n);xr(`Character detection: ${Math.round(Ua()-c)}ms`);const s=i.map(e=>e.characterId),d=s.slice().sort((e,t)=>e-t).join(","),l=Ua(),u=((e,t)=>{if(!e||!Array.isArray(t)||0===t.length)return[];const n=new Map;for(const r of t){const t=Number.parseInt(r.assetId||"",10);!Number.isInteger(t)||t<=0||n.has(t)||Nc(e,r.matchText)&&n.set(t,r.name||r.matchText||"Asset")}return[...n.entries()].map(([e,t])=>({assetId:e,name:t}))})(a,r);xr(`Asset detection: ${Math.round(Ua()-l)}ms`);const p=u.map(e=>e.assetId),h=p.slice().sort((e,t)=>e-t).join(","),m=Ua(),g=((e,t)=>{if(!e||!Array.isArray(t)||0===t.length)return[];const n=new Map;for(const r of t){const t=Number.parseInt(r.locationId||"",10);!Number.isInteger(t)||t<=0||n.has(t)||!Ec(r)||Nc(e,r.matchText)&&n.set(t,r.name||r.matchText||"Location")}return[...n.entries()].map(([e,t])=>({locationId:e,name:t}))})(a,o);xr(`Location detection: ${Math.round(Ua()-m)}ms`);const y=g.map(e=>e.locationId),w=y.slice().sort((e,t)=>e-t).join(",");if(!d||ar===ln&&cr===d)vr("Character suggestions skipped.");else{if(!Ma(t))return void vr("Character suggestions skipped before submit (stale snapshot).");const e=Ua(),n=await Xa("/api/word-companion/runtime/character-suggestions",{documentGuid:t.documentGuid,sceneId:t.sceneId,characterIds:s});if(xr(`Character suggestion API: ${Math.round(Ua()-e)}ms`),!Ma(t))return void vr("Character suggestion result ignored (stale snapshot).");if(ar=t.sceneId,cr=d,(n?.createdCount||0)>0){const e=i.slice(0,3).map(e=>e.name).join(", "),t=i.length>3?" +"+(i.length-3):"";$r(e?`Characters detected: ${e}${t}`:n.message)}vr(n?.message||"Character suggestions submitted.")}if(!h||lr===ln&&ur===h)vr("Asset suggestions skipped.");else{if(!Ma(t))return void vr("Asset suggestions skipped before submit (stale snapshot).");vr(`Asset matches found: ${u.map(e=>e.name).join(", ")}`);const e=Ua(),n=await Xa("/api/word-companion/runtime/asset-suggestions",{documentGuid:t.documentGuid,sceneId:t.sceneId,assetIds:p});if(xr(`Asset suggestion API: ${Math.round(Ua()-e)}ms`),!Ma(t))return void vr("Asset suggestion result ignored (stale snapshot).");if(lr=t.sceneId,ur=h,(n?.createdCount||0)>0){const e=u.slice(0,3).map(e=>e.name).join(", "),t=u.length>3?" +"+(u.length-3):"";$r(e?`Assets detected: ${e}${t}`:n.message)}vr(n?.message||"Asset suggestions submitted.")}if(!w||gr===ln&&yr===w)vr("Location suggestions skipped.");else{if(!Ma(t))return void vr("Location suggestions skipped before submit (stale snapshot).");vr(`Location matches found: ${g.map(e=>e.name).join(", ")}`);const e=Ua(),n=await Xa("/api/word-companion/runtime/location-suggestions",{documentGuid:t.documentGuid,sceneId:t.sceneId,locationIds:y});if(xr(`Location suggestion API: ${Math.round(Ua()-e)}ms`),!Ma(t))return void vr("Location suggestion result ignored (stale snapshot).");if(gr=t.sceneId,yr=w,(n?.createdCount||0)>0){const e=g.slice(0,3).map(e=>e.name).join(", "),t=g.length>3?" +"+(g.length-3):"";$r(e?`Locations detected: ${e}${t}`:n.message)}vr(n?.message||"Location suggestions submitted.")}xr(`Total suggestion pass: ${Math.round(Ua()-e)}ms`)}catch(e){console.error("Unable to detect runtime suggestions.",e),Eo({lastError:Do(e)})}finally{er=!1,tr&&(tr=!1,Pc(1500))}},Pc=(e=2500)=>{Ic(),!ln||Un||er?er&&(tr=!0):Zn=window.setTimeout(()=>{Dc()},e)},Lc=async()=>{const e=Wo(),t=Ro(),n=Io(rn,"chapter");if(bo({projectId:e?.projectId,projectTitle:e?.title,bookId:t?.bookId,bookTitle:t?To(t):void 0,detectedChapter:rn,detectedScene:on,requestChapter:n,requestScene:"",result:"Not run",chapterId:null,sceneId:null}),!t)return mo("Not detected"),$r("Select a PlotDirector book first."),!1;if(!rn)return mo("Not detected"),$r("No chapter detected in Word. Use Heading 1 for chapters."),!1;mo("Not detected");try{const e=await Co(t.bookId),r=sn?e.find(e=>e.chapterId===sn):null,o=n.toLocaleLowerCase(),a=o?e.find(e=>Io(e.title,"chapter").toLocaleLowerCase()===o):null,c=r||a;return c?(lo(),Qr(null,c.chapterId,r?"Chapter anchor":"Chapter title"),hn=r?"ChapterID Anchor":"Title Match",Tr("Chapter linked to PlotDirector"),$r("No scene detected in Word. Use Heading 2 for scenes."),bo({result:"Chapter matched",chapterId:c.chapterId,sceneId:null}),!0):(Qr(null),Tr("Chapter not found in PlotDirector."),$r("Chapter not found in PlotDirector."),bo({result:"Chapter not matched",chapterId:null,sceneId:null}),uo("Chapter not found in PlotDirector."),!1)}catch(e){return mo("Not found in PlotDirector"),Eo({lastError:Do(e)}),$r("Unable to load PlotDirector chapter data."),!1}},qc=async()=>{const e=Wo(),t=Ro(),n=Io(rn,"chapter"),r=Io(on,"scene");if(bo({projectId:e?.projectId,projectTitle:e?.title,bookId:t?.bookId,bookTitle:t?To(t):void 0,detectedChapter:rn,detectedScene:on,requestChapter:n,requestScene:r,result:"Not run",chapterId:null,sceneId:null}),jr({anchorType:dn?"SceneID Anchor":sn?"ChapterID Anchor":"Title Match",storedSceneId:dn,storedChapterId:sn,resolutionMethod:"-"}),!t)return mo("Not detected"),$r("Select a PlotDirector book first."),bo({result:"Error"}),!1;if(!rn||!on)return mo("Not detected"),$r("No scene detected in Word. Use Heading 2 for scenes."),bo({result:"Error"}),!1;ce&&(ce.disabled=!0,ce.textContent="Refreshing...");const o=()=>{ce&&(ce.disabled=!1,ce.textContent="Refresh PlotDirector Scene")};mo("Not detected"),$r("Resolving PlotDirector scene...");try{if(dn){const e=await(async(e,t)=>{const n=await Qa(`/api/word-companion/books/${e}/structure`),r=Array.isArray(n?.chapters)?n.chapters:[];for(const e of r){const n=Array.isArray(e.scenes)?e.scenes:[];for(const r of n)if(t(e,r))return{chapter:e,scene:r}}return null})(t.bookId,(e,t)=>t.sceneId===dn);if(e)return bo({result:"Matched",chapterId:e.chapter.chapterId,sceneId:e.scene.sceneId}),await xo(t.bookId,e.scene.sceneId,e.chapter.chapterId,"Anchor","SceneID Anchor"),!0;mn=!0,hn="SceneID Anchor",Tr("Not found in PlotDirector"),$r("Stored PlotDirector ID is invalid."),bo({result:"Error",chapterId:sn,sceneId:dn}),jr({anchorType:"SceneID Anchor",storedSceneId:dn,storedChapterId:sn,resolutionMethod:"Anchor"})}if(sn){const e=(await So(t.bookId)).find(e=>e.chapterId===sn),n=e?(Array.isArray(e.scenes)?e.scenes:[]).find(e=>Io(e.title,"scene").toLocaleLowerCase()===r.toLocaleLowerCase()):null;if(n)return bo({result:"Matched",chapterId:e.chapterId,sceneId:n.sceneId}),await xo(t.bookId,n.sceneId,e.chapterId,"Anchor","ChapterID Anchor"),!0;e&&!dn?(hn="ChapterID Anchor",Qr(null,sn,"Chapter anchor"),bo({result:"Chapter matched",chapterId:sn,sceneId:null})):dn||(hn="ChapterID Anchor",bo({result:"Chapter anchor not found",chapterId:sn,sceneId:null}))}const e=await Xa(`/api/word-companion/books/${t.bookId}/resolve-scene`,{chapterTitle:n,sceneTitle:r});if(!e?.matched||!e.sceneId){const t=mn,n=Number.parseInt(e?.chapterId||"",10),r=Number.isInteger(un)&&un>0?un:null,a=Number.isInteger(n)&&n>0?n:r,c=!!e?.chapterMatched||!!a;return mo("Not found in PlotDirector"),mn=t,c&&(Qr(null,a,a===r?"Chapter anchor":"Chapter title"),Tr("Scene not found in PlotDirector.")),$r(t?"Stored PlotDirector ID is invalid.":c?"Scene not found in PlotDirector.":"This chapter does not exist in PlotDirector. Create or link the chapter first."),bo({result:"Not matched",chapterId:c?a:e?.chapterId,sceneId:e?.sceneId}),t||(c?(lo(),ho("Scene not found in PlotDirector.")):(po(),uo("Chapter not found in PlotDirector."))),o(),!1}const a=mn;return bo({result:a?"Matched by title fallback":"Matched",chapterId:e.chapterId,sceneId:e.sceneId}),await xo(t.bookId,e.sceneId,e.chapterId,a?"Title fallback":"Title","Title Match"),a&&(mn=!0,$r("Stored PlotDirector ID is invalid."),Wr()),!0}catch(e){const t=mn;return Eo({lastError:Do(e)}),mo("Not found in PlotDirector"),mn=t,$r(t?"Stored PlotDirector ID is invalid.":"Unable to load PlotDirector scene data."),bo({result:"Error"}),!1}finally{o()}},Ac=async()=>{if(Mn=null,Yn)return Hn=!0,void vr("Runtime update deferred; update already running.");if(!Jt||!Ro())return On=!1,void vr("Runtime update skipped.");if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return On=!1,_r("Manual"),void $r("Automatic scene tracking is unavailable in this Word version. Use Refresh Current Scene.");if(!vn)return On=!1,_r("Manual"),void $r("Document structure is not cached yet. Use Refresh Current Scene.");const e=Date.now()-Bn;if(On&&e{Mn&&window.clearTimeout(Mn),Mn=window.setTimeout(Ac,Math.max(0,e)),_r("Waiting for typing to pause"),vr("Runtime update scheduled.")},Tc=()=>{Mn&&(window.clearTimeout(Mn),Mn=null),On=!1,Hn=!1},jc=()=>{((e="selection",t=1200)=>{if(On=!0,Bn=Date.now(),Gn+=1,Fn=null,Ur(),Ic(),_n&&(zn=!0),er&&(tr=!0),vr(`${e} changed.`),Yn)return Hn=!0,void _r("Waiting for typing to pause");$c(t)})("Selection")},Wc=()=>{if(Kn&&window.Office?.context?.document&&"function"==typeof window.Office.context.document.removeHandlerAsync&&window.Office?.EventType?.DocumentSelectionChanged)try{window.Office.context.document.removeHandlerAsync(window.Office.EventType.DocumentSelectionChanged,{handler:jc}),Kn=!1}catch(e){console.warn("Unable to remove Word selection tracking handler.",e)}},Rc=async(e,n)=>{if(vn=null,ro(),!e)return Xt=[],Ao(G,"Select a project first"),Ao(m,"Select a project first"),Ar(""),qo(t,null),ra(),mo("Not detected"),void Gr();Ao(G,"Loading books..."),Ar(""),mo("Not detected"),Gr("Select a book to analyse manuscript structure.");try{const r=await Qa(`/api/word-companion/projects/${e}/books`);if(Xt=Array.isArray(r.books)?r.books:[],0===Xt.length)return Ao(G,"No books available."),Ao(m,"No books available."),Ar("No books available."),qo(t,null),ra(),void zo("No books available.");$o(G,"Choose a book",Xt.map(e=>({...e,displayTitle:To(e)})),"bookId","displayTitle");const o=Xt.find(e=>e.bookId===n);o&&G?(G.value=String(o.bookId),qo(t,o.bookId)):qo(t,null),((e=null)=>{if(!m)return;if(0===Xt.length)return void Ao(m,"No books available.");$o(m,"Choose a book",Xt.map(e=>({...e,displayTitle:To(e)})),"bookId","displayTitle");const t=e||Number.parseInt(G?.value||"",10),n=Xt.find(e=>e.bookId===t);n&&(m.value=String(n.bookId)),Yo()})(o?.bookId||n),ra(),mo((Ro(),"Not detected")),Gr(Ro()?"Ready to analyse manuscript structure.":"Select a book to analyse manuscript structure."),zo(Mo()?"Ready to scan manuscript structure.":"Select a Book to begin.")}catch{Xt=[],Ao(G,"Unable to load books."),Ao(m,"Unable to load books."),Ar("Unable to load books."),qo(t,null),ra(),mo("Not detected"),Gr("Unable to load books."),zo("Unable to load books.")}};for(const e of i)e.addEventListener("click",()=>Dr(e.dataset.tabButton));Pr(),H?.addEventListener("change",async()=>{const n=Number.parseInt(H.value||"",10);no(),h&&Number.isInteger(n)&&n>0&&(h.value=String(n)),qo(e,Number.isInteger(n)&&n>0?n:null),qo(t,null),mo("Not detected"),Gr(),zo("Select a Book to begin."),await Rc(n,null),Fo()}),G?.addEventListener("change",()=>{const e=Number.parseInt(G.value||"",10);no(),qo(t,Number.isInteger(e)&&e>0?e:null),m&&Number.isInteger(e)&&e>0&&(m.value=String(e)),ra(),mo("Not detected"),Gr(Ro()?"Ready to analyse manuscript structure.":"Select a book to analyse manuscript structure."),zo(Mo()?"Ready to scan manuscript structure.":"Select a Book to begin."),Fo()}),h?.addEventListener("change",async()=>{const n=Number.parseInt(h.value||"",10);no(),H&&Number.isInteger(n)&&n>0&&(H.value=String(n)),qo(e,Number.isInteger(n)&&n>0?n:null),qo(t,null),zo("Select a Book to begin."),await Rc(n,null),Fo()}),m?.addEventListener("change",()=>{const e=Number.parseInt(m.value||"",10);no(),G&&Number.isInteger(e)&&e>0&&(G.value=String(e)),qo(t,Number.isInteger(e)&&e>0?e:null),ra(),zo(Mo()?"Ready to scan manuscript structure.":"Select a Book to begin."),Fo()}),g?.addEventListener("input",Yo),y?.addEventListener("click",async()=>{const e=Uo(),t=String(g?.value||"").trim();if(e&&t){y.disabled=!0,Ko("Creating Book...");try{const n=await Xa(`/api/word-companion/projects/${e.projectId}/books`,{title:t});g&&(g.value=""),H&&(H.value=String(e.projectId)),await Rc(e.projectId,n.bookId),m&&(m.value=String(n.bookId)),Ko("Ready to scan manuscript structure.")}catch(e){console.error("Unable to create Book from Word Companion.",e),Ko(`Unable to create Book: ${Do(e)}`)}finally{Yo()}}else Yo()}),w?.addEventListener("click",async()=>{const e=Uo(),t=Mo();if(e&&t)if(Zt&&en&&window.Word&&"function"==typeof window.Word.run){w&&(w.disabled=!0,w.textContent="Scanning..."),Ko("Scanning manuscript structure...");try{const n=await window.Word.run(async e=>{const n=e.document.body.paragraphs;return n.load("text,styleBuiltIn,style"),await e.sync(),((e,t)=>{const n=[];let r=null,o=null,a=0;const c=[],i=new Set(["***","###"]),s=()=>{if(!r)return null;const e={title:`Scene ${r.scenes.length+1}`,sortOrder:10*(r.scenes.length+1),wordCount:0};return r.scenes.push(e),e};return e.forEach(e=>{const d=String(e.text||"").trim();if(!d)return;if(c.push(d),ca(e,1))return r={title:d,sortOrder:10*(n.length+1),wordCount:0,scenes:[]},n.push(r),o=s(),void(a+=ia(d));if(!r||!o)return;if(t&&i.has(d))return void(o=s());const l=ia(d);r.wordCount+=l,o.wordCount+=l,a+=l}),{chapters:n,chapterCount:n.length,sceneCount:n.reduce((e,t)=>e+t.scenes.length,0),wordCount:a,documentText:c.join("\n")}})(n.items,!!t.usesExplicitScenes)});Dn=n,Ln=!1;const r=await Xa("/api/word-companion/manuscript/analyse",{projectId:e.projectId,bookId:t.bookId,documentGuid:ta(),chapterCount:n.chapterCount,sceneCount:n.sceneCount,wordCount:n.wordCount});En=r,(e=>{if(!b)return;b.innerHTML="";const t=document.createElement("strong");t.textContent="Manuscript Analysis",b.append(t);const n=document.createElement("dl"),r=[["Project",e.projectTitle],["Book",To({title:e.bookTitle,subtitle:e.bookSubtitle})],["Chapters",Number(e.chapterCount||0).toLocaleString()],["Scenes",Number(e.sceneCount||0).toLocaleString()],["Words",Number(e.wordCount||0).toLocaleString()]];for(const[e,t]of r){const r=document.createElement("dt");r.textContent=e;const o=document.createElement("dd");o.textContent=t||"-",n.append(r,o)}b.append(n),Mr(b,!1),Mr(D,!1),Ko(e.message||"Preview generated."),Yo()})(r);try{const t=await Xa("/api/word-companion/manuscript/discover-characters",{projectId:e.projectId,documentText:n.documentText||""});Pn=Array.isArray(t?.candidates)?t.candidates:[],Mr(S,!0),Mr(v,!0)}catch(e){console.error("Unable to discover character candidates.",e),Pn=[],Mr(S,!0),Mr(v,!0)}Eo({lastScan:"Success",lastError:"-",paragraphs:"-",heading1:String(n.chapterCount),heading2:"-"})}catch(e){console.error("Unable to scan manuscript for first-run import.",e),zo(`Unable to scan manuscript: ${Do(e)}`),Eo({lastScan:"Failure",lastError:Do(e)})}finally{w&&(w.textContent="Scan Manuscript"),Yo()}}else zo("Word document access is unavailable.");else zo("Select a Project and Book to begin.")}),P?.addEventListener("click",async()=>{const n=Uo(),o=Mo();if(!(n&&o&&Dn&&En?.canImport))return void Yo();let a=!1;if(!En.requiresReplaceConfirmation||(a=await new Promise(e=>{if(!q||"function"!=typeof q.showModal)return void e(window.confirm("This Book already contains planned chapters and scenes.\n\nImporting this manuscript will replace the planned structure.\n\nContinue?"));const t=t=>{A?.removeEventListener("click",n),$?.removeEventListener("click",r),q?.removeEventListener("cancel",o),q?.close(),e(t)},n=()=>t(!0),r=()=>t(!1),o=e=>{e.preventDefault(),t(!1)};A?.addEventListener("click",n),$?.addEventListener("click",r),q?.addEventListener("cancel",o),q.showModal()}),a)){$n=!0,Yo(),Ko("Importing manuscript structure...");try{const i=ta(),s=await Xa("/api/word-companion/manuscript/import",{projectId:n.projectId,bookId:o.bookId,documentGuid:i,bindingVersion:1,replacePlanned:a,chapters:Dn.chapters});if(!s.imported)return En={...En,canImport:!s.requiresReplaceConfirmation,requiresReplaceConfirmation:!!s.requiresReplaceConfirmation,message:s.message||En.message},Ko(s.message||"Import was not completed."),void Yo();const d=s.manuscriptDocument;await(c={documentGuid:d?.documentGuid||i,bookId:s.bookId,projectId:s.projectId,bindingVersion:d?.bindingVersion||1},new Promise((e,t)=>{const n=window.Office?.context?.document?.settings;n?(n.set(r.documentGuid,c.documentGuid),n.set(r.bookId,String(c.bookId)),n.set(r.projectId,String(c.projectId)),n.set(r.bindingVersion,String(c.bindingVersion||1)),n.saveAsync(n=>{n.status===window.Office.AsyncResultStatus.Succeeded?e():t(n.error||new Error("Unable to save Word document metadata."))})):t(new Error("Word document settings are unavailable."))})),Cn={documentGuid:d?.documentGuid||i,bookId:s.bookId,projectId:s.projectId,bindingVersion:d?.bindingVersion||1},qo(e,s.projectId),qo(t,s.bookId),H&&(H.value=String(s.projectId));const l=Pn;await Rc(s.projectId,s.bookId),Lr("Connected"),Ln=!0,qn=l.length>0,An=qn?Date.now():0,Pn=l,l.length>0?(Jo(!0),Xo(l),Mr(D,!0),Mr(v,!1),Ko(s.message||"Manuscript structure imported."),$r(s.message||"Manuscript structure imported."),window.setTimeout(Yo,750)):await Za(s.message||"Manuscript structure imported.")}catch(e){console.error("Unable to import manuscript structure.",e),Ko(`Unable to import manuscript: ${Do(e)}`)}finally{$n=!1,Yo()}var c}else Ko("Import cancelled.")}),x?.addEventListener("click",async()=>{const e=Uo()||Wo(),t=Qo();if(!qn||Date.now()-An<750||!e||0===t.length)Yo();else{Tn=!0,Yo(),No(C,"Creating selected characters...");try{const n=await Xa("/api/word-companion/manuscript/create-discovered-characters",{projectId:e.projectId,candidateNames:t});$r(n.message||"Characters created."),No(C,`${n.createdCount||0} created, ${n.skippedCount||0} already existed.`),await Za(n.message||"Characters created.")}catch(e){console.error("Unable to create discovered characters.",e),No(C,`Unable to create characters: ${Do(e)}`)}finally{Tn=!1,Yo()}}}),N?.addEventListener("click",()=>Za("Character creation skipped.")),E?.addEventListener("click",()=>Za()),f?.addEventListener("click",()=>Jo(!1)),L?.addEventListener("click",()=>zo("Import cancelled.")),J?.addEventListener("click",Ha),ne?.addEventListener("click",Ga),re?.addEventListener("click",async()=>{const e=Ro();if(!e||0===Fr().length||jn)return void Vr();if(!await Va())return;jn=!0,Kr(),Hr("Applying structure sync...");const t={createdChapters:0,createdScenes:0,linkedChapters:0,linkedScenes:0,skipped:Array.isArray(Sn?.manualReview)?Sn.manualReview.length:0,failed:0},n=Fr().filter(e=>"couldLink"===e.category&&"Chapter"===e.type),r=Fr().filter(e=>"wouldCreateChapters"===e.category),o=Fr().filter(e=>"couldLink"===e.category&&"Scene"===e.type),a=Fr().filter(e=>"wouldCreateScenes"===e.category),c=[];let i="";const s=(e,t)=>{c.push({item:e,counterName:t})},d=async(e,n)=>{try{await(async e=>{if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)throw new Error("Word document access is unavailable.");await window.Word.run(async t=>{const n=await Wa(t),r="Chapter"===e.type?e.wordChapter:e.wordScene,o=Number.isInteger(r?.paragraphIndex)?n.bodyParagraphs[r.paragraphIndex]:null;if(!o)throw new Error("Unable to locate the Word heading.");if("Chapter"===e.type){const t=e.pdChapter?.chapterId;if(!t)throw new Error("Chapter ID is not available.");ac(o,"PlotDirector Chapter",`PD-CHAPTER-${t}`,"PD-CHAPTER")}else{const t=e.pdScene?.sceneId;if(!t)throw new Error("Scene ID is not available.");ac(o,"PlotDirector Scene",`PD-SCENE-${t}`,"PD-SCENE")}await t.sync()})})(e),Ka(e,"Success"),t[n]+=1}catch(n){Ka(e,"Failed",Do(n)),t.failed+=1}};try{for(const e of n)s(e,"linkedChapters");for(const n of r)try{await _a(e.bookId,n),s(n,"createdChapters")}catch(e){Ka(n,"Failed",Do(e)),t.failed+=1}for(const e of o)s(e,"linkedScenes");for(const n of a)try{await za(e.bookId,n),s(n,"createdScenes")}catch(e){Ka(n,"Failed",Do(e)),t.failed+=1}for(const e of c)await d(e.item,e.counterName);for(const e of Sn.manualReview||[])Ka(e,"Skipped");ja(Sn),i=(e=>`Structure Sync Complete. Created: ${e.createdChapters} Chapters, ${e.createdScenes} Scenes. Linked: ${e.linkedChapters} Chapters, ${e.linkedScenes} Scenes. Skipped: ${e.skipped} Manual Review items. Failed: ${e.failed} Items.`)(t),Hr(i)}finally{jn=!1,Kr(),i&&(ro(),await Ga(),Hr(`${i} Preview refreshed.`))}}),St?.addEventListener("click",()=>Fa(!0)),Ct?.addEventListener("click",()=>Fa(!1)),It?.addEventListener("cancel",e=>{e.preventDefault(),Fa(!1)}),ce?.addEventListener("click",qc),Y?.addEventListener("click",async()=>{if(!Ro())return $r("Select a PlotDirector book first."),void mo("Not detected");Tc(),Xr(!0),_r("Updating...");try{await Promise.all([kc(!0),vc(!0),xc(!0)]);if(!await Ha())return;if(zr(!1),!on)return await Lc(),void _r("Current scene updated");await qc(),_r("Current scene updated")}finally{Xr(!1)}}),ke?.addEventListener("click",()=>wc({source:"manual"})),Re?.addEventListener("click",async()=>{if(ln)if(!Un&&await(async()=>{if(!ln)return!1;if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return zr(!0,"The Word cursor is now in a different scene. Refresh Current Scene before saving."),!1;try{const e=await window.Word.run(async e=>await Wa(e)),t=e.currentChapter?.title||"",n=e.currentScene?.title||"",r=e.currentScene?.anchorId||null,o=e.currentChapter?.anchorId||null,a=Number.isInteger(r)&&r===ln,c=Number.isInteger(o)&&o===un,i=Io(n,"scene").toLocaleLowerCase()===Io(fr||on,"scene").toLocaleLowerCase(),s=Io(t,"chapter").toLocaleLowerCase()===Io(Ir||rn,"chapter").toLocaleLowerCase();return a||i&&(c||s)?(zr(!1),!0):(zr(!0,"The Word cursor is now in a different scene. Refresh Current Scene before saving."),!1)}catch(e){return console.error("Unable to verify current Word scene before saving.",e),Eo({lastError:Do(e)}),zr(!0,"The Word cursor is now in a different scene. Refresh Current Scene before saving."),!1}})()){Re&&(Re.disabled=!0,Re.textContent="Saving...");try{await(e=`/api/word-companion/scenes/${ln}/links`,t={characterIds:rc(Te),assetIds:rc(je),locationIds:[],primaryLocationId:oc()},Qa(e,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})),(()=>{const e=new Set(rc(Te)),t=new Set(rc(je));kr={characters:kr.characters.map(t=>({...t,linked:e.has(Number.parseInt(go(t,"character"),10))})),assets:kr.assets.map(e=>({...e,linked:t.has(Number.parseInt(go(e,"asset"),10))})),locations:kr.locations,primaryLocationId:oc()}})(),oo("Scene links saved successfully."),Eo({lastError:"-"})}catch(e){oo("Unable to save scene links."),Eo({lastError:Do(e)})}finally{Re&&(Re.disabled=!ln||Un,Re.textContent="Save Scene Links")}var e,t}else oo("The Word cursor is now in a different scene. Refresh Current Scene before saving.");else oo("Link a PlotDirector scene first.")}),pe?.addEventListener("click",async()=>{await cc()}),he?.addEventListener("click",async()=>{console.debug("Word Companion unlink clicked"),$r("Preparing to unlink manuscript...");const e=(()=>{const e=Cn||Zo();if(e)return e;const t=kn?.manuscriptDocument?.documentGuid||"",n=Number.parseInt(kn?.bookId||"",10),r=Number.parseInt(kn?.projectId||"",10);return t&&Number.isInteger(n)&&n>0&&Number.isInteger(r)&&r>0?{documentGuid:t,bookId:n,projectId:r,bindingVersion:Number.parseInt(kn?.manuscriptDocument?.bindingVersion||"",10)||1}:null})();if(!e)return void $r("This Word document is not linked.");if(!e.documentGuid||!Number.isInteger(e.bookId)||e.bookId<=0)return void $r("Unable to unlink: bound document metadata is incomplete.");if(await new Promise(e=>{if(!T||"function"!=typeof T.showModal)return void e(window.confirm("This will remove PlotDirector binding metadata from this Word document.\n\nIf PlotDirector is reachable, it will also unlink this Book in PlotDirector.\n\nYour manuscript text will not be changed.\n\nContinue?"));const t=t=>{j?.removeEventListener("click",n),W?.removeEventListener("click",r),T?.removeEventListener("cancel",o),T?.close(),e(t)},n=()=>t(!0),r=()=>t(!1),o=e=>{e.preventDefault(),t(!1)};j?.addEventListener("click",n),W?.addEventListener("click",r),T?.addEventListener("cancel",o),T.showModal()})){$r("Unlinking manuscript..."),he&&(he.disabled=!0,he.textContent="Unlinking...");try{await Xa("/api/word-companion/manuscript/unlink",{documentGuid:e.documentGuid,bookId:e.bookId}),await ea(),await nc("Manuscript unlinked.")}catch(e){console.error("Unable to unlink manuscript binding.",e),$r("Unable to unlink from PlotDirector.");if(window.confirm("PlotDirector could not be updated, but you can still remove the binding from this Word document.\n\nYou may need to unlink the Book manually in PlotDirector later.\n\nRemove binding from this Word document only?"))try{await ea(),await nc("Word metadata removed. Unlink the Book manually in PlotDirector if it still shows as linked.")}catch(e){console.error("Unable to remove Word document metadata.",e),$r(`Unable to remove Word metadata: ${Do(e)}`)}else $r(`Unable to unlink manuscript: ${Do(e)}`)}finally{he&&(he.disabled=!1,he.textContent="Unlink this Word document"),Or()}}else $r("Unlink cancelled.")}),He?.addEventListener("click",async()=>{const e=Ro();if(!e||!rn||un)return void uo("Chapter not found in PlotDirector.");const t=fo(rn),n=await((e,t)=>(No(gt,`"${e}"`),No(yt,`"${t}"`),mt?new Promise(e=>{bn=e,mt.showModal?mt.showModal():mt.hidden=!1}):Promise.resolve(window.confirm(`Create PlotDirector chapter:\n\n"${e}"\n\nin book:\n\n"${t}"\n\n?`))))(t,To(e)||"selected book");if(n){He&&(He.disabled=!0,He.textContent="Creating...");try{const n=Number.isInteger(cn)&&cn>0?10*cn:null,r=await Xa(`/api/word-companion/books/${e.bookId}/chapters`,{title:t,sortOrder:n});if(!r?.chapterId)throw new Error("Chapter creation did not return a chapter ID.");ro(),Qr(null,r.chapterId,"Manual chapter link");if(!await ic("Chapter created and linked successfully."))return;await sc("Chapter created and linked successfully.")}catch(e){co("Unable to create chapter."),Eo({lastError:Do(e)})}finally{He&&(He.disabled=!io(),He.textContent="Create Chapter in PlotDirector")}}}),Ge?.addEventListener("click",async()=>{const e=Ro();if(e&&rn&&!un){Ge&&(Ge.disabled=!0,Ge.textContent="Loading...");try{fn=await Co(e.bookId),In=null,dt&&(dt.value=""),No(ht,""),lc(),st?.showModal?st.showModal():st&&(st.hidden=!1)}catch(e){co("Unable to load chapters."),Eo({lastError:Do(e)})}finally{Ge&&(Ge.disabled=!io(),Ge.textContent="Link to Existing Chapter")}}else uo("Chapter not found in PlotDirector.")}),dt?.addEventListener("input",lc),ut?.addEventListener("click",async()=>{const e=fn.find(e=>e.chapterId===In);if(e){ut&&(ut.disabled=!0,ut.textContent="Linking...");try{Qr(null,e.chapterId,"Manual chapter link");if(!await ic("Chapter linked successfully."))return;uc(),await sc("Chapter linked successfully.")}catch(e){No(ht,"Unable to link chapter."),co("Unable to link chapter."),Eo({lastError:Do(e)})}finally{ut&&(ut.disabled=!In,ut.textContent="Link Chapter")}}else No(ht,"Select a chapter.")}),pt?.addEventListener("click",uc),wt?.addEventListener("click",()=>dc(!0)),ft?.addEventListener("click",()=>dc(!1)),mt?.addEventListener("cancel",e=>{e.preventDefault(),dc(!1)}),Je?.addEventListener("click",async()=>{const e=Ro();if(!e||!on)return void ho("Scene not found in PlotDirector.");const t=Io(on,"scene"),n=Io(rn,"chapter"),r=await((e,t)=>(No(ot,`"${e}"`),No(at,`"${t}"`),rt?new Promise(e=>{wn=e,rt.showModal?rt.showModal():rt.hidden=!1}):Promise.resolve(window.confirm(`Create PlotDirector scene:\n\n"${e}"\n\nwithin chapter:\n\n"${t}"\n\n?`))))(t,n);if(r){Je&&(Je.disabled=!0,Je.textContent="Creating...");try{const n=await vo();if(!n)return void ho("This chapter does not exist in PlotDirector. Create or link the chapter first.");const r=await Xa(`/api/word-companion/books/${e.bookId}/chapters/${n.chapterId}/scenes`,{sceneTitle:t});if(!r?.sceneId||!r?.chapterId)throw new Error("Scene creation did not return a scene ID.");ro(),await pc(r.sceneId,r.chapterId,"Scene created and linked successfully.")}catch(e){ao("Unable to create scene."),Eo({lastError:Do(e)})}finally{Je&&(Je.disabled=!so(),Je.textContent="Create Scene in PlotDirector")}}}),_e?.addEventListener("click",async()=>{const e=Ro();if(e&&on){_e&&(_e.disabled=!0,_e.textContent="Loading...");try{const t=await So(e.bookId),n=ko(t);if(!n)return void ho("This chapter does not exist in PlotDirector. Create or link the chapter first.");gn=Array.isArray(n.scenes)?n.scenes:[],yn=null,Xe&&(Xe.value=""),No(nt,""),mc(),Qe?.showModal?Qe.showModal():Qe&&(Qe.hidden=!1)}catch(e){ao("Unable to load scenes."),Eo({lastError:Do(e)})}finally{_e&&(_e.disabled=!so(),_e.textContent="Link to Existing Scene")}}else ho("Scene not found in PlotDirector.")}),Xe?.addEventListener("input",mc),et?.addEventListener("click",async()=>{const e=Ro(),t=gn.find(e=>e.sceneId===yn);if(e&&t){et&&(et.disabled=!0,et.textContent="Linking...");try{const e=await vo();if(!e)throw new Error("This chapter does not exist in PlotDirector. Create or link the chapter first.");gc(),await pc(t.sceneId,e.chapterId,"Scene linked successfully.")}catch(e){No(nt,"Unable to link scene."),ao("Unable to link scene."),Eo({lastError:Do(e)})}finally{et&&(et.disabled=!yn,et.textContent="Link Scene")}}else No(nt,"Select a scene.")}),tt?.addEventListener("click",gc),ct?.addEventListener("click",()=>hc(!0)),it?.addEventListener("click",()=>hc(!1)),rt?.addEventListener("cancel",e=>{e.preventDefault(),hc(!1)}),Tt?.addEventListener("click",async()=>{Tt&&(Tt.disabled=!0,Tt.textContent="Running...");try{if(await Po(),!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return void Eo({lastScan:"Failure",lastError:"Word document access is unavailable.",paragraphs:"-",heading1:"-",heading2:"-"});const e=await window.Word.run(async e=>{const t=e.document.body.paragraphs;return t.load("items"),await e.sync(),t.items.length});Eo({lastScan:"Success",lastError:"-",paragraphs:String(e)})}catch(e){console.error("Word Companion diagnostics failed.",e);const t=Do(e);Eo({lastScan:"Failure",lastError:t}),$r(`Unable to scan the Word document: ${t}`)}finally{Tt&&(Tt.disabled=!1,Tt.textContent="Run Diagnostics")}});const Uc=Jt?(async()=>{Ao(H,"Loading projects..."),Ao(G,"Select a project first"),Ao(h,"Loading projects..."),Ao(m,"Select a project first"),qr(""),Ar("");try{const n=await Qa("/api/word-companion/projects");if(Qt=Array.isArray(n.projects)?n.projects:[],0===Qt.length)return Ao(H,"No projects available."),Ao(h,"No projects available."),qr("No projects available."),qo(e,null),qo(t,null),ra(),void zo("No projects available.");$o(H,"Choose a project",Qt,"projectId","title"),_o();const r=Lo(e),o=Qt.find(e=>e.projectId===r);o&&H?(H.value=String(o.projectId),h&&(h.value=String(o.projectId)),qo(e,o.projectId),await Rc(o.projectId,Lo(t))):(qo(e,null),qo(t,null),ra(),zo("Select a Project and Book to begin."))}catch{Qt=[],Xt=[],Ao(H,"Unable to connect to PlotDirector."),Ao(G,"Select a project first"),Ao(h,"Unable to connect to PlotDirector."),Ao(m,"Select a project first"),Lr("Unable to connect to PlotDirector."),qr("Unable to connect to PlotDirector."),qo(e,null),qo(t,null),ra(),zo("Unable to connect to PlotDirector.")}})():Promise.resolve();if(Jt||(Ao(H,"Please sign in to PlotDirector."),Ao(G,"Please sign in to PlotDirector."),Ao(h,"Please sign in to PlotDirector."),Ao(m,"Please sign in to PlotDirector."),ra()),!window.Office||"function"!=typeof window.Office.onReady)return Nr("Word Companion ready"),$r("Word document access is unavailable."),_r("Manual"),Eo({host:"Unavailable",platform:"Unavailable",ready:"No",wordApi:"No"}),void Vo().catch(e=>{console.debug("Word Companion presence unavailable.",e)});Po().then(async()=>{Nr("Word Companion ready"),await Uc,await(async()=>{if(!Jt||!en)return void Jo(!1);const n=Zo();if(n)try{const r=await Qa(`/api/word-companion/runtime/book/${n.bookId}?documentGuid=${encodeURIComponent(n.documentGuid)}`),o=r?.manuscriptDocument;if(r?.bookId===n.bookId&&r?.projectId===n.projectId&&o&&String(o.documentGuid).toLowerCase()===n.documentGuid.toLowerCase())return Cn=n,to(r),qo(e,r.projectId),qo(t,r.bookId),H&&(H.value=String(r.projectId)),await Rc(r.projectId,r.bookId),Jo(!1),Lr("Connected"),Ar("Bound manuscript detected."),void await tc()}catch{}Cn=null,no(),_o(),Uo()?await Rc(Uo().projectId,null):Ao(m,"Select a project first"),zo("Select a Project and Book to begin."),Jo(!0)})(),await Vo(),en?(()=>{if(Jt){if(!window.Office?.context?.document||"function"!=typeof window.Office.context.document.addHandlerAsync||!window.Office?.EventType?.DocumentSelectionChanged)return _r("Manual"),void $r("Automatic scene tracking is unavailable in this Word version. Use Refresh Current Scene.");try{window.Office.context.document.addHandlerAsync(window.Office.EventType.DocumentSelectionChanged,jc,e=>{e?.status===window.Office.AsyncResultStatus.Succeeded?(Kn=!0,_r("Live")):(_r("Manual"),$r("Automatic scene tracking is unavailable in this Word version. Use Refresh Current Scene."))}),window.addEventListener("beforeunload",Wc)}catch(e){console.warn("Unable to register Word selection tracking handler.",e),_r("Manual"),$r("Automatic scene tracking is unavailable in this Word version. Use Refresh Current Scene.")}}})():($r("Word document access is unavailable."),_r("Manual"))}).catch(e=>{console.error("Office readiness check failed.",e),Lr("Unable to connect to PlotDirector."),Nr("Word Companion unavailable"),$r("Word document access is unavailable."),Eo({ready:"No",wordApi:"No",lastScan:"Failure"})})})(); \ No newline at end of file +(()=>{const e="plotdirector.word.projectId",t="plotdirector.word.bookId",n="plotdirector.word.activeTab",r={documentGuid:"plotdirector.word.documentGuid",bookId:"plotdirector.word.boundBookId",projectId:"plotdirector.word.boundProjectId",bindingVersion:"plotdirector.word.bindingVersion"},o=new Set(["scene","review","maintenance","diagnostics"]),a=document.querySelector(".word-companion-shell"),c=document.querySelector(".word-companion-tabs"),i=[...document.querySelectorAll("[data-tab-button]")],s=[...document.querySelectorAll("[data-tab-panel]")],d=document.querySelector("[data-linked-manuscript-card]"),l=document.querySelector("[data-linked-project-title]"),u=document.querySelector("[data-linked-book-title]"),p=document.querySelector("[data-first-run-wizard]"),h=document.querySelector("[data-first-run-project-select]"),m=document.querySelector("[data-first-run-book-select]"),y=document.querySelector("[data-first-run-new-book-title]"),g=document.querySelector("[data-first-run-create-book]"),w=document.querySelector("[data-first-run-scan]"),f=document.querySelector("[data-first-run-cancel]"),I=document.querySelector("[data-first-run-status]"),b=document.querySelector("[data-first-run-summary]"),S=document.querySelector("[data-first-run-character-section]"),C=document.querySelector("[data-first-run-character-status]"),k=document.querySelector("[data-first-run-character-candidates]"),v=document.querySelector("[data-first-run-character-actions]"),x=document.querySelector("[data-first-run-create-characters]"),N=document.querySelector("[data-first-run-skip-characters]"),E=document.querySelector("[data-first-run-continue]"),D=document.querySelector("[data-first-run-import-actions]"),P=document.querySelector("[data-first-run-import]"),L=document.querySelector("[data-first-run-import-cancel]"),q=document.querySelector("[data-first-run-replace-dialog]"),A=document.querySelector("[data-first-run-replace-confirm]"),$=document.querySelector("[data-first-run-replace-cancel]"),T=document.querySelector("[data-unlink-word-document-dialog]"),j=document.querySelector("[data-unlink-word-document-confirm]"),W=document.querySelector("[data-unlink-word-document-cancel]"),R=document.querySelector("[data-office-status]"),U=document.querySelector("[data-connection-status]"),M=document.querySelector("[data-summary-connection]"),O=document.querySelector("[data-summary-project]"),B=document.querySelector("[data-summary-book]"),H=document.querySelector("[data-project-select]"),G=document.querySelector("[data-book-select]"),F=document.querySelector("[data-runtime-book-selectors]"),V=document.querySelector("[data-project-message]"),K=document.querySelector("[data-book-message]"),Y=document.querySelector("[data-refresh-current-scene]"),J=document.querySelector("[data-refresh-document]"),_=document.querySelector("[data-current-chapter]"),z=document.querySelector("[data-current-scene]"),Q=document.querySelector("[data-current-word-count]"),X=document.querySelector("[data-scene-tracking-status]"),Z=document.querySelector("[data-document-message]"),ee=document.querySelector("[data-sync-note]"),te=document.querySelector("[data-document-outline]"),ne=document.querySelector("[data-analyse-manuscript-structure]"),re=document.querySelector("[data-apply-structure-sync]"),oe=document.querySelector("[data-structure-preview-status]"),ae=document.querySelector("[data-structure-preview-results]"),ce=document.querySelector("[data-refresh-plotdirector-scene]"),ie=document.querySelector("[data-plotdirector-scene-status]"),se=document.querySelector("[data-anchor-status]"),de=document.querySelector("[data-anchor-book-id]"),le=document.querySelector("[data-anchor-chapter-id]"),ue=document.querySelector("[data-anchor-scene-id]"),pe=document.querySelector("[data-attach-plotdirector-ids]"),he=document.querySelector("[data-unlink-word-document]"),me=document.querySelector("[data-plotdirector-scene-details]"),ye=document.querySelector("[data-plotdirector-scene-title]"),ge=document.querySelector("[data-plotdirector-revision-status]"),we=document.querySelector("[data-plotdirector-estimated-words]"),fe=document.querySelector("[data-plotdirector-actual-words]"),Ie=document.querySelector("[data-plotdirector-blocked-status]"),be=document.querySelector("[data-plotdirector-blocked-reason-row]"),Se=document.querySelector("[data-plotdirector-blocked-reason]"),Ce=document.querySelector("[data-runtime-last-sync]"),ke=document.querySelector("[data-sync-scene-progress]"),ve=document.querySelector("[data-sync-word-count]"),xe=document.querySelector("[data-sync-plotdirector-count]"),Ne=document.querySelector("[data-sync-last-synced]"),Ee=document.querySelector("[data-sync-status]"),De=document.querySelector("[data-writing-brief-block]"),Pe=document.querySelector("[data-writing-brief]"),Le=document.querySelector("[data-plotdirector-link-groups]"),qe=document.querySelector("[data-character-review-summary]"),Ae=document.querySelector("[data-asset-review-summary]"),$e=document.querySelector("[data-location-review-summary]"),Te=document.querySelector("[data-character-chips]"),je=document.querySelector("[data-asset-chips]"),We=(document.querySelector("[data-location-chips]"),document.querySelector("[data-primary-location-select]")),Re=document.querySelector("[data-save-scene-links]"),Ue=document.querySelector("[data-scene-links-status]"),Me=document.querySelector("[data-links-editing-scene]"),Oe=document.querySelector("[data-chapter-create-link]"),Be=document.querySelector("[data-chapter-create-link-chapter]"),He=document.querySelector("[data-create-plotdirector-chapter]"),Ge=document.querySelector("[data-link-existing-chapter]"),Fe=document.querySelector("[data-chapter-create-link-status]"),Ve=document.querySelector("[data-scene-create-link]"),Ke=document.querySelector("[data-create-link-chapter]"),Ye=document.querySelector("[data-create-link-scene]"),Je=document.querySelector("[data-create-plotdirector-scene]"),_e=document.querySelector("[data-link-existing-scene]"),ze=document.querySelector("[data-create-link-status]"),Qe=document.querySelector("[data-link-existing-dialog]"),Xe=document.querySelector("[data-link-scene-search]"),Ze=document.querySelector("[data-link-scene-options]"),et=document.querySelector("[data-link-selected-scene]"),tt=document.querySelector("[data-link-dialog-cancel]"),nt=document.querySelector("[data-link-dialog-status]"),rt=document.querySelector("[data-create-scene-dialog]"),ot=document.querySelector("[data-create-dialog-scene]"),at=document.querySelector("[data-create-dialog-chapter]"),ct=document.querySelector("[data-create-dialog-confirm]"),it=document.querySelector("[data-create-dialog-cancel]"),st=document.querySelector("[data-link-existing-chapter-dialog]"),dt=document.querySelector("[data-link-chapter-search]"),lt=document.querySelector("[data-link-chapter-options]"),ut=document.querySelector("[data-link-selected-chapter]"),pt=document.querySelector("[data-link-chapter-dialog-cancel]"),ht=document.querySelector("[data-link-chapter-dialog-status]"),mt=document.querySelector("[data-create-chapter-dialog]"),yt=document.querySelector("[data-create-chapter-dialog-chapter]"),gt=document.querySelector("[data-create-chapter-dialog-book]"),wt=document.querySelector("[data-create-chapter-dialog-confirm]"),ft=document.querySelector("[data-create-chapter-dialog-cancel]"),It=document.querySelector("[data-apply-structure-sync-dialog]"),bt=document.querySelector("[data-apply-structure-sync-summary]"),St=document.querySelector("[data-apply-structure-sync-confirm]"),Ct=document.querySelector("[data-apply-structure-sync-cancel]"),kt=document.querySelector("[data-resolve-project-id]"),vt=document.querySelector("[data-resolve-project-title]"),xt=document.querySelector("[data-resolve-book-id]"),Nt=document.querySelector("[data-resolve-book-title]"),Et=document.querySelector("[data-resolve-detected-chapter]"),Dt=document.querySelector("[data-resolve-detected-scene]"),Pt=document.querySelector("[data-resolve-request-chapter]"),Lt=document.querySelector("[data-resolve-request-scene]"),qt=document.querySelector("[data-resolve-result]"),At=document.querySelector("[data-resolve-chapter-id]"),$t=document.querySelector("[data-resolve-scene-id]"),Tt=document.querySelector("[data-run-diagnostics]"),jt=document.querySelector("[data-diagnostic-host]"),Wt=document.querySelector("[data-diagnostic-platform]"),Rt=document.querySelector("[data-diagnostic-ready]"),Ut=document.querySelector("[data-diagnostic-word-api]"),Mt=document.querySelector("[data-diagnostic-last-scan]"),Ot=document.querySelector("[data-diagnostic-last-error]"),Bt=document.querySelector("[data-diagnostic-anchor-type]"),Ht=document.querySelector("[data-diagnostic-stored-scene-id]"),Gt=document.querySelector("[data-diagnostic-stored-chapter-id]"),Ft=document.querySelector("[data-diagnostic-resolution-method]"),Vt=document.querySelector("[data-diagnostic-paragraphs]"),Kt=document.querySelector("[data-diagnostic-heading1]"),Yt=document.querySelector("[data-diagnostic-heading2]"),Jt="true"===a?.dataset.authenticated,_t=Number.parseInt(a?.dataset.userId||"",10),zt=String(a?.dataset.companionVersion||"20C").trim();let Qt=[],Xt=[],Zt=!1,en=!1,tn="Unknown",nn="Unknown",rn="",on="",an=null,cn=null,sn=null,dn=null,ln=null,un=null,pn="",hn="Title Match",mn=!1,yn=[],gn=null,wn=null,fn=[],In=null,bn=null,Sn=null,Cn=null,kn=null,vn=null,xn=null,Nn=null,En=null,Dn=null,Pn=[],Ln=null,qn=!1,An=!1,$n=0,Tn=!1,jn=!1,Wn=!1,Rn=null,Un="Manual",Mn=!1,On=null,Bn=!1,Hn=0,Gn=!1,Fn=0,Vn=null;const Kn=1200;let Yn=!1,Jn=!1,_n=null,zn=!1,Qn=!1,Xn=null,Zn=null,er=null,tr=!1,nr=!1,rr=null,or="",ar=[],cr=null,ir="",sr=null,dr="",lr=[],ur=null,pr="",hr=null,mr="",yr=[],gr=null,wr="",fr="",Ir="",br="",Sr=0,Cr=null,kr=null,vr={characters:[],assets:[],locations:[],primaryLocationId:null};const xr=e=>{const t=Date.now();t-Sr<1e3||(Sr=t,console.debug(`[Word Companion Runtime] ${e}`))},Nr=e=>{console.debug(`[Word Companion Timing] ${e}`)},Er=e=>{R&&(R.textContent=e)},Dr=e=>"links"===e?"review":"structure"===e?"maintenance":e,Pr=(e,t=!0)=>{const r=Dr(e),a=o.has(r)&&i.some(e=>e.dataset.tabButton===r)?r:"scene";for(const e of i){const t=e.dataset.tabButton===a;e.classList.toggle("is-active",t),e.setAttribute("aria-selected",t?"true":"false")}for(const e of s){const t=e.dataset.tabPanel===a;e.classList.toggle("is-active",t),e.hidden=!t}t&&((e,t)=>{try{t?window.localStorage.setItem(e,String(t)):window.localStorage.removeItem(e)}catch{}})(n,a)},Lr=()=>{const e=Dr((e=>{try{return window.localStorage.getItem(e)||""}catch{return""}})(n));Pr(o.has(e)?e:"scene",!1)},qr=e=>{U&&(U.textContent=e),M&&(M.textContent=e)},Ar=e=>{V&&(V.textContent=e)},$r=e=>{K&&(K.textContent=e)},Tr=e=>{Z&&(Z.textContent=e)},jr=e=>{ie&&(ie.textContent=e)},Wr=e=>{const t=t=>Object.prototype.hasOwnProperty.call(e,t);t("anchorType")&&Eo(Bt,eo(e.anchorType)),t("storedSceneId")&&Eo(Ht,eo(e.storedSceneId)),t("storedChapterId")&&Eo(Gt,eo(e.storedChapterId)),t("resolutionMethod")&&Eo(Ft,eo(e.resolutionMethod))},Rr=()=>{const e=Uo(),t=Number.isInteger(ln)&&ln>0&&dn===ln,n=!Number.isInteger(un)||un<=0||sn===un,r=t&&n;Eo(se,r?"Attached to PlotDirector":"Not attached"),Eo(de,e?.bookId?String(e.bookId):"-"),Eo(le,Number.isInteger(un)&&un>0?String(un):"-"),Eo(ue,Number.isInteger(ln)&&ln>0?String(ln):"-"),pe&&(Or(pe,r&&!mn),pe.disabled=!ln||r&&!mn,pe.textContent=dn&&!r?"Reattach PlotDirector IDs":"Attach PlotDirector IDs"),Wr({anchorType:hn||"Title Match",storedSceneId:dn,storedChapterId:sn,resolutionMethod:pn})},Ur=(e,t,n,r=null,o=null,a=null)=>{const c=e||"",i=t||"",s=Number.isInteger(n)?n:null,d=Number.isInteger(a)&&a>0?a:null,l=Number.isInteger(r)&&r>0?r:null,u=Number.isInteger(o)&&o>0?o:null,p=rn===c&&on===i&&an===s&&cn===d&&sn===l&&dn===u;rn=e||"",on=t||"",an=Number.isInteger(n)?n:null,cn=Number.isInteger(a)&&a>0?a:null,sn=Number.isInteger(r)&&r>0?r:null,dn=Number.isInteger(o)&&o>0?o:null,p||(_&&(_.textContent=e||"Not detected"),z&&(z.textContent=t||"Not detected"),Q&&(Q.textContent=Number.isInteger(n)?String(n):"-"),Eo(ve,Number.isInteger(n)?String(n):"-"),Rr())},Mr=()=>{_n&&(window.clearTimeout(_n),_n=null)},Or=(e,t)=>{e&&(e.hidden=t)},Br=()=>{Or(he,!Cn)},Hr=e=>{Eo(Ee,e)},Gr=e=>{Eo(oe,e)},Fr=(e="Select a project and book to analyse manuscript structure.")=>{if(Sn=null,ae){ae.innerHTML="";const e=document.createElement("p");e.textContent="No preview generated.",ae.append(e)}Gr(e),Kr()},Vr=(e=Sn)=>((e=Sn)=>Ea.flatMap(t=>Array.isArray(e?.[t.key])?e[t.key]:[]))(e).filter(e=>"couldLink"===e.category||"wouldCreateChapters"===e.category||"wouldCreateScenes"===e.category),Kr=()=>{re&&(re.disabled=Wn||0===Vr().length)},Yr=()=>{ne&&(ne.disabled=Wn||!Ro()||!Uo()),Kr()},Jr=()=>{Me&&(Me.textContent=ln?`Editing links for: ${Ir||on||"Current scene"}`:"Link a PlotDirector scene first.")},_r=()=>{const e=Mn||!ln,t=e||!un||zn;ke&&(ke.disabled=t),Re&&(Re.disabled=e),We&&(We.disabled=e);for(const t of[Te,je])for(const n of t?[...t.querySelectorAll("input[type='checkbox']")]:[])n.disabled=e},zr=e=>{"Live"!==e&&"Manual"!==e||(Un=e),Eo(X,e||Un||"Manual")},Qr=(e,t="")=>{Mn=!!e,Mn&&(Mr(),Sc()),Eo(X,Mn?"Stale":Un),_r(),t&&(Tr(t),ao(t))},Xr=(e,t=null,n="")=>{ln=Number.isInteger(e)&&e>0?e:null,un=Number.isInteger(t)&&t>0?t:null,pn=n||"",ln!==Xn&&(Zn=null),ln||(Mr(),Sc()),_r(),Hr(Mn?"Refresh Current Scene before syncing.":ln?"Ready to sync.":"No resolved PlotDirector scene."),ao(Mn?"Refresh Current Scene before saving.":ln?"Ready to save.":"Link a PlotDirector scene first."),Jr(),Rr()},Zr=e=>{Y&&(Y.disabled=e,Y.textContent=e?"Refreshing...":"Refresh Current Scene")},eo=e=>null==e||""===e?"-":String(e),to=e=>{if(!e)return"-";const t=e instanceof Date?e:new Date(e);if(Number.isNaN(t.getTime()))return"-";const n=new Date;return`Last synced: ${t.toDateString()===n.toDateString()?"Today":t.toLocaleDateString()} ${t.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}`},no=e=>{kn=e||null;const t=kn?.lastSyncUtc||kn?.manuscriptDocument?.lastSyncUtc;Eo(Ce,to(t)),Eo(Ne,to(t)),Wo()},ro=()=>{vn=null,xn=null,Nn=null,Fn+=1,Vn=null,Cc(),kc(),vc(),no(null)},oo=()=>{xn=null,Nn=null},ao=e=>{Eo(Ue,e)},co=e=>{Eo(ze,e)},io=e=>{Eo(Fe,e)},so=()=>!!Uo()&&!!rn&&!un,lo=()=>!!Uo()&&!!on&&Number.isInteger(un)&&un>0,uo=()=>{Or(Oe,!0),io(""),He&&(He.disabled=!0,He.textContent="Create Chapter in PlotDirector"),Ge&&(Ge.disabled=!0,Ge.textContent="Link to Existing Chapter")},po=(e="")=>{Eo(Be,eo(rn)),Or(Oe,!1),io(e);const t=so();He&&(He.disabled=!t,He.textContent="Create Chapter in PlotDirector"),Ge&&(Ge.disabled=!t,Ge.textContent="Link to Existing Chapter")},ho=()=>{Or(Ve,!0),co(""),Je&&(Je.disabled=!0,Je.textContent="Create Scene in PlotDirector"),_e&&(_e.disabled=!0,_e.textContent="Link to Existing Scene")},mo=(e="")=>{Eo(Ke,eo(rn)),Eo(Ye,eo(on)),Or(Ve,!1),co(e);const t=lo();Je&&(Je.disabled=!t,Je.textContent="Create Scene in PlotDirector"),_e&&(_e.disabled=!t,_e.textContent="Link to Existing Scene")},yo=e=>{jr(e),Or(me,!0),Or(De,!0),Or(Le,!1),uo(),ho(),pn="",Ir="",br="",hn=dn?"SceneID Anchor":sn?"ChapterID Anchor":"Title Match",mn=!1,vr={characters:[],assets:[],locations:[],primaryLocationId:null},wo(Te,[],"character"),wo(je,[],"asset"),fo([],null,!1),aa(),Xr(null),Eo(xe,"-"),Eo(ye,"-"),Eo(ge,"-"),Eo(we,"-"),Eo(fe,"-"),Eo(Ie,"-"),Eo(Se,"-"),Or(be,!0),Rr(),Jr()},go=(e,t)=>"asset"===t?e?.assetId||e?.AssetId||e?.storyAssetId||e?.StoryAssetId||0:"location"===t?e?.locationId||e?.LocationId||0:e?.characterId||e?.CharacterId||0,wo=(e,t,n,r=!1)=>{if(!e)return;e.innerHTML="";const o=Array.isArray(t)?t:[];if(0===o.length){const t=document.createElement("p");return t.className="word-companion-empty-list",t.textContent="None",void e.append(t)}for(const t of o){const o=Number.parseInt(go(t,n),10);if(!Number.isInteger(o)||o<=0)continue;const a=document.createElement("label");a.className="word-companion-check-item";const c=document.createElement("input");c.type="checkbox",c.value=String(o),c.checked=!!t.linked,c.disabled=!r;const i=document.createElement("span");i.textContent=t.name||"Unnamed",a.append(c,i),e.append(a)}},fo=(e,t,n=!1)=>{if(!We)return;We.innerHTML="";const r=document.createElement("option");r.value="",r.textContent="None",We.append(r);const o=Number.parseInt(t||"",10);for(const t of Array.isArray(e)?e:[]){const e=Number.parseInt(go(t,"location"),10);if(!Number.isInteger(e)||e<=0)continue;const n=document.createElement("option");n.value=String(e),n.textContent=t.name||"Unnamed",n.selected=e===o,We.append(n)}We.disabled=!n},Io=e=>String(e||"").trim().replace(/\s+/g," "),bo=(e,t)=>{const n=Io(e);if(!n)return"";const r=new RegExp(`^${"scene"===t?"scene":"chapter"}\\s+(?:\\d+|[ivxlcdm]+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)\\s*(?::|-|\\u2013|\\u2014)\\s*`,"i");return Io(n.replace(r,""))||n},So=e=>{const t=t=>Object.prototype.hasOwnProperty.call(e,t);t("projectId")&&Eo(kt,eo(e.projectId)),t("projectTitle")&&Eo(vt,eo(e.projectTitle)),t("bookId")&&Eo(xt,eo(e.bookId)),t("bookTitle")&&Eo(Nt,eo(e.bookTitle)),t("detectedChapter")&&Eo(Et,eo(e.detectedChapter)),t("detectedScene")&&Eo(Dt,eo(e.detectedScene)),t("requestChapter")&&Eo(Pt,eo(e.requestChapter)),t("requestScene")&&Eo(Lt,eo(e.requestScene)),t("result")&&Eo(qt,eo(e.result)),t("chapterId")&&Eo(At,eo(e.chapterId)),t("sceneId")&&Eo($t,eo(e.sceneId))},Co=async e=>{if(Nn===e&&Array.isArray(xn?.chapters))return xn.chapters;const t=await Za(`/api/word-companion/books/${e}/structure`);return Nn=e,xn=t||null,Array.isArray(t?.chapters)?t.chapters:[]},ko=async e=>{const t=await Za(`/api/word-companion/books/${e}/chapters`);return Array.isArray(t?.chapters)?t.chapters:[]},vo=e=>{if(Number.isInteger(un)&&un>0){const t=e.find(e=>e.chapterId===un);if(t)return t}const t=bo(rn,"chapter").toLocaleLowerCase();return t&&e.find(e=>bo(e.title,"chapter").toLocaleLowerCase()===t)||null},xo=async()=>{const e=Uo();return e?vo(await Co(e.bookId)):null},No=async(e,t,n,r,o)=>{const a=await Za(`/api/word-companion/scenes/${t}/companion`);try{a.revisionStatus=await(async(e,t)=>{const n=await Za(`/api/word-companion/books/${e}/structure`),r=Array.isArray(n?.chapters)?n.chapters:[];for(const e of r){const n=(Array.isArray(e.scenes)?e.scenes:[]).find(e=>e.sceneId===t);if(n)return n.revisionStatus||""}return""})(e,t)}catch{a.revisionStatus=""}var c;hn=o||"Title Match",mn=!1,Qr(!1),jr("Linked to PlotDirector"),Tr(""),uo(),ho(),Xr(t,n,r),c=a,Ir=c?.sceneTitle||on||"",br=c?.chapterTitle||rn||"",Eo(ye,eo(c?.sceneTitle)),Eo(ge,eo(c?.revisionStatus||c?.revisionStatusName||"Not provided")),Eo(we,eo(c?.estimatedWords)),Eo(fe,eo(c?.actualWords)),Eo(xe,eo(c?.actualWords)),Eo(Ie,c?.blocked?"Blocked":"Not blocked"),c?.blockedReason?(Eo(Se,c.blockedReason),Or(be,!1)):(Eo(Se,"-"),Or(be,!0)),Pe&&(Pe.textContent=c?.writingBrief||"No writing brief has been added for this scene."),vr={characters:Array.isArray(c?.characters)?c.characters:[],assets:Array.isArray(c?.assets)?c.assets:[],locations:Array.isArray(c?.locations)?c.locations:[],primaryLocationId:c?.primaryLocationId??null},wo(Te,vr.characters,"character",!!ln&&!Mn),wo(je,vr.assets,"asset",!!ln&&!Mn),fo(vr.locations,vr.primaryLocationId,!!ln&&!Mn),aa(),Jr(),ao(Mn?"Refresh Current Scene before saving.":ln?"Ready to save.":"No resolved PlotDirector scene."),Or(me,!1),Or(De,!1),Or(Le,!1),nc(t,n),bc(),qc()},Eo=(e,t)=>{if(e){const n=null==t?"":String(t);e.textContent!==n&&(e.textContent=n)}},Do=e=>{const t=t=>Object.prototype.hasOwnProperty.call(e,t);t("host")&&Eo(jt,e.host),t("platform")&&Eo(Wt,e.platform),t("ready")&&Eo(Rt,e.ready),t("wordApi")&&Eo(Ut,e.wordApi),t("lastScan")&&Eo(Mt,e.lastScan),t("lastError")&&Eo(Ot,e.lastError),t("paragraphs")&&Eo(Vt,e.paragraphs),t("heading1")&&Eo(Kt,e.heading1),t("heading2")&&Eo(Yt,e.heading2)},Po=e=>{const t=[];return e?.name&&t.push(e.name),e?.code&&e.code!==e.name&&t.push(e.code),e?.message&&t.push(e.message),e?.debugInfo?.errorLocation&&t.push(`Location: ${e.debugInfo.errorLocation}`),t.length>0?t.join(": "):String(e)},Lo=async()=>{if(!window.Office||"function"!=typeof window.Office.onReady)return Zt=!1,en=!1,tn="Unavailable",nn="Unavailable",Do({host:tn,platform:nn,ready:"No",wordApi:"No"}),null;const e=await window.Office.onReady();return Zt=!0,tn=e?.host||"Unknown",nn=e?.platform||"Unknown",en=!!window.Word&&!!window.Office.HostType&&e?.host===window.Office.HostType.Word,Do({host:tn,platform:nn,ready:"Yes",wordApi:en?"Yes":"No"}),e},qo=e=>{try{const t=window.localStorage.getItem(e),n=Number.parseInt(t||"",10);return Number.isInteger(n)&&n>0?n:null}catch{return null}},Ao=(e,t)=>{try{t?window.localStorage.setItem(e,String(t)):window.localStorage.removeItem(e)}catch{}},$o=(e,t)=>{if(!e)return;e.innerHTML="";const n=document.createElement("option");n.value="",n.textContent=t,e.append(n),e.disabled=!0},To=(e,t,n,r,o)=>{if(!e)return;e.innerHTML="";const a=document.createElement("option");a.value="",a.textContent=t,e.append(a);for(const t of n){const n=document.createElement("option");n.value=String(t[r]),n.textContent=t[o],e.append(n)}e.disabled=0===n.length},jo=e=>{const t=String(e?.displayTitle||"").trim();if(t)return t;const n=String(e?.title||"").trim(),r=String(e?.subtitle||"").trim();return r?`${n}: ${r}`:n},Wo=()=>{const e=!!Cn;Or(d,!e),Or(F,e),e&&(Eo(l,kn?.projectTitle||Ro()?.title||"Project"),Eo(u,kn?.bookTitle||jo(Uo())||"Book"))},Ro=()=>{const e=Number.parseInt(H?.value||"",10);return Qt.find(t=>t.projectId===e)||null},Uo=()=>{const e=Number.parseInt(G?.value||"",10);return Xt.find(t=>t.bookId===e)||null},Mo=()=>{const e=Number.parseInt(h?.value||"",10);return Qt.find(t=>t.projectId===e)||null},Oo=()=>{const e=Number.parseInt(m?.value||"",10);return Xt.find(t=>t.bookId===e)||null},Bo=()=>{const t=Cn?.projectId||Mo()?.projectId||Ro()?.projectId||qo(e);return Number.isInteger(t)&&t>0?t:null},Ho=()=>{const e=Cn?.bookId||Oo()?.bookId||Uo()?.bookId||qo(t);return Number.isInteger(e)&&e>0?e:null},Go=()=>{const e=String(window.Office?.context?.document?.url||"").trim();if(!e)return"";const t=e.split(/[\\/]/).filter(Boolean).pop()||"";try{return decodeURIComponent(t)}catch{return t}},Fo=()=>({userID:Number.isInteger(_t)&&_t>0?_t:null,companionVersion:zt,machineName:"",documentOpen:!!en,currentDocumentName:Go(),linkedProjectID:Bo(),linkedBookID:Ho()}),Vo=()=>{(async()=>{Cr&&Cr.state===signalR.HubConnectionState.Connected&&await Cr.invoke("CompanionHeartbeat",Fo())})().catch(e=>{console.debug("Word Companion heartbeat unavailable.",e)})},Ko=async()=>{Jt&&window.signalR&&!Cr&&(Cr=(new signalR.HubConnectionBuilder).withUrl("/hubs/word-companion-follow").withAutomaticReconnect().build(),Cr.onreconnected(Vo),Cr.on("ScanCurrentDocument",e=>{Sa(e)}),Cr.on("UpdateOnboardingBuildMarkers",e=>{Ca(e)}),Cr.onclose(()=>{kr&&(window.clearInterval(kr),kr=null)}),await Cr.start(),await Cr.invoke("RegisterCompanion",Fo()),kr=window.setInterval(Vo,25e3))};window.addEventListener("beforeunload",()=>{kr&&(window.clearInterval(kr),kr=null),Cr&&Cr.stop().catch(()=>{})});const Yo=e=>Eo(I,e),Jo=()=>{const e=Mo(),t=Oo(),n=An&&Date.now()-$n>=750;w&&(w.disabled=!e||!t||Tn),g&&(g.disabled=!e||!String(y?.value||"").trim()||Tn),P&&(P.disabled=!En?.canImport||!Dn||Tn),x&&(x.disabled=!n||jn||0===Xo().length)},_o=e=>{Or(p,!e),Or(c,e),Br(),Wo();for(const t of s)Or(t,!!e||"scene"!==t.dataset.tabPanel&&!t.classList.contains("is-active"));e||Lr()},zo=()=>{if(!h)return;if(0===Qt.length)return void $o(h,"No projects available.");To(h,"Choose a project",Qt,"projectId","title");const e=Number.parseInt(H?.value||"",10);Number.isInteger(e)&&e>0&&(h.value=String(e))},Qo=(e="Select a Project and Book to begin.")=>{En=null,Dn=null,Pn=[],qn=!1,An=!1,$n=0,Or(b,!0),Or(D,!0),Or(S,!0),Or(v,!0),b&&(b.innerHTML=""),k&&(k.innerHTML=""),Eo(C,""),Yo(e),Jo()},Xo=()=>k?[...k.querySelectorAll("input[type='checkbox']:checked")].map(e=>String(e.value||"").trim()).filter(Boolean):[],Zo=e=>{if(Pn=Array.isArray(e)?e:[],S&&k){if(k.innerHTML="",Or(S,!1),Or(v,!qn||0===Pn.length),Or(x,!1),Or(N,!1),Or(E,!0),0===Pn.length)return Eo(C,"No likely major characters found."),void Jo();Eo(C,`${Pn.length} likely major characters found.`);for(const e of Pn){const t=document.createElement("label");t.className="word-companion-character-candidate";const n=document.createElement("input");n.type="checkbox",n.value=e.text||"",n.checked="high"===String(e.confidence||"").trim().toLowerCase(),n.addEventListener("change",Jo);const r=document.createElement("strong");r.textContent=e.text||"";const o=document.createElement("em"),a=String(e.reason||"").trim();o.textContent=a?`${Number(e.mentionCount||0).toLocaleString()} mentions · ${a}`:`${Number(e.mentionCount||0).toLocaleString()} mentions`;const c=document.createElement("span");c.className="word-companion-character-candidate-text",c.append(r,o),t.append(n,c),k.append(t)}Jo()}},ea=()=>{const e=window.Office?.context?.document?.settings;if(!e)return null;const t=String(e.get(r.documentGuid)||"").trim(),n=Number.parseInt(e.get(r.bookId)||"",10),o=Number.parseInt(e.get(r.projectId)||"",10),a=Number.parseInt(e.get(r.bindingVersion)||"",10);return!t||!Number.isInteger(n)||n<=0||!Number.isInteger(o)||o<=0?null:{documentGuid:t,bookId:n,projectId:o,bindingVersion:Number.isInteger(a)&&a>0?a:1}},ta=()=>new Promise((e,t)=>{const n=window.Office?.context?.document?.settings;if(n){for(const e of Object.values(r))"function"==typeof n.remove?n.remove(e):n.set(e,"");n.saveAsync(n=>{n.status===window.Office.AsyncResultStatus.Succeeded?e():t(n.error||new Error("Unable to remove Word document metadata."))})}else t(new Error("Word document settings are unavailable."))}),na=()=>Cn?.documentGuid?Cn.documentGuid:window.crypto?.randomUUID?window.crypto.randomUUID():"10000000-1000-4000-8000-100000000000".replace(/[018]/g,e=>(Number(e)^16*Math.random()>>Number(e)/4).toString(16)),ra=()=>Cn?.documentGuid||ea()?.documentGuid||"",oa=()=>{const e=Ro(),t=Uo();O&&(O.textContent=e?.title||"(Not connected)"),B&&(B.textContent=t?jo(t):"(Not connected)"),ee&&(ee.textContent=t?"":"Select a PlotDirector book before refreshing."),Yr(),Wo()},aa=()=>{const e=Array.isArray(vr.characters)?vr.characters.length:0,t=Array.isArray(vr.assets)?vr.assets.length:0,n=Array.isArray(vr.locations)?vr.locations.length:0;Eo(qe,`Characters (${e})`),Eo(Ae,`Assets (${t})`),Eo($e,`Locations (${n})`)},ca=e=>String(e?.styleBuiltIn||e?.style||"").replace(/\s+/g,"").toLowerCase(),ia=(e,t)=>{const n=ca(e);return n===`heading${t}`||n.endsWith(`.heading${t}`)||n.includes(`heading${t}`)},sa=e=>String(e||"").trim().split(/\s+/).filter(Boolean).length,da=(e,t)=>{const n=String(e||"").match(new RegExp(`^${t}-(\\d+)$`,"i"));if(!n)return null;const r=Number.parseInt(n[1],10);return Number.isInteger(r)&&r>0?r:null},la=e=>{try{return Array.isArray(e?.contentControls?.items)?e.contentControls.items:[]}catch(e){return console.warn("Word paragraph content controls were not available.",e),[]}},ua=(e,t)=>{const n=la(e);for(const e of n){const n=da(e.tag,t);if(n)return n}return null},pa=(e,t)=>{const n=[];let r=0,o=0,a=null,c=null;e.forEach((e,t)=>{const i=String(e.text||"").trim();if(i){if(ia(e,1))return r+=1,a={index:n.length+1,paragraphIndex:t,title:i,anchorId:ua(e,"PD-CHAPTER"),scenes:[]},n.push(a),void(c=null);if(ia(e,2)){if(o+=1,!a)return;return c={index:a.scenes.length+1,paragraphIndex:t,title:i,anchorId:ua(e,"PD-SCENE"),wordCount:0},void a.scenes.push(c)}c&&(c.wordCount+=sa(i))}});const i=t.find(e=>String(e.text||"").trim()),s=i?e.findIndex(e=>{return t=e,n=i,String(t?.text||"").trim()===String(n?.text||"").trim()&&ca(t)===ca(n);var t,n}):-1,d=s>=0&&n.filter(e=>e.paragraphIndex<=s).at(-1)||null,l=d&&s>=0&&d.scenes.filter(e=>e.paragraphIndex<=s).at(-1)||null;return{chapters:n,currentChapter:d,currentScene:l,paragraphCount:e.length,heading1Count:r,heading2Count:o}},ha=new Set(["***","###"]),ma=(e,t)=>{const n=String(e?.text||"").trim();return{index:t,text:n,styleBuiltIn:e?.styleBuiltIn||"",style:e?.style||"",wordCount:sa(n),chapterAnchorId:null,sceneAnchorId:null}},ya=(e,t)=>{e&&(e.endParagraphIndex=t)},ga=(e,t)=>{e&&(e.endParagraphIndex=t,ya(e.scenes.at(-1),t))},wa=(e,t,n)=>e?.[t]??e?.[n]??null,fa=(e,t,n)=>e?.[t]??e?.[n]??null,Ia=async(e,t,n,r={})=>{Cr&&Cr.state===signalR.HubConnectionState.Connected&&await Cr.invoke("ReportOnboardingScanProgress",{userID:fa(e,"userID","UserID"),onboardingID:fa(e,"onboardingID","OnboardingID"),message:t,percentComplete:n,chapterCount:r.chapterCount||0,sceneCount:r.sceneCount||0,characterCandidateCount:r.characterCandidateCount||0,totalWordCount:r.totalWordCount||0})},ba=async(e,t)=>{Cr&&Cr.state===signalR.HubConnectionState.Connected&&await Cr.invoke("FailOnboardingScan",{userID:fa(e,"userID","UserID"),onboardingID:fa(e,"onboardingID","OnboardingID"),message:t})},Sa=async e=>{var t;if(en&&window.Word)try{await Ia(e,"Preparing document scan",5);const n=((e,t)=>{const n=[],r=[],o=[];let a=null,c=null,i=0,s=0;const d=()=>{c=null},l=(e,t,r=!1)=>(d(),a={temporaryChapterKey:`chapter-${n.length+1}`,chapterNumber:n.length+1,title:t,wordCount:0,startPosition:e?.index??null,existingChapterID:r?null:ua(e,"PD-CHAPTER")},n.push(a),u(e,null),a),u=(e,t=null)=>a?(d(),c={temporarySceneKey:`scene-${r.length+1}`,temporaryChapterKey:a.temporaryChapterKey,sceneNumberWithinChapter:r.filter(e=>e.temporaryChapterKey===a.temporaryChapterKey).length+1,title:t,wordCount:0,openingTextPreview:"",startPosition:e?.index??null,existingSceneID:e?ua(e,"PD-SCENE"):null},r.push(c),c):null,p=(e,t)=>{!a||!c||t<=0||(a.wordCount+=t,c.wordCount+=t,c.openingTextPreview||!e||ha.has(e)||(c.openingTextPreview=e.length>180?`${e.slice(0,177)}...`:e))};if(e.forEach((e,t)=>{e.index=t;const n=String(e.text||"").trim();if(!n)return;o.push(n);const r=sa(n);return ia(e,1)?(s+=1,l(e,n),i+=r,void(a.wordCount+=r)):(a||l(e,"Opening pages",!0),ia(e,2)?(u(e,n),i+=r,void p(n,r)):void(ha.has(n)?u(e,null):(i+=r,p(n,r))))}),0===i)throw new Error("The document appears to be empty. Add manuscript text, then try again.");if(0===s)throw new Error("No chapters were detected. Make sure your chapter headings use Heading 1, or add clear chapter headings.");const h=o.join("\n");return{previewID:"00000000-0000-0000-0000-000000000000",userID:t.userID||t.UserID||0,onboardingID:t.onboardingID||t.OnboardingID||0,projectID:t.projectID||t.ProjectID||0,bookID:t.bookID||t.BookID||0,source:"WordCompanion",documentTitle:Go()||"Word manuscript",companionDocumentIdentifier:ra(),totalWordCount:i,chapterCount:n.length,sceneCount:r.length,characterCandidateCount:0,createdUtc:(new Date).toISOString(),chapters:n,scenes:r,characterCandidates:[],documentTextForDiscovery:h}})(await window.Word.run(async t=>{const n=t.document.body.paragraphs;n.load("items/text,items/style,items/styleBuiltIn"),await t.sync(),await Ia(e,"Detecting chapters",20);for(const e of n.items)(ia(e,1)||ia(e,2))&&e.contentControls.load("items/tag,title");return await t.sync(),n.items}),e);await Ia(e,"Detecting scene breaks",50,n),await Ia(e,`Scene ${n.sceneCount} of ${n.sceneCount} scanned`,70,n),await Ia(e,"Finding character candidates",85,n);const r=n.documentTextForDiscovery||"";delete n.documentTextForDiscovery;try{const e=await ec("/api/word-companion/manuscript/discover-characters",{projectId:n.projectID,documentText:r,includeExcluded:!0});n.characterCandidates=(t=e?.candidates,(Array.isArray(t)?t:[]).map((e,t)=>({temporaryCharacterKey:`character-${t+1}`,name:wa(e,"text","Text")||wa(e,"name","Name")||"",mentionCount:Number.parseInt(wa(e,"mentionCount","MentionCount")||"0",10)||0,qualityScore:Number.parseInt(wa(e,"qualityScore","QualityScore")||"0",10)||0,category:wa(e,"category","Category")||"PossibleCharacter",reason:wa(e,"reason","Reason")||"",isExistingCharacterMatch:!!wa(e,"isExistingCharacterMatch","IsExistingCharacterMatch"),suggestedImportance:null})).filter(e=>e.name)),n.characterCandidateCount=n.characterCandidates.filter(e=>"excluded"!==String(e.category||"").toLowerCase()).length}catch(e){console.warn("Unable to refine onboarding character candidates.",e),n.characterCandidates=[],n.characterCandidateCount=0}await Ia(e,"Preparing preview",95,n),Ln=n,await Cr.invoke("CompleteOnboardingScan",n)}catch(t){console.error("Unable to complete onboarding scan.",t),await ba(e,ka(t))}else await ba(e,"Open your manuscript in Microsoft Word, then try the scan again.")},Ca=async e=>{if(!Ln||!en||!window.Word||"function"!=typeof window.Word.run)throw new Error("Word document access is unavailable.");const t=new Map((e?.chapterMappings||e?.ChapterMappings||[]).map(e=>[e.temporaryChapterKey||e.TemporaryChapterKey,e.chapterID||e.ChapterID])),n=new Map((e?.sceneMappings||e?.SceneMappings||[]).map(e=>[e.temporarySceneKey||e.TemporarySceneKey,e.sceneID||e.SceneID]));await window.Word.run(async e=>{const r=e.document.body.paragraphs;r.load("items/text,items/style,items/styleBuiltIn"),await e.sync();for(const e of Ln.chapters||[]){const n=t.get(e.temporaryChapterKey),o=Number.parseInt(e.startPosition,10);n&&Number.isInteger(o)&&r.items[o]&&ic(r.items[o],"PlotDirector Chapter",`PD-CHAPTER-${n}`,"PD-CHAPTER")}for(const e of Ln.scenes||[]){const t=n.get(e.temporarySceneKey),o=Number.parseInt(e.startPosition,10);t&&Number.isInteger(o)&&r.items[o]&&ic(r.items[o],"PlotDirector Scene",`PD-SCENE-${t}`,"PD-SCENE")}await e.sync()})},ka=e=>{const t=Po(e);return/No chapters were detected/i.test(t)?"No chapters were detected. Make sure your chapter headings use Heading 1, or add clear chapter headings.":/empty/i.test(t)?"The document appears to be empty. Add manuscript text, then try again.":/network|disconnect|connection/i.test(t)?"The Word Companion disconnected before the scan finished. Reopen Word and try again.":`The scan could not finish: ${t}`},va=(e,t)=>{const n=(t||[]).find(e=>String(e.text||"").trim());if(!e||!n)return e?.currentParagraphIndex??-1;const r=e.paragraphs.filter(e=>((e,t)=>String(e?.text||"").trim()===String(t?.text||"").trim()&&ca(e)===ca(t))(e,n)).map(e=>e.index);return 0===r.length?e.currentParagraphIndex??-1:Number.isInteger(e.currentParagraphIndex)?r.reduce((t,n)=>Math.abs(n-e.currentParagraphIndex){if(!vn)return!1;const t=((e,t)=>!e||!Number.isInteger(t)||t<0?null:e.chapters.filter(e=>e.startParagraphIndex<=t).at(-1)||null)(vn,e),n=((e,t)=>!e||!Number.isInteger(t)||t<0?null:e.scenes.filter(e=>e.startParagraphIndex<=t).at(-1)||null)(t,e),r=t?.startParagraphIndex!==vn.currentChapter?.startParagraphIndex||n?.startParagraphIndex!==vn.currentScene?.startParagraphIndex||t?.anchorId!==vn.currentChapter?.anchorId||n?.anchorId!==vn.currentScene?.anchorId;return vn.currentParagraphIndex=e,vn.currentChapter=t,vn.currentScene=n,!!r&&(Ur(t?.title,n?.title,n?.wordCount,t?.anchorId,n?.anchorId,t?.index),r)},Na=e=>{if(!te)return;if(te.innerHTML="",0===e.chapters.length){const e=document.createElement("p");return e.textContent="No chapters detected. Use Heading 1 for chapter titles.",void te.append(e)}if(!e.chapters.some(e=>e.scenes.length>0)){const e=document.createElement("p");e.textContent="No scenes detected. Use Heading 2 for scene titles.",te.append(e)}for(const t of e.chapters){const e=document.createElement("div");e.className="word-companion-outline-chapter";const n=document.createElement("strong");if(n.textContent=t.title,e.append(n),t.scenes.length>0){const n=document.createElement("ul");for(const e of t.scenes){const t=document.createElement("li");t.textContent=e.title,n.append(t)}e.append(n)}te.append(e)}},Ea=[{key:"alreadyLinked",title:"Already Linked"},{key:"couldLink",title:"Could Link"},{key:"wouldCreateChapters",title:"Would Create Chapters"},{key:"wouldCreateScenes",title:"Would Create Scenes"},{key:"manualReview",title:"Manual Review Required"}],Da={alreadyLinked:"Already Linked",couldLink:"Could Link",wouldCreateChapter:"Would Create Chapter",wouldCreateScene:"Would Create Scene",manualReview:"Manual Review Required"},Pa={alreadyLinked:"✓",couldLink:"~",wouldCreateChapter:"+",wouldCreateScene:"+",manualReview:"?"},La=(e,t)=>bo(e,t).toLocaleLowerCase(),qa=({category:e,action:t,type:n,wordTitle:r,match:o="",anchorStatus:a="None",matches:c=[],pdChapter:i=null,pdScene:s=null,wordChapter:d=null,wordScene:l=null,parentItem:u=null})=>({id:`${n}-${d?.index||0}-${l?.index||0}-${d?.paragraphIndex??l?.paragraphIndex??0}`,category:e,action:t,type:n,wordTitle:r,match:o,anchorStatus:a,matches:c,pdChapter:i,pdScene:s,wordChapter:d,wordScene:l,parentItem:u,result:"Pending",error:""}),Aa=(e,t)=>{Array.isArray(e[t.category])&&e[t.category].push(t)},$a=e=>Array.isArray(e?.scenes)?e.scenes:[],Ta=(e,t,n)=>{const r=e.anchorId?`PD-CHAPTER-${e.anchorId}`:"None";if(e.anchorId){const o=t.find(t=>t.chapterId===e.anchorId);if(o){const t=qa({category:"alreadyLinked",action:"alreadyLinked",type:"Chapter",wordTitle:e.title,match:`Chapter ${o.chapterId}: ${o.title}`,anchorStatus:r,pdChapter:o,wordChapter:e});return Aa(n,t),t}const a=qa({category:"manualReview",action:"manualReview",type:"Chapter",wordTitle:e.title,anchorStatus:`${r} not found`,matches:[],pdChapter:null,wordChapter:e});return Aa(n,a),a}const o=t.filter(t=>La(t.title,"chapter")===La(e.title,"chapter"));if(1===o.length){const t=qa({category:"couldLink",action:"couldLink",type:"Chapter",wordTitle:e.title,match:`Chapter ${o[0].chapterId}: ${o[0].title}`,anchorStatus:r,pdChapter:o[0],wordChapter:e});return Aa(n,t),t}if(o.length>1){const t=qa({category:"manualReview",action:"manualReview",type:"Chapter",wordTitle:e.title,anchorStatus:r,matches:o.map(e=>`Chapter ${e.chapterId}: ${e.title}`),pdChapter:null,wordChapter:e});return Aa(n,t),t}const a=qa({category:"wouldCreateChapters",action:"wouldCreateChapter",type:"Chapter",wordTitle:e.title,anchorStatus:r,pdChapter:null,wordChapter:e});return Aa(n,a),a},ja=(e,t,n,r)=>{const o=e.anchorId?`PD-SCENE-${e.anchorId}`:"None";if(e.anchorId){const a=((e,t)=>{for(const n of e){const e=$a(n).find(e=>e.sceneId===t);if(e)return{chapter:n,scene:e}}return null})(n,e.anchorId);return a?void Aa(r,qa({category:"alreadyLinked",action:"alreadyLinked",type:"Scene",wordTitle:e.title,match:`Scene ${a.scene.sceneId}: ${a.scene.title}`,anchorStatus:o,pdChapter:a.chapter,pdScene:a.scene,wordChapter:t?.wordChapter,wordScene:e,parentItem:t})):void Aa(r,qa({category:"manualReview",action:"manualReview",type:"Scene",wordTitle:e.title,anchorStatus:`${o} not found`,wordChapter:t?.wordChapter,wordScene:e,parentItem:t}))}if(!t?.pdChapter&&"manualReview"===t?.category)return void Aa(r,qa({category:"manualReview",action:"manualReview",type:"Scene",wordTitle:e.title,anchorStatus:o,matches:["Parent chapter requires manual review."],wordChapter:t?.wordChapter,wordScene:e,parentItem:t}));if(!t?.pdChapter)return void Aa(r,qa({category:"wouldCreateScenes",action:"wouldCreateScene",type:"Scene",wordTitle:e.title,anchorStatus:o,wordChapter:t?.wordChapter,wordScene:e,parentItem:t}));const a=$a(t.pdChapter).filter(t=>La(t.title,"scene")===La(e.title,"scene"));1!==a.length?a.length>1?Aa(r,qa({category:"manualReview",action:"manualReview",type:"Scene",wordTitle:e.title,anchorStatus:o,matches:a.map(e=>`Scene ${e.sceneId}: ${e.title}`),wordChapter:t.wordChapter,wordScene:e,parentItem:t})):Aa(r,qa({category:"wouldCreateScenes",action:"wouldCreateScene",type:"Scene",wordTitle:e.title,anchorStatus:o,pdChapter:t.pdChapter,wordChapter:t.wordChapter,wordScene:e,parentItem:t})):Aa(r,qa({category:"couldLink",action:"couldLink",type:"Scene",wordTitle:e.title,match:`Scene ${a[0].sceneId}: ${a[0].title}`,anchorStatus:o,pdChapter:t.pdChapter,pdScene:a[0],wordChapter:t.wordChapter,wordScene:e,parentItem:t}))},Wa=e=>{const t=document.createElement("article");t.className="word-companion-preview-item";const n=document.createElement("strong");n.textContent=`${Pa[e.action]||""} ${e.type}: ${e.wordTitle}`,t.append(n);const r=document.createElement("span");if(r.textContent=`Anchor: ${e.anchorStatus||"None"}`,t.append(r),e.match){const n=document.createElement("span");n.textContent=`Match: ${e.match}`,t.append(n)}if(Array.isArray(e.matches)&&e.matches.length>0){const n=document.createElement("div");n.className="word-companion-preview-matches";const r=document.createElement("span");r.textContent="Matches:",n.append(r);const o=document.createElement("ul");for(const t of e.matches){const e=document.createElement("li");e.textContent=t,o.append(e)}n.append(o),t.append(n)}const o=document.createElement("span");if(o.textContent=`Action: ${Da[e.action]||e.action}`,t.append(o),e.result&&"Pending"!==e.result){const n=document.createElement("span");n.textContent=e.error?`Result: ${e.result} - ${e.error}`:`Result: ${e.result}`,t.append(n)}return t},Ra=e=>{if(ae){ae.innerHTML="";for(const t of Ea){const n=document.createElement("section");n.className="word-companion-preview-group";const r=document.createElement("strong");r.textContent=t.title,n.append(r);const o=Array.isArray(e?.[t.key])?e[t.key]:[];if(0===o.length){const e=document.createElement("p");e.className="word-companion-empty-list",e.textContent="None",n.append(e)}else for(const e of o)n.append(Wa(e));ae.append(n)}Kr()}},Ua=async e=>{const t=e.document.body.paragraphs,n=e.document.getSelection().paragraphs;t.load("text,styleBuiltIn,style"),n.load("text,styleBuiltIn,style"),await e.sync();try{for(const e of t.items)(ia(e,1)||ia(e,2))&&e.contentControls.load("items/tag,title");await e.sync()}catch(e){console.warn("Unable to load Word heading content controls. Continuing without PlotDirector anchors.",e)}const r=pa(t.items,n.items);return r.bodyParagraphs=t.items,r},Ma=async(e,t)=>{const n=e.document.body.paragraphs,r=e.document.getSelection().paragraphs;n.load("text,styleBuiltIn,style"),r.load("text,styleBuiltIn,style"),await e.sync();const o=((e,t)=>{const n=e.map(ma),r=[];let o=0,a=0,c=null,i=null,s=0;const d=(e,t=null,n=null)=>c?(ya(i,e.index-1),i={index:c.scenes.length+1,paragraphIndex:e.index,startParagraphIndex:e.index,endParagraphIndex:e.index,title:t||`Scene ${c.scenes.length+1}`,anchorId:n,wordCount:0},c.scenes.push(i),i):null;for(const e of n)e.text&&(ia(e,1)?(ga(c,e.index-1),o+=1,c={index:r.length+1,paragraphIndex:e.index,startParagraphIndex:e.index,endParagraphIndex:e.index,title:e.text,anchorId:e.chapterAnchorId,wordCount:0,scenes:[]},r.push(c),i=null,d(e,"Scene 1",e.sceneAnchorId),s+=e.wordCount):c&&(t&&ha.has(e.text)?(a+=1,d(e)):(c.wordCount+=e.wordCount,i&&(i.wordCount+=e.wordCount),s+=e.wordCount)));return ga(c,n.length-1),{paragraphs:n,chapters:r,usesExplicitScenes:!!t,paragraphCount:n.length,heading1Count:o,heading2Count:a,documentWordCount:s,currentParagraphIndex:null,currentChapter:null,currentScene:null}})(n.items,t),a=va(o,r.items);return vn=o,xa(a),xr("Cache rebuilt."),o},Oa=()=>window.performance&&"function"==typeof window.performance.now?window.performance.now():Date.now(),Ba=e=>!!e&&!e.stale&&e.generation===Fn&&e.documentGuid===ra()&&e.sceneId===ln,Ha=async(e={})=>{const t=!!e.includeSelection,n=!1!==e.updateDisplay,r=Fn,o=Oa(),a=Ro(),c=Uo(),i=vn?.currentScene;if(!i||!Number.isInteger(i.startParagraphIndex)||!Number.isInteger(i.endParagraphIndex))return null;if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return null;const s=Oa(),d=await window.Word.run(async e=>{const n=e.document.body.paragraphs;n.load("text");let r=null;return t&&(r=e.document.getSelection().paragraphs,r.load("text,styleBuiltIn,style")),await e.sync(),{bodyTexts:n.items.map(e=>String(e.text||"")),selectionParagraphs:t&&r?r.items.map(e=>({text:String(e.text||""),styleBuiltIn:e.styleBuiltIn,style:e.style})):[]}}),l=Math.round(Oa()-s);if(r!==Fn)return Nr(`Office snapshot: ${l}ms stale`),{stale:!0,generation:r};let u=!1;if(t){const e=va(vn,d.selectionParagraphs);u=xa(e)}const p=vn?.currentScene;if(!p||!Number.isInteger(p.startParagraphIndex)||!Number.isInteger(p.endParagraphIndex))return null;const h=[];let m=0;const y=Math.min(p.endParagraphIndex,d.bodyTexts.length-1),g=vn.currentChapter?.startParagraphIndex;for(let e=p.startParagraphIndex;e<=y;e+=1){const t=String(d.bodyTexts[e]||"").trim();t&&!ha.has(t)&&e!==g&&(h.push(t),m+=sa(t))}p.wordCount=m,n&&Ur(vn.currentChapter?.title,p.title,m,vn.currentChapter?.anchorId,p.anchorId,vn.currentChapter?.index);const w={documentGuid:ra(),projectId:a?.projectId||null,bookId:c?.bookId||null,chapterId:un,sceneId:ln,chapterTitle:vn.currentChapter?.title||"",sceneTitle:p.title||"",sceneText:h.join("\n"),wordCount:m,selectionSignature:`${vn.currentParagraphIndex??-1}:${p.startParagraphIndex}:${p.endParagraphIndex}`,selectionChanged:u,generation:r};return Vn=w,Nr(`Office snapshot: ${l}ms; scene words: ${m}; total snapshot: ${Math.round(Oa()-o)}ms`),w},Ga=async()=>Ba(Vn)?Vn:await Ha(),Fa=async()=>{if(Tr(""),!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return Ur(null,null,null),Tr("Word document access is unavailable."),Do({lastScan:"Failure",lastError:"Word document access is unavailable."}),!1;J&&(J.disabled=!0,J.textContent="Scanning..."),Tr("Scanning document structure...");try{const e=await window.Word.run(async e=>{const t=Uo();return t?await Ma(e,!!t.usesExplicitScenes):(vn=null,await Ua(e))});return vn||Ur(e.currentChapter?.title,e.currentScene?.title,e.currentScene?.wordCount,e.currentChapter?.anchorId,e.currentScene?.anchorId,e.currentChapter?.index),yo("Not detected"),Na(e),Tr(""),Do({lastScan:"Success",lastError:"-",paragraphs:String(e.paragraphCount),heading1:String(e.heading1Count),heading2:String(e.heading2Count)}),!0}catch(e){const t=Po(e);return console.error("Unable to scan the Word document.",e),Ur(null,null,null),Tr(`Unable to scan the Word document: ${t}`),Do({lastScan:"Failure",lastError:t}),!1}finally{J&&(J.disabled=!1,J.textContent="Refresh Document Structure")}},Va=async()=>{const e=Uo();if(!Ro()||!e)return Gr("Select a project and book to analyse manuscript structure."),!1;if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return Gr("Word document access is unavailable."),Do({lastScan:"Failure",lastError:"Word document access is unavailable."}),!1;ne&&(ne.disabled=!0,ne.textContent="Analysing..."),Gr("Analysing manuscript structure...");try{const t=await window.Word.run(async e=>await Ua(e)),n=await Za(`/api/word-companion/books/${e.bookId}/structure`);return Ur(t.currentChapter?.title,t.currentScene?.title,t.currentScene?.wordCount,t.currentChapter?.anchorId,t.currentScene?.anchorId,t.currentChapter?.index),Na(t),Sn=((e,t)=>{const n={alreadyLinked:[],couldLink:[],wouldCreateChapters:[],wouldCreateScenes:[],manualReview:[]},r=Array.isArray(t?.chapters)?t.chapters:[];for(const t of Array.isArray(e?.chapters)?e.chapters:[]){const e=Ta(t,r,n);for(const o of $a(t))ja(o,e,r,n)}return n})(t,n),Ra(Sn),Gr("Preview generated. No changes have been applied."),Do({lastScan:"Success",lastError:"-",paragraphs:String(t.paragraphCount),heading1:String(t.heading1Count),heading2:String(t.heading2Count)}),!0}catch(e){return console.error("Unable to analyse manuscript structure.",e),Gr("Unable to analyse manuscript structure."),Do({lastScan:"Failure",lastError:Po(e)}),!1}finally{ne&&(ne.textContent="Analyse Manuscript Structure",Yr())}},Ka=e=>{Rn&&(Rn(e),Rn=null),It?.close?It.close():It&&(It.hidden=!0)},Ya=()=>{const e=((e=Sn)=>{const t=Vr(e);return{linkChapters:t.filter(e=>"couldLink"===e.category&&"Chapter"===e.type).length,linkScenes:t.filter(e=>"couldLink"===e.category&&"Scene"===e.type).length,createChapters:t.filter(e=>"wouldCreateChapters"===e.category).length,createScenes:t.filter(e=>"wouldCreateScenes"===e.category).length,manualReview:Array.isArray(e?.manualReview)?e.manualReview.length:0}})();return(e=>{if(!bt)return;bt.innerHTML="";const t=[`Link ${e.linkChapters+e.linkScenes} chapters/scenes`,`Create ${e.createChapters} chapters`,`Create ${e.createScenes} scenes`],n=document.createElement("ul");for(const e of t){const t=document.createElement("li");t.textContent=e,n.append(t)}bt.append(n)})(e),It?new Promise(e=>{Rn=e,It.showModal?It.showModal():It.hidden=!1}):Promise.resolve(window.confirm(`Apply Structure Sync?\n\nThis will:\n\n- Link ${e.linkChapters+e.linkScenes} chapters/scenes\n- Create ${e.createChapters} chapters\n- Create ${e.createScenes} scenes\n\nManual review items will be skipped.`))},Ja=(e,t,n="")=>{e.result=t,e.error=n},_a=e=>Number.isInteger(e?.wordChapter?.index)&&e.wordChapter.index>0?10*e.wordChapter.index:null,za=e=>Number.isInteger(e?.wordScene?.index)&&e.wordScene.index>0?10*e.wordScene.index:null,Qa=async(e,t)=>{const n=await ec(`/api/word-companion/books/${e}/chapters`,{title:Io(t.wordTitle),sortOrder:_a(t)});if(!n?.chapterId)throw new Error("Chapter creation did not return a chapter ID.");oo(),t.pdChapter={chapterId:n.chapterId,title:n.title||t.wordTitle,sortOrder:n.sortOrder||_a(t)||0,scenes:[]},t.match=`Chapter ${t.pdChapter.chapterId}: ${t.pdChapter.title}`},Xa=async(e,t)=>{const n=t.pdChapter||t.parentItem?.pdChapter;if(!n?.chapterId)throw new Error("Scene parent chapter is not available.");const r=await ec(`/api/word-companion/books/${e}/chapters/${n.chapterId}/scenes`,{sceneTitle:Io(t.wordTitle),sortOrder:za(t)});if(!r?.sceneId)throw new Error("Scene creation did not return a scene ID.");oo(),t.pdChapter=n,t.pdScene={sceneId:r.sceneId,title:t.wordTitle,sortOrder:za(t)||0},t.match=`Scene ${t.pdScene.sceneId}: ${t.pdScene.title}`},Za=async(e,t={})=>{const n=await window.fetch(e,{credentials:"same-origin",...t,headers:{Accept:"application/json",...t.headers||{}}});if(!n.ok)throw new Error(`Request failed: ${n.status}`);return await n.json()},ec=(e,t)=>Za(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}),tc=async(e="")=>{An=!1,$n=0,Pn=[],k&&(k.innerHTML=""),Or(S,!0),_o(!1),Yo(""),Or(v,!0),e&&Tr(e);const t=await rc();e&&t&&rn&&Tr(e)},nc=async(e=ln,t=un)=>{const n=Ro(),r=Uo(),o=ra(),a=Number.parseInt(e||"",10),c=Number.parseInt(t||"",10);if(!n||!r||!o||!Number.isInteger(a)||a<=0||!Number.isInteger(c)||c<=0)return;const i=`${n.projectId}:${r.bookId}:${a}`;if(fr!==i){fr=i;try{await ec("/api/word-companion/runtime/current-scene",{documentGuid:o,projectId:n.projectId,bookId:r.bookId,chapterId:c,sceneId:a}),xr("Current scene follow event sent.")}catch(e){fr="",console.debug("Unable to notify PlotDirector of current scene.",e)}}},rc=async()=>{const e=Uo();if(!(e&&Zt&&en&&window.Word&&"function"==typeof window.Word.run))return!1;Tr("Reading manuscript position...");try{oo(),await Promise.all([xc(!0),Nc(!0),Ec(!0)]),await Co(e.bookId);const t=await window.Word.run(async t=>await Ma(t,!!e.usesExplicitScenes));return Na(t),Do({lastScan:"Success",lastError:"-",paragraphs:String(t.paragraphCount),heading1:String(t.heading1Count),heading2:String(t.heading2Count)}),rn?(on?await $c():await Ac(),Qr(!1),zr("Live"),Tr(""),!0):(yo("Not detected"),Qr(!1),zr("Live"),Tr("Manuscript linked. Click inside a chapter to show the current scene."),!0)}catch(e){return console.error("Unable to initialise bound manuscript runtime.",e),vn=null,zr("Manual"),Tr(`Unable to read manuscript position: ${Po(e)}`),Do({lastScan:"Failure",lastError:Po(e)}),!1}},oc=async n=>{Cn=null,Wc(),Mr(),ro(),yo("Not detected"),zr("Manual"),qr("Connected"),Ao(e,null),Ao(t,null),H&&(H.value=""),$o(G,"Select a project first"),zo(),h&&(h.value=""),$o(m,"Select a project first"),Qo(n||"Select a Project and Book to begin."),Tr(n||"Manuscript unlinked."),Hr("No resolved PlotDirector scene."),_o(!0),Br()},ac=e=>e?[...e.querySelectorAll("input[type='checkbox']:checked")].map(e=>Number.parseInt(e.value||"",10)).filter(e=>Number.isInteger(e)&&e>0):[],cc=()=>{const e=Number.parseInt(We?.value||"",10);return Number.isInteger(e)&&e>0?e:null},ic=(e,t,n,r)=>{const o=((e,t)=>la(e).find(e=>da(e.tag,t))||null)(e,r),a=o||e.getRange().insertContentControl();return a.title=t,a.tag=n,a.appearance="Hidden",a},sc=async(e="PlotDirector IDs attached successfully.")=>{if(!ln||!un)return Tr("No resolved PlotDirector scene."),!1;if((dn&&dn!==ln||sn&&sn!==un)&&!window.confirm("This scene is already linked. Replace the existing PlotDirector link?"))return!1;if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return Tr("Word document access is unavailable."),!1;pe&&(pe.disabled=!0,pe.textContent="Attaching...");try{return await window.Word.run(async e=>{const t=await Ua(e);if(!t.currentChapter||!t.currentScene)throw new Error("No scene detected in Word. Use Heading 2 for scenes.");const n=t.bodyParagraphs[t.currentChapter.paragraphIndex],r=t.bodyParagraphs[t.currentScene.paragraphIndex];if(!n||!r)throw new Error("Unable to locate the current Word headings.");ic(n,"PlotDirector Chapter",`PD-CHAPTER-${un}`,"PD-CHAPTER"),ic(r,"PlotDirector Scene",`PD-SCENE-${ln}`,"PD-SCENE"),await e.sync()}),sn=un,dn=ln,mn=!1,hn="SceneID Anchor",pn="Anchor",Tr(e),Do({lastError:"-"}),Rr(),!0}catch(e){return console.error("Unable to attach PlotDirector IDs.",e),Tr("Unable to attach PlotDirector IDs."),Do({lastError:Po(e)}),Rr(),!1}finally{pe&&(pe.textContent=dn===ln?"Attach PlotDirector IDs":"Reattach PlotDirector IDs",Rr())}},dc=async e=>{if(!un)return io("No resolved PlotDirector chapter."),!1;if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return io("Word document access is unavailable."),!1;try{return await window.Word.run(async e=>{const t=await Ua(e);if(!t.currentChapter)throw new Error("No chapter detected in Word. Use Heading 1 for chapters.");const n=t.bodyParagraphs[t.currentChapter.paragraphIndex];if(!n)throw new Error("Unable to locate the current Word chapter heading.");ic(n,"PlotDirector Chapter",`PD-CHAPTER-${un}`,"PD-CHAPTER"),await e.sync()}),sn=un,mn=!1,hn="ChapterID Anchor",pn="Chapter anchor",io(e),Tr(e),Do({lastError:"-"}),Rr(),!0}catch(e){return console.error("Unable to attach PlotDirector chapter ID.",e),io("Unable to attach PlotDirector chapter ID."),Do({lastError:Po(e)}),Rr(),!1}},lc=async e=>{uo(),await $c(),Tr(e),io(e)},uc=e=>{bn&&(bn(e),bn=null),mt?.close?mt.close():mt&&(mt.hidden=!0)},pc=()=>{if(!lt)return;const e=Io(dt?.value||"").toLocaleLowerCase(),t=fn.filter(t=>!e||String(t.title||"").toLocaleLowerCase().includes(e));if(lt.innerHTML="",In=null,ut&&(ut.disabled=!0),0===t.length){const e=document.createElement("p");return e.className="word-companion-empty-list",e.textContent="No chapters found.",void lt.append(e)}for(const e of t){const t=Number.parseInt(e.chapterId||"",10);if(!Number.isInteger(t)||t<=0)continue;const n=document.createElement("label");n.className="word-companion-scene-option";const r=document.createElement("input");r.type="radio",r.name="word-companion-link-chapter",r.value=String(t),r.addEventListener("change",()=>{In=t,ut&&(ut.disabled=!1)});const o=document.createElement("span");o.textContent=e.title||"Untitled chapter",n.append(r,o),lt.append(n)}},hc=()=>{st?.close?st.close():st&&(st.hidden=!0)},mc=async(e,t,n)=>{const r=Uo();if(!r)return co("Select a PlotDirector book first."),!1;await No(r.bookId,e,t,"Manual link","Title Match");return!!await sc(n)&&(co(n),ho(),!0)},yc=e=>{wn&&(wn(e),wn=null),rt?.close?rt.close():rt&&(rt.hidden=!0)},gc=()=>{if(!Ze)return;const e=Io(Xe?.value||"").toLocaleLowerCase(),t=yn.filter(t=>!e||String(t.title||"").toLocaleLowerCase().includes(e));if(Ze.innerHTML="",gn=null,et&&(et.disabled=!0),0===t.length){const e=document.createElement("p");return e.className="word-companion-empty-list",e.textContent="No scenes found.",void Ze.append(e)}for(const e of t){const t=Number.parseInt(e.sceneId||"",10);if(!Number.isInteger(t)||t<=0)continue;const n=document.createElement("label");n.className="word-companion-scene-option";const r=document.createElement("input");r.type="radio",r.name="word-companion-link-scene",r.value=String(t),r.addEventListener("change",()=>{gn=t,et&&(et.disabled=!1)});const o=document.createElement("span");o.textContent=e.title||"Untitled scene",n.append(r,o),Ze.append(n)}},wc=()=>{Qe?.close?Qe.close():Qe&&(Qe.hidden=!0)},fc=()=>{zn=!1,_r(),Qn&&(Qn=!1,bc(1e3))},Ic=async(e={})=>{const t=e.source||"manual",n=Oa();if(!ln)return void Hr("No resolved PlotDirector scene.");if(!un)return void Hr("No resolved PlotDirector chapter.");if(Mn)return void Hr("Refresh Current Scene before syncing.");const r=Uo(),o=ra();if(!r||!o)return void Hr("Bound manuscript metadata is unavailable.");if(zn)return Qn=!0,void xr("Scene sync deferred; sync already running.");Mr(),zn=!0,Qn=!1;let a=e.snapshot||null;try{a=Ba(a)?a:await Ga()}catch(e){return Hr("Sync failed"),Do({lastError:Po(e)}),xr("Scene sync failed."),void fc()}if(!Ba(a))return Hr("Waiting for typing to pause"),xr("Scene sync skipped (stale snapshot)."),void fc();const c=a.wordCount;if(!Number.isInteger(c)||c<0)return Hr("Sync failed"),xr("Scene sync failed."),void fc();if(Xn===a.sceneId&&Zn===c)return Hr("Synced"),xr("Sync skipped (count unchanged)."),void fc();xr("Scene word count changed."),ke&&(ke.disabled=!0,ke.textContent="Syncing..."),Hr("Syncing..."),xr(`Scene sync started (${t}).`);try{const e=Oa(),t=await ec("/api/word-companion/runtime/scene-word-count",{documentGuid:a.documentGuid||o,bookId:a.bookId||r.bookId,chapterId:a.chapterId,sceneId:a.sceneId,wordCount:c});if(Nr(`Word count sync API: ${Math.round(Oa()-e)}ms`),!Ba(a))return void xr("Scene sync result ignored (stale snapshot).");const i=t?.actualWords??c,s=new Date;Xn=a.sceneId,Zn=i,Eo(xe,eo(i)),Eo(fe,eo(i)),Eo(Ne,to(s)),Eo(Ce,to(s)),Hr("Synced"),xr("Scene sync succeeded."),Nr(`Total word count sync: ${Math.round(Oa()-n)}ms`)}catch(e){Hr("Sync failed"),Do({lastError:Po(e)}),xr("Scene sync failed.")}finally{ke&&(ke.textContent="Sync Current Scene"),fc()}},bc=(e=4e3)=>{Mr(),ln&&un&&!Mn&&!zn&&(_n=window.setTimeout(()=>{_n=null,Ic({source:"automatic"})},e))},Sc=()=>{er&&(window.clearTimeout(er),er=null)},Cc=()=>{rr=null,or="",ar=[],cr=null,ir="",Sc()},kc=()=>{sr=null,dr="",lr=[],ur=null,pr=""},vc=()=>{hr=null,mr="",yr=[],gr=null,wr=""},xc=async(e=!1)=>{const t=Ro(),n=ra();if(!t||!n)return ar=[],[];if(!e&&rr===t.projectId&&or===n&&ar.length>0)return ar;const r=await Za(`/api/word-companion/runtime/characters?projectId=${encodeURIComponent(t.projectId)}&documentGuid=${encodeURIComponent(n)}`);return rr=t.projectId,or=n,ar=Array.isArray(r?.characters)?r.characters:[],xr("Character alias cache refreshed."),ar},Nc=async(e=!1)=>{const t=Ro(),n=ra();if(!t||!n)return lr=[],[];if(!e&&sr===t.projectId&&dr===n&&lr.length>0)return lr;const r=await Za(`/api/word-companion/runtime/assets?projectId=${encodeURIComponent(t.projectId)}&documentGuid=${encodeURIComponent(n)}`);return sr=t.projectId,dr=n,lr=Array.isArray(r?.assets)?r.assets:[],xr(`Asset cache loaded: ${lr.length} aliases.`),lr},Ec=async(e=!1)=>{const t=Ro(),n=ra();if(!t||!n)return yr=[],[];if(!e&&hr===t.projectId&&mr===n&&yr.length>0)return yr;const r=await Za(`/api/word-companion/runtime/locations?projectId=${encodeURIComponent(t.projectId)}&documentGuid=${encodeURIComponent(n)}`);return hr=t.projectId,mr=n,yr=Array.isArray(r?.locations)?r.locations:[],xr(`Location cache loaded: ${yr.length} aliases.`),yr},Dc=(e,t)=>{const n=String(t||"").trim();if(!n)return!1;const r=(o=n,String(o||"").replace(/[.*+?^${}()|[\]\\]/g,"\\$&")).replace(/\s+/g,"\\s+");var o;return new RegExp(`(^|[^\\p{L}\\p{N}_])${r}(?=$|[^\\p{L}\\p{N}_])`,"iu").test(e)},Pc=e=>{if(e?.excludeFromCompanionDetection)return!1;const t=Number.parseInt(e?.detectionPriority||"0",10);return n=e?.matchText,String(n||"").trim().split(/\s+/).filter(Boolean).length>=2||Number.isInteger(t)&&t>=75;var n},Lc=async()=>{if(er=null,!ln||Mn||!ra())return;if(tr)return nr=!0,void xr("Character suggestion detection deferred; detection already running.");tr=!0,nr=!1;const e=Oa();try{const t=await Ga();if(!Ba(t))return void xr("Runtime suggestions skipped (stale snapshot).");const[n,r,o]=await Promise.all([xc(),Nc(),Ec()]);if(!Ba(t))return void xr("Runtime suggestions skipped after cache load (stale snapshot).");const a=t.sceneText||"",c=Oa(),i=((e,t)=>{if(!e||!Array.isArray(t)||0===t.length)return[];const n=new Map;for(const r of t){const t=Number.parseInt(r.characterId||"",10);!Number.isInteger(t)||t<=0||n.has(t)||Dc(e,r.matchText)&&n.set(t,r.name||r.matchText||"Character")}return[...n.entries()].map(([e,t])=>({characterId:e,name:t}))})(a,n);Nr(`Character detection: ${Math.round(Oa()-c)}ms`);const s=i.map(e=>e.characterId),d=s.slice().sort((e,t)=>e-t).join(","),l=Oa(),u=((e,t)=>{if(!e||!Array.isArray(t)||0===t.length)return[];const n=new Map;for(const r of t){const t=Number.parseInt(r.assetId||"",10);!Number.isInteger(t)||t<=0||n.has(t)||Dc(e,r.matchText)&&n.set(t,r.name||r.matchText||"Asset")}return[...n.entries()].map(([e,t])=>({assetId:e,name:t}))})(a,r);Nr(`Asset detection: ${Math.round(Oa()-l)}ms`);const p=u.map(e=>e.assetId),h=p.slice().sort((e,t)=>e-t).join(","),m=Oa(),y=((e,t)=>{if(!e||!Array.isArray(t)||0===t.length)return[];const n=new Map;for(const r of t){const t=Number.parseInt(r.locationId||"",10);!Number.isInteger(t)||t<=0||n.has(t)||!Pc(r)||Dc(e,r.matchText)&&n.set(t,r.name||r.matchText||"Location")}return[...n.entries()].map(([e,t])=>({locationId:e,name:t}))})(a,o);Nr(`Location detection: ${Math.round(Oa()-m)}ms`);const g=y.map(e=>e.locationId),w=g.slice().sort((e,t)=>e-t).join(",");if(!d||cr===ln&&ir===d)xr("Character suggestions skipped.");else{if(!Ba(t))return void xr("Character suggestions skipped before submit (stale snapshot).");const e=Oa(),n=await ec("/api/word-companion/runtime/character-suggestions",{documentGuid:t.documentGuid,sceneId:t.sceneId,characterIds:s});if(Nr(`Character suggestion API: ${Math.round(Oa()-e)}ms`),!Ba(t))return void xr("Character suggestion result ignored (stale snapshot).");if(cr=t.sceneId,ir=d,(n?.createdCount||0)>0){const e=i.slice(0,3).map(e=>e.name).join(", "),t=i.length>3?" +"+(i.length-3):"";Tr(e?`Characters detected: ${e}${t}`:n.message)}xr(n?.message||"Character suggestions submitted.")}if(!h||ur===ln&&pr===h)xr("Asset suggestions skipped.");else{if(!Ba(t))return void xr("Asset suggestions skipped before submit (stale snapshot).");xr(`Asset matches found: ${u.map(e=>e.name).join(", ")}`);const e=Oa(),n=await ec("/api/word-companion/runtime/asset-suggestions",{documentGuid:t.documentGuid,sceneId:t.sceneId,assetIds:p});if(Nr(`Asset suggestion API: ${Math.round(Oa()-e)}ms`),!Ba(t))return void xr("Asset suggestion result ignored (stale snapshot).");if(ur=t.sceneId,pr=h,(n?.createdCount||0)>0){const e=u.slice(0,3).map(e=>e.name).join(", "),t=u.length>3?" +"+(u.length-3):"";Tr(e?`Assets detected: ${e}${t}`:n.message)}xr(n?.message||"Asset suggestions submitted.")}if(!w||gr===ln&&wr===w)xr("Location suggestions skipped.");else{if(!Ba(t))return void xr("Location suggestions skipped before submit (stale snapshot).");xr(`Location matches found: ${y.map(e=>e.name).join(", ")}`);const e=Oa(),n=await ec("/api/word-companion/runtime/location-suggestions",{documentGuid:t.documentGuid,sceneId:t.sceneId,locationIds:g});if(Nr(`Location suggestion API: ${Math.round(Oa()-e)}ms`),!Ba(t))return void xr("Location suggestion result ignored (stale snapshot).");if(gr=t.sceneId,wr=w,(n?.createdCount||0)>0){const e=y.slice(0,3).map(e=>e.name).join(", "),t=y.length>3?" +"+(y.length-3):"";Tr(e?`Locations detected: ${e}${t}`:n.message)}xr(n?.message||"Location suggestions submitted.")}Nr(`Total suggestion pass: ${Math.round(Oa()-e)}ms`)}catch(e){console.error("Unable to detect runtime suggestions.",e),Do({lastError:Po(e)})}finally{tr=!1,nr&&(nr=!1,qc(1500))}},qc=(e=2500)=>{Sc(),!ln||Mn||tr?tr&&(nr=!0):er=window.setTimeout(()=>{Lc()},e)},Ac=async()=>{const e=Ro(),t=Uo(),n=bo(rn,"chapter");if(So({projectId:e?.projectId,projectTitle:e?.title,bookId:t?.bookId,bookTitle:t?jo(t):void 0,detectedChapter:rn,detectedScene:on,requestChapter:n,requestScene:"",result:"Not run",chapterId:null,sceneId:null}),!t)return yo("Not detected"),Tr("Select a PlotDirector book first."),!1;if(!rn)return yo("Not detected"),Tr("No chapter detected in Word. Use Heading 1 for chapters."),!1;yo("Not detected");try{const e=await ko(t.bookId),r=sn?e.find(e=>e.chapterId===sn):null,o=n.toLocaleLowerCase(),a=o?e.find(e=>bo(e.title,"chapter").toLocaleLowerCase()===o):null,c=r||a;return c?(uo(),Xr(null,c.chapterId,r?"Chapter anchor":"Chapter title"),hn=r?"ChapterID Anchor":"Title Match",jr("Chapter linked to PlotDirector"),Tr("No scene detected in Word. Use Heading 2 for scenes."),So({result:"Chapter matched",chapterId:c.chapterId,sceneId:null}),!0):(Xr(null),jr("Chapter not found in PlotDirector."),Tr("Chapter not found in PlotDirector."),So({result:"Chapter not matched",chapterId:null,sceneId:null}),po("Chapter not found in PlotDirector."),!1)}catch(e){return yo("Not found in PlotDirector"),Do({lastError:Po(e)}),Tr("Unable to load PlotDirector chapter data."),!1}},$c=async()=>{const e=Ro(),t=Uo(),n=bo(rn,"chapter"),r=bo(on,"scene");if(So({projectId:e?.projectId,projectTitle:e?.title,bookId:t?.bookId,bookTitle:t?jo(t):void 0,detectedChapter:rn,detectedScene:on,requestChapter:n,requestScene:r,result:"Not run",chapterId:null,sceneId:null}),Wr({anchorType:dn?"SceneID Anchor":sn?"ChapterID Anchor":"Title Match",storedSceneId:dn,storedChapterId:sn,resolutionMethod:"-"}),!t)return yo("Not detected"),Tr("Select a PlotDirector book first."),So({result:"Error"}),!1;if(!rn||!on)return yo("Not detected"),Tr("No scene detected in Word. Use Heading 2 for scenes."),So({result:"Error"}),!1;ce&&(ce.disabled=!0,ce.textContent="Refreshing...");const o=()=>{ce&&(ce.disabled=!1,ce.textContent="Refresh PlotDirector Scene")};yo("Not detected"),Tr("Resolving PlotDirector scene...");try{if(dn){const e=await(async(e,t)=>{const n=await Za(`/api/word-companion/books/${e}/structure`),r=Array.isArray(n?.chapters)?n.chapters:[];for(const e of r){const n=Array.isArray(e.scenes)?e.scenes:[];for(const r of n)if(t(e,r))return{chapter:e,scene:r}}return null})(t.bookId,(e,t)=>t.sceneId===dn);if(e)return So({result:"Matched",chapterId:e.chapter.chapterId,sceneId:e.scene.sceneId}),await No(t.bookId,e.scene.sceneId,e.chapter.chapterId,"Anchor","SceneID Anchor"),!0;mn=!0,hn="SceneID Anchor",jr("Not found in PlotDirector"),Tr("Stored PlotDirector ID is invalid."),So({result:"Error",chapterId:sn,sceneId:dn}),Wr({anchorType:"SceneID Anchor",storedSceneId:dn,storedChapterId:sn,resolutionMethod:"Anchor"})}if(sn){const e=(await Co(t.bookId)).find(e=>e.chapterId===sn),n=e?(Array.isArray(e.scenes)?e.scenes:[]).find(e=>bo(e.title,"scene").toLocaleLowerCase()===r.toLocaleLowerCase()):null;if(n)return So({result:"Matched",chapterId:e.chapterId,sceneId:n.sceneId}),await No(t.bookId,n.sceneId,e.chapterId,"Anchor","ChapterID Anchor"),!0;e&&!dn?(hn="ChapterID Anchor",Xr(null,sn,"Chapter anchor"),So({result:"Chapter matched",chapterId:sn,sceneId:null})):dn||(hn="ChapterID Anchor",So({result:"Chapter anchor not found",chapterId:sn,sceneId:null}))}const e=await ec(`/api/word-companion/books/${t.bookId}/resolve-scene`,{chapterTitle:n,sceneTitle:r});if(!e?.matched||!e.sceneId){const t=mn,n=Number.parseInt(e?.chapterId||"",10),r=Number.isInteger(un)&&un>0?un:null,a=Number.isInteger(n)&&n>0?n:r,c=!!e?.chapterMatched||!!a;return yo("Not found in PlotDirector"),mn=t,c&&(Xr(null,a,a===r?"Chapter anchor":"Chapter title"),jr("Scene not found in PlotDirector.")),Tr(t?"Stored PlotDirector ID is invalid.":c?"Scene not found in PlotDirector.":"This chapter does not exist in PlotDirector. Create or link the chapter first."),So({result:"Not matched",chapterId:c?a:e?.chapterId,sceneId:e?.sceneId}),t||(c?(uo(),mo("Scene not found in PlotDirector.")):(ho(),po("Chapter not found in PlotDirector."))),o(),!1}const a=mn;return So({result:a?"Matched by title fallback":"Matched",chapterId:e.chapterId,sceneId:e.sceneId}),await No(t.bookId,e.sceneId,e.chapterId,a?"Title fallback":"Title","Title Match"),a&&(mn=!0,Tr("Stored PlotDirector ID is invalid."),Rr()),!0}catch(e){const t=mn;return Do({lastError:Po(e)}),yo("Not found in PlotDirector"),mn=t,Tr(t?"Stored PlotDirector ID is invalid.":"Unable to load PlotDirector scene data."),So({result:"Error"}),!1}finally{o()}},Tc=async()=>{if(On=null,Jn)return Gn=!0,void xr("Runtime update deferred; update already running.");if(!Jt||!Uo())return Bn=!1,void xr("Runtime update skipped.");if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return Bn=!1,zr("Manual"),void Tr("Automatic scene tracking is unavailable in this Word version. Use Refresh Current Scene.");if(!vn)return Bn=!1,zr("Manual"),void Tr("Document structure is not cached yet. Use Refresh Current Scene.");const e=Date.now()-Hn;if(Bn&&e{On&&window.clearTimeout(On),On=window.setTimeout(Tc,Math.max(0,e)),zr("Waiting for typing to pause"),xr("Runtime update scheduled.")},Wc=()=>{On&&(window.clearTimeout(On),On=null),Bn=!1,Gn=!1},Rc=()=>{((e="selection",t=1200)=>{if(Bn=!0,Hn=Date.now(),Fn+=1,Vn=null,Mr(),Sc(),zn&&(Qn=!0),tr&&(nr=!0),xr(`${e} changed.`),Jn)return Gn=!0,void zr("Waiting for typing to pause");jc(t)})("Selection")},Uc=()=>{if(Yn&&window.Office?.context?.document&&"function"==typeof window.Office.context.document.removeHandlerAsync&&window.Office?.EventType?.DocumentSelectionChanged)try{window.Office.context.document.removeHandlerAsync(window.Office.EventType.DocumentSelectionChanged,{handler:Rc}),Yn=!1}catch(e){console.warn("Unable to remove Word selection tracking handler.",e)}},Mc=async(e,n)=>{if(vn=null,oo(),!e)return Xt=[],$o(G,"Select a project first"),$o(m,"Select a project first"),$r(""),Ao(t,null),oa(),yo("Not detected"),void Fr();$o(G,"Loading books..."),$r(""),yo("Not detected"),Fr("Select a book to analyse manuscript structure.");try{const r=await Za(`/api/word-companion/projects/${e}/books`);if(Xt=Array.isArray(r.books)?r.books:[],0===Xt.length)return $o(G,"No books available."),$o(m,"No books available."),$r("No books available."),Ao(t,null),oa(),void Qo("No books available.");To(G,"Choose a book",Xt.map(e=>({...e,displayTitle:jo(e)})),"bookId","displayTitle");const o=Xt.find(e=>e.bookId===n);o&&G?(G.value=String(o.bookId),Ao(t,o.bookId)):Ao(t,null),((e=null)=>{if(!m)return;if(0===Xt.length)return void $o(m,"No books available.");To(m,"Choose a book",Xt.map(e=>({...e,displayTitle:jo(e)})),"bookId","displayTitle");const t=e||Number.parseInt(G?.value||"",10),n=Xt.find(e=>e.bookId===t);n&&(m.value=String(n.bookId)),Jo()})(o?.bookId||n),oa(),yo((Uo(),"Not detected")),Fr(Uo()?"Ready to analyse manuscript structure.":"Select a book to analyse manuscript structure."),Qo(Oo()?"Ready to scan manuscript structure.":"Select a Book to begin.")}catch{Xt=[],$o(G,"Unable to load books."),$o(m,"Unable to load books."),$r("Unable to load books."),Ao(t,null),oa(),yo("Not detected"),Fr("Unable to load books."),Qo("Unable to load books.")}};for(const e of i)e.addEventListener("click",()=>Pr(e.dataset.tabButton));Lr(),H?.addEventListener("change",async()=>{const n=Number.parseInt(H.value||"",10);ro(),h&&Number.isInteger(n)&&n>0&&(h.value=String(n)),Ao(e,Number.isInteger(n)&&n>0?n:null),Ao(t,null),yo("Not detected"),Fr(),Qo("Select a Book to begin."),await Mc(n,null),Vo()}),G?.addEventListener("change",()=>{const e=Number.parseInt(G.value||"",10);ro(),Ao(t,Number.isInteger(e)&&e>0?e:null),m&&Number.isInteger(e)&&e>0&&(m.value=String(e)),oa(),yo("Not detected"),Fr(Uo()?"Ready to analyse manuscript structure.":"Select a book to analyse manuscript structure."),Qo(Oo()?"Ready to scan manuscript structure.":"Select a Book to begin."),Vo()}),h?.addEventListener("change",async()=>{const n=Number.parseInt(h.value||"",10);ro(),H&&Number.isInteger(n)&&n>0&&(H.value=String(n)),Ao(e,Number.isInteger(n)&&n>0?n:null),Ao(t,null),Qo("Select a Book to begin."),await Mc(n,null),Vo()}),m?.addEventListener("change",()=>{const e=Number.parseInt(m.value||"",10);ro(),G&&Number.isInteger(e)&&e>0&&(G.value=String(e)),Ao(t,Number.isInteger(e)&&e>0?e:null),oa(),Qo(Oo()?"Ready to scan manuscript structure.":"Select a Book to begin."),Vo()}),y?.addEventListener("input",Jo),g?.addEventListener("click",async()=>{const e=Mo(),t=String(y?.value||"").trim();if(e&&t){g.disabled=!0,Yo("Creating Book...");try{const n=await ec(`/api/word-companion/projects/${e.projectId}/books`,{title:t});y&&(y.value=""),H&&(H.value=String(e.projectId)),await Mc(e.projectId,n.bookId),m&&(m.value=String(n.bookId)),Yo("Ready to scan manuscript structure.")}catch(e){console.error("Unable to create Book from Word Companion.",e),Yo(`Unable to create Book: ${Po(e)}`)}finally{Jo()}}else Jo()}),w?.addEventListener("click",async()=>{const e=Mo(),t=Oo();if(e&&t)if(Zt&&en&&window.Word&&"function"==typeof window.Word.run){w&&(w.disabled=!0,w.textContent="Scanning..."),Yo("Scanning manuscript structure...");try{const n=await window.Word.run(async e=>{const n=e.document.body.paragraphs;return n.load("text,styleBuiltIn,style"),await e.sync(),((e,t)=>{const n=[];let r=null,o=null,a=0;const c=[],i=new Set(["***","###"]),s=()=>{if(!r)return null;const e={title:`Scene ${r.scenes.length+1}`,sortOrder:10*(r.scenes.length+1),wordCount:0};return r.scenes.push(e),e};return e.forEach(e=>{const d=String(e.text||"").trim();if(!d)return;if(c.push(d),ia(e,1))return r={title:d,sortOrder:10*(n.length+1),wordCount:0,scenes:[]},n.push(r),o=s(),void(a+=sa(d));if(!r||!o)return;if(t&&i.has(d))return void(o=s());const l=sa(d);r.wordCount+=l,o.wordCount+=l,a+=l}),{chapters:n,chapterCount:n.length,sceneCount:n.reduce((e,t)=>e+t.scenes.length,0),wordCount:a,documentText:c.join("\n")}})(n.items,!!t.usesExplicitScenes)});Dn=n,qn=!1;const r=await ec("/api/word-companion/manuscript/analyse",{projectId:e.projectId,bookId:t.bookId,documentGuid:na(),chapterCount:n.chapterCount,sceneCount:n.sceneCount,wordCount:n.wordCount});En=r,(e=>{if(!b)return;b.innerHTML="";const t=document.createElement("strong");t.textContent="Manuscript Analysis",b.append(t);const n=document.createElement("dl"),r=[["Project",e.projectTitle],["Book",jo({title:e.bookTitle,subtitle:e.bookSubtitle})],["Chapters",Number(e.chapterCount||0).toLocaleString()],["Scenes",Number(e.sceneCount||0).toLocaleString()],["Words",Number(e.wordCount||0).toLocaleString()]];for(const[e,t]of r){const r=document.createElement("dt");r.textContent=e;const o=document.createElement("dd");o.textContent=t||"-",n.append(r,o)}b.append(n),Or(b,!1),Or(D,!1),Yo(e.message||"Preview generated."),Jo()})(r);try{const t=await ec("/api/word-companion/manuscript/discover-characters",{projectId:e.projectId,documentText:n.documentText||""});Pn=Array.isArray(t?.candidates)?t.candidates:[],Or(S,!0),Or(v,!0)}catch(e){console.error("Unable to discover character candidates.",e),Pn=[],Or(S,!0),Or(v,!0)}Do({lastScan:"Success",lastError:"-",paragraphs:"-",heading1:String(n.chapterCount),heading2:"-"})}catch(e){console.error("Unable to scan manuscript for first-run import.",e),Qo(`Unable to scan manuscript: ${Po(e)}`),Do({lastScan:"Failure",lastError:Po(e)})}finally{w&&(w.textContent="Scan Manuscript"),Jo()}}else Qo("Word document access is unavailable.");else Qo("Select a Project and Book to begin.")}),P?.addEventListener("click",async()=>{const n=Mo(),o=Oo();if(!(n&&o&&Dn&&En?.canImport))return void Jo();let a=!1;if(!En.requiresReplaceConfirmation||(a=await new Promise(e=>{if(!q||"function"!=typeof q.showModal)return void e(window.confirm("This Book already contains planned chapters and scenes.\n\nImporting this manuscript will replace the planned structure.\n\nContinue?"));const t=t=>{A?.removeEventListener("click",n),$?.removeEventListener("click",r),q?.removeEventListener("cancel",o),q?.close(),e(t)},n=()=>t(!0),r=()=>t(!1),o=e=>{e.preventDefault(),t(!1)};A?.addEventListener("click",n),$?.addEventListener("click",r),q?.addEventListener("cancel",o),q.showModal()}),a)){Tn=!0,Jo(),Yo("Importing manuscript structure...");try{const i=na(),s=await ec("/api/word-companion/manuscript/import",{projectId:n.projectId,bookId:o.bookId,documentGuid:i,bindingVersion:1,replacePlanned:a,chapters:Dn.chapters});if(!s.imported)return En={...En,canImport:!s.requiresReplaceConfirmation,requiresReplaceConfirmation:!!s.requiresReplaceConfirmation,message:s.message||En.message},Yo(s.message||"Import was not completed."),void Jo();const d=s.manuscriptDocument;await(c={documentGuid:d?.documentGuid||i,bookId:s.bookId,projectId:s.projectId,bindingVersion:d?.bindingVersion||1},new Promise((e,t)=>{const n=window.Office?.context?.document?.settings;n?(n.set(r.documentGuid,c.documentGuid),n.set(r.bookId,String(c.bookId)),n.set(r.projectId,String(c.projectId)),n.set(r.bindingVersion,String(c.bindingVersion||1)),n.saveAsync(n=>{n.status===window.Office.AsyncResultStatus.Succeeded?e():t(n.error||new Error("Unable to save Word document metadata."))})):t(new Error("Word document settings are unavailable."))})),Cn={documentGuid:d?.documentGuid||i,bookId:s.bookId,projectId:s.projectId,bindingVersion:d?.bindingVersion||1},Ao(e,s.projectId),Ao(t,s.bookId),H&&(H.value=String(s.projectId));const l=Pn;await Mc(s.projectId,s.bookId),qr("Connected"),qn=!0,An=l.length>0,$n=An?Date.now():0,Pn=l,l.length>0?(_o(!0),Zo(l),Or(D,!0),Or(v,!1),Yo(s.message||"Manuscript structure imported."),Tr(s.message||"Manuscript structure imported."),window.setTimeout(Jo,750)):await tc(s.message||"Manuscript structure imported.")}catch(e){console.error("Unable to import manuscript structure.",e),Yo(`Unable to import manuscript: ${Po(e)}`)}finally{Tn=!1,Jo()}var c}else Yo("Import cancelled.")}),x?.addEventListener("click",async()=>{const e=Mo()||Ro(),t=Xo();if(!An||Date.now()-$n<750||!e||0===t.length)Jo();else{jn=!0,Jo(),Eo(C,"Creating selected characters...");try{const n=await ec("/api/word-companion/manuscript/create-discovered-characters",{projectId:e.projectId,candidateNames:t});Tr(n.message||"Characters created."),Eo(C,`${n.createdCount||0} created, ${n.skippedCount||0} already existed.`),await tc(n.message||"Characters created.")}catch(e){console.error("Unable to create discovered characters.",e),Eo(C,`Unable to create characters: ${Po(e)}`)}finally{jn=!1,Jo()}}}),N?.addEventListener("click",()=>tc("Character creation skipped.")),E?.addEventListener("click",()=>tc()),f?.addEventListener("click",()=>_o(!1)),L?.addEventListener("click",()=>Qo("Import cancelled.")),J?.addEventListener("click",Fa),ne?.addEventListener("click",Va),re?.addEventListener("click",async()=>{const e=Uo();if(!e||0===Vr().length||Wn)return void Kr();if(!await Ya())return;Wn=!0,Yr(),Gr("Applying structure sync...");const t={createdChapters:0,createdScenes:0,linkedChapters:0,linkedScenes:0,skipped:Array.isArray(Sn?.manualReview)?Sn.manualReview.length:0,failed:0},n=Vr().filter(e=>"couldLink"===e.category&&"Chapter"===e.type),r=Vr().filter(e=>"wouldCreateChapters"===e.category),o=Vr().filter(e=>"couldLink"===e.category&&"Scene"===e.type),a=Vr().filter(e=>"wouldCreateScenes"===e.category),c=[];let i="";const s=(e,t)=>{c.push({item:e,counterName:t})},d=async(e,n)=>{try{await(async e=>{if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)throw new Error("Word document access is unavailable.");await window.Word.run(async t=>{const n=await Ua(t),r="Chapter"===e.type?e.wordChapter:e.wordScene,o=Number.isInteger(r?.paragraphIndex)?n.bodyParagraphs[r.paragraphIndex]:null;if(!o)throw new Error("Unable to locate the Word heading.");if("Chapter"===e.type){const t=e.pdChapter?.chapterId;if(!t)throw new Error("Chapter ID is not available.");ic(o,"PlotDirector Chapter",`PD-CHAPTER-${t}`,"PD-CHAPTER")}else{const t=e.pdScene?.sceneId;if(!t)throw new Error("Scene ID is not available.");ic(o,"PlotDirector Scene",`PD-SCENE-${t}`,"PD-SCENE")}await t.sync()})})(e),Ja(e,"Success"),t[n]+=1}catch(n){Ja(e,"Failed",Po(n)),t.failed+=1}};try{for(const e of n)s(e,"linkedChapters");for(const n of r)try{await Qa(e.bookId,n),s(n,"createdChapters")}catch(e){Ja(n,"Failed",Po(e)),t.failed+=1}for(const e of o)s(e,"linkedScenes");for(const n of a)try{await Xa(e.bookId,n),s(n,"createdScenes")}catch(e){Ja(n,"Failed",Po(e)),t.failed+=1}for(const e of c)await d(e.item,e.counterName);for(const e of Sn.manualReview||[])Ja(e,"Skipped");Ra(Sn),i=(e=>`Structure Sync Complete. Created: ${e.createdChapters} Chapters, ${e.createdScenes} Scenes. Linked: ${e.linkedChapters} Chapters, ${e.linkedScenes} Scenes. Skipped: ${e.skipped} Manual Review items. Failed: ${e.failed} Items.`)(t),Gr(i)}finally{Wn=!1,Yr(),i&&(oo(),await Va(),Gr(`${i} Preview refreshed.`))}}),St?.addEventListener("click",()=>Ka(!0)),Ct?.addEventListener("click",()=>Ka(!1)),It?.addEventListener("cancel",e=>{e.preventDefault(),Ka(!1)}),ce?.addEventListener("click",$c),Y?.addEventListener("click",async()=>{if(!Uo())return Tr("Select a PlotDirector book first."),void yo("Not detected");Wc(),Zr(!0),zr("Updating...");try{await Promise.all([xc(!0),Nc(!0),Ec(!0)]);if(!await Fa())return;if(Qr(!1),!on)return await Ac(),void zr("Current scene updated");await $c(),zr("Current scene updated")}finally{Zr(!1)}}),ke?.addEventListener("click",()=>Ic({source:"manual"})),Re?.addEventListener("click",async()=>{if(ln)if(!Mn&&await(async()=>{if(!ln)return!1;if(!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return Qr(!0,"The Word cursor is now in a different scene. Refresh Current Scene before saving."),!1;try{const e=await window.Word.run(async e=>await Ua(e)),t=e.currentChapter?.title||"",n=e.currentScene?.title||"",r=e.currentScene?.anchorId||null,o=e.currentChapter?.anchorId||null,a=Number.isInteger(r)&&r===ln,c=Number.isInteger(o)&&o===un,i=bo(n,"scene").toLocaleLowerCase()===bo(Ir||on,"scene").toLocaleLowerCase(),s=bo(t,"chapter").toLocaleLowerCase()===bo(br||rn,"chapter").toLocaleLowerCase();return a||i&&(c||s)?(Qr(!1),!0):(Qr(!0,"The Word cursor is now in a different scene. Refresh Current Scene before saving."),!1)}catch(e){return console.error("Unable to verify current Word scene before saving.",e),Do({lastError:Po(e)}),Qr(!0,"The Word cursor is now in a different scene. Refresh Current Scene before saving."),!1}})()){Re&&(Re.disabled=!0,Re.textContent="Saving...");try{await(e=`/api/word-companion/scenes/${ln}/links`,t={characterIds:ac(Te),assetIds:ac(je),locationIds:[],primaryLocationId:cc()},Za(e,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})),(()=>{const e=new Set(ac(Te)),t=new Set(ac(je));vr={characters:vr.characters.map(t=>({...t,linked:e.has(Number.parseInt(go(t,"character"),10))})),assets:vr.assets.map(e=>({...e,linked:t.has(Number.parseInt(go(e,"asset"),10))})),locations:vr.locations,primaryLocationId:cc()}})(),ao("Scene links saved successfully."),Do({lastError:"-"})}catch(e){ao("Unable to save scene links."),Do({lastError:Po(e)})}finally{Re&&(Re.disabled=!ln||Mn,Re.textContent="Save Scene Links")}var e,t}else ao("The Word cursor is now in a different scene. Refresh Current Scene before saving.");else ao("Link a PlotDirector scene first.")}),pe?.addEventListener("click",async()=>{await sc()}),he?.addEventListener("click",async()=>{console.debug("Word Companion unlink clicked"),Tr("Preparing to unlink manuscript...");const e=(()=>{const e=Cn||ea();if(e)return e;const t=kn?.manuscriptDocument?.documentGuid||"",n=Number.parseInt(kn?.bookId||"",10),r=Number.parseInt(kn?.projectId||"",10);return t&&Number.isInteger(n)&&n>0&&Number.isInteger(r)&&r>0?{documentGuid:t,bookId:n,projectId:r,bindingVersion:Number.parseInt(kn?.manuscriptDocument?.bindingVersion||"",10)||1}:null})();if(!e)return void Tr("This Word document is not linked.");if(!e.documentGuid||!Number.isInteger(e.bookId)||e.bookId<=0)return void Tr("Unable to unlink: bound document metadata is incomplete.");if(await new Promise(e=>{if(!T||"function"!=typeof T.showModal)return void e(window.confirm("This will remove PlotDirector binding metadata from this Word document.\n\nIf PlotDirector is reachable, it will also unlink this Book in PlotDirector.\n\nYour manuscript text will not be changed.\n\nContinue?"));const t=t=>{j?.removeEventListener("click",n),W?.removeEventListener("click",r),T?.removeEventListener("cancel",o),T?.close(),e(t)},n=()=>t(!0),r=()=>t(!1),o=e=>{e.preventDefault(),t(!1)};j?.addEventListener("click",n),W?.addEventListener("click",r),T?.addEventListener("cancel",o),T.showModal()})){Tr("Unlinking manuscript..."),he&&(he.disabled=!0,he.textContent="Unlinking...");try{await ec("/api/word-companion/manuscript/unlink",{documentGuid:e.documentGuid,bookId:e.bookId}),await ta(),await oc("Manuscript unlinked.")}catch(e){console.error("Unable to unlink manuscript binding.",e),Tr("Unable to unlink from PlotDirector.");if(window.confirm("PlotDirector could not be updated, but you can still remove the binding from this Word document.\n\nYou may need to unlink the Book manually in PlotDirector later.\n\nRemove binding from this Word document only?"))try{await ta(),await oc("Word metadata removed. Unlink the Book manually in PlotDirector if it still shows as linked.")}catch(e){console.error("Unable to remove Word document metadata.",e),Tr(`Unable to remove Word metadata: ${Po(e)}`)}else Tr(`Unable to unlink manuscript: ${Po(e)}`)}finally{he&&(he.disabled=!1,he.textContent="Unlink this Word document"),Br()}}else Tr("Unlink cancelled.")}),He?.addEventListener("click",async()=>{const e=Uo();if(!e||!rn||un)return void po("Chapter not found in PlotDirector.");const t=Io(rn),n=await((e,t)=>(Eo(yt,`"${e}"`),Eo(gt,`"${t}"`),mt?new Promise(e=>{bn=e,mt.showModal?mt.showModal():mt.hidden=!1}):Promise.resolve(window.confirm(`Create PlotDirector chapter:\n\n"${e}"\n\nin book:\n\n"${t}"\n\n?`))))(t,jo(e)||"selected book");if(n){He&&(He.disabled=!0,He.textContent="Creating...");try{const n=Number.isInteger(cn)&&cn>0?10*cn:null,r=await ec(`/api/word-companion/books/${e.bookId}/chapters`,{title:t,sortOrder:n});if(!r?.chapterId)throw new Error("Chapter creation did not return a chapter ID.");oo(),Xr(null,r.chapterId,"Manual chapter link");if(!await dc("Chapter created and linked successfully."))return;await lc("Chapter created and linked successfully.")}catch(e){io("Unable to create chapter."),Do({lastError:Po(e)})}finally{He&&(He.disabled=!so(),He.textContent="Create Chapter in PlotDirector")}}}),Ge?.addEventListener("click",async()=>{const e=Uo();if(e&&rn&&!un){Ge&&(Ge.disabled=!0,Ge.textContent="Loading...");try{fn=await ko(e.bookId),In=null,dt&&(dt.value=""),Eo(ht,""),pc(),st?.showModal?st.showModal():st&&(st.hidden=!1)}catch(e){io("Unable to load chapters."),Do({lastError:Po(e)})}finally{Ge&&(Ge.disabled=!so(),Ge.textContent="Link to Existing Chapter")}}else po("Chapter not found in PlotDirector.")}),dt?.addEventListener("input",pc),ut?.addEventListener("click",async()=>{const e=fn.find(e=>e.chapterId===In);if(e){ut&&(ut.disabled=!0,ut.textContent="Linking...");try{Xr(null,e.chapterId,"Manual chapter link");if(!await dc("Chapter linked successfully."))return;hc(),await lc("Chapter linked successfully.")}catch(e){Eo(ht,"Unable to link chapter."),io("Unable to link chapter."),Do({lastError:Po(e)})}finally{ut&&(ut.disabled=!In,ut.textContent="Link Chapter")}}else Eo(ht,"Select a chapter.")}),pt?.addEventListener("click",hc),wt?.addEventListener("click",()=>uc(!0)),ft?.addEventListener("click",()=>uc(!1)),mt?.addEventListener("cancel",e=>{e.preventDefault(),uc(!1)}),Je?.addEventListener("click",async()=>{const e=Uo();if(!e||!on)return void mo("Scene not found in PlotDirector.");const t=bo(on,"scene"),n=bo(rn,"chapter"),r=await((e,t)=>(Eo(ot,`"${e}"`),Eo(at,`"${t}"`),rt?new Promise(e=>{wn=e,rt.showModal?rt.showModal():rt.hidden=!1}):Promise.resolve(window.confirm(`Create PlotDirector scene:\n\n"${e}"\n\nwithin chapter:\n\n"${t}"\n\n?`))))(t,n);if(r){Je&&(Je.disabled=!0,Je.textContent="Creating...");try{const n=await xo();if(!n)return void mo("This chapter does not exist in PlotDirector. Create or link the chapter first.");const r=await ec(`/api/word-companion/books/${e.bookId}/chapters/${n.chapterId}/scenes`,{sceneTitle:t});if(!r?.sceneId||!r?.chapterId)throw new Error("Scene creation did not return a scene ID.");oo(),await mc(r.sceneId,r.chapterId,"Scene created and linked successfully.")}catch(e){co("Unable to create scene."),Do({lastError:Po(e)})}finally{Je&&(Je.disabled=!lo(),Je.textContent="Create Scene in PlotDirector")}}}),_e?.addEventListener("click",async()=>{const e=Uo();if(e&&on){_e&&(_e.disabled=!0,_e.textContent="Loading...");try{const t=await Co(e.bookId),n=vo(t);if(!n)return void mo("This chapter does not exist in PlotDirector. Create or link the chapter first.");yn=Array.isArray(n.scenes)?n.scenes:[],gn=null,Xe&&(Xe.value=""),Eo(nt,""),gc(),Qe?.showModal?Qe.showModal():Qe&&(Qe.hidden=!1)}catch(e){co("Unable to load scenes."),Do({lastError:Po(e)})}finally{_e&&(_e.disabled=!lo(),_e.textContent="Link to Existing Scene")}}else mo("Scene not found in PlotDirector.")}),Xe?.addEventListener("input",gc),et?.addEventListener("click",async()=>{const e=Uo(),t=yn.find(e=>e.sceneId===gn);if(e&&t){et&&(et.disabled=!0,et.textContent="Linking...");try{const e=await xo();if(!e)throw new Error("This chapter does not exist in PlotDirector. Create or link the chapter first.");wc(),await mc(t.sceneId,e.chapterId,"Scene linked successfully.")}catch(e){Eo(nt,"Unable to link scene."),co("Unable to link scene."),Do({lastError:Po(e)})}finally{et&&(et.disabled=!gn,et.textContent="Link Scene")}}else Eo(nt,"Select a scene.")}),tt?.addEventListener("click",wc),ct?.addEventListener("click",()=>yc(!0)),it?.addEventListener("click",()=>yc(!1)),rt?.addEventListener("cancel",e=>{e.preventDefault(),yc(!1)}),Tt?.addEventListener("click",async()=>{Tt&&(Tt.disabled=!0,Tt.textContent="Running...");try{if(await Lo(),!Zt||!en||!window.Word||"function"!=typeof window.Word.run)return void Do({lastScan:"Failure",lastError:"Word document access is unavailable.",paragraphs:"-",heading1:"-",heading2:"-"});const e=await window.Word.run(async e=>{const t=e.document.body.paragraphs;return t.load("items"),await e.sync(),t.items.length});Do({lastScan:"Success",lastError:"-",paragraphs:String(e)})}catch(e){console.error("Word Companion diagnostics failed.",e);const t=Po(e);Do({lastScan:"Failure",lastError:t}),Tr(`Unable to scan the Word document: ${t}`)}finally{Tt&&(Tt.disabled=!1,Tt.textContent="Run Diagnostics")}});const Oc=Jt?(async()=>{$o(H,"Loading projects..."),$o(G,"Select a project first"),$o(h,"Loading projects..."),$o(m,"Select a project first"),Ar(""),$r("");try{const n=await Za("/api/word-companion/projects");if(Qt=Array.isArray(n.projects)?n.projects:[],0===Qt.length)return $o(H,"No projects available."),$o(h,"No projects available."),Ar("No projects available."),Ao(e,null),Ao(t,null),oa(),void Qo("No projects available.");To(H,"Choose a project",Qt,"projectId","title"),zo();const r=qo(e),o=Qt.find(e=>e.projectId===r);o&&H?(H.value=String(o.projectId),h&&(h.value=String(o.projectId)),Ao(e,o.projectId),await Mc(o.projectId,qo(t))):(Ao(e,null),Ao(t,null),oa(),Qo("Select a Project and Book to begin."))}catch{Qt=[],Xt=[],$o(H,"Unable to connect to PlotDirector."),$o(G,"Select a project first"),$o(h,"Unable to connect to PlotDirector."),$o(m,"Select a project first"),qr("Unable to connect to PlotDirector."),Ar("Unable to connect to PlotDirector."),Ao(e,null),Ao(t,null),oa(),Qo("Unable to connect to PlotDirector.")}})():Promise.resolve();if(Jt||($o(H,"Please sign in to PlotDirector."),$o(G,"Please sign in to PlotDirector."),$o(h,"Please sign in to PlotDirector."),$o(m,"Please sign in to PlotDirector."),oa()),!window.Office||"function"!=typeof window.Office.onReady)return Er("Word Companion ready"),Tr("Word document access is unavailable."),zr("Manual"),Do({host:"Unavailable",platform:"Unavailable",ready:"No",wordApi:"No"}),void Ko().catch(e=>{console.debug("Word Companion presence unavailable.",e)});Lo().then(async()=>{Er("Word Companion ready"),await Oc,await(async()=>{if(!Jt||!en)return void _o(!1);const n=ea();if(n)try{const r=await Za(`/api/word-companion/runtime/book/${n.bookId}?documentGuid=${encodeURIComponent(n.documentGuid)}`),o=r?.manuscriptDocument;if(r?.bookId===n.bookId&&r?.projectId===n.projectId&&o&&String(o.documentGuid).toLowerCase()===n.documentGuid.toLowerCase())return Cn=n,no(r),Ao(e,r.projectId),Ao(t,r.bookId),H&&(H.value=String(r.projectId)),await Mc(r.projectId,r.bookId),_o(!1),qr("Connected"),$r("Bound manuscript detected."),void await rc()}catch{}Cn=null,ro(),zo(),Mo()?await Mc(Mo().projectId,null):$o(m,"Select a project first"),Qo("Select a Project and Book to begin."),_o(!0)})(),await Ko(),en?(()=>{if(Jt){if(!window.Office?.context?.document||"function"!=typeof window.Office.context.document.addHandlerAsync||!window.Office?.EventType?.DocumentSelectionChanged)return zr("Manual"),void Tr("Automatic scene tracking is unavailable in this Word version. Use Refresh Current Scene.");try{window.Office.context.document.addHandlerAsync(window.Office.EventType.DocumentSelectionChanged,Rc,e=>{e?.status===window.Office.AsyncResultStatus.Succeeded?(Yn=!0,zr("Live")):(zr("Manual"),Tr("Automatic scene tracking is unavailable in this Word version. Use Refresh Current Scene."))}),window.addEventListener("beforeunload",Uc)}catch(e){console.warn("Unable to register Word selection tracking handler.",e),zr("Manual"),Tr("Automatic scene tracking is unavailable in this Word version. Use Refresh Current Scene.")}}})():(Tr("Word document access is unavailable."),zr("Manual"))}).catch(e=>{console.error("Office readiness check failed.",e),qr("Unable to connect to PlotDirector."),Er("Word Companion unavailable"),Tr("Word document access is unavailable."),Do({ready:"No",wordApi:"No",lastScan:"Failure"})})})(); \ No newline at end of file diff --git a/PlotLine/wwwroot/js/word-companion-presence.js b/PlotLine/wwwroot/js/word-companion-presence.js index 7ff8acc..3c395ca 100644 --- a/PlotLine/wwwroot/js/word-companion-presence.js +++ b/PlotLine/wwwroot/js/word-companion-presence.js @@ -148,6 +148,19 @@ connection.on("OnboardingScanProgress", applyScanState); connection.on("OnboardingScanCompleted", applyScanState); connection.on("OnboardingScanFailed", applyScanState); + connection.on("OnboardingBuildProgress", (state) => { + const message = field(state, "message", "Message") || "Building project structure..."; + document.querySelectorAll("[data-onboarding-build-message]").forEach((node) => { + const percent = field(state, "percentComplete", "PercentComplete"); + node.textContent = percent === null || percent === undefined ? message : `${message} ${percent}%`; + }); + }); + connection.on("OnboardingBuildCompleted", (result) => { + const previewId = field(result, "previewID", "PreviewID") || field(result, "previewId", "PreviewId"); + if (previewId) { + window.location.href = `/onboarding/build-complete?previewId=${encodeURIComponent(previewId)}`; + } + }); connection.onreconnecting(() => applyStatus({ status: "Connecting", isConnected: false })); connection.onreconnected(() => connection.invoke("WatchCompanionPresence").then(applyStatus).catch(() => {})); connection.onclose(() => applyStatus({ status: "Offline", isConnected: false })); @@ -172,4 +185,27 @@ } }); }); + + document.querySelectorAll("[data-onboarding-build-start]").forEach((button) => { + button.addEventListener("click", async () => { + const previewId = button.dataset.previewId; + if (!previewId) { + return; + } + button.disabled = true; + document.querySelectorAll("[data-onboarding-build-message]").forEach((node) => { + node.textContent = "Creating chapters..."; + }); + try { + const result = await connection.invoke("StartOnboardingProjectBuild", previewId); + const returnedPreviewId = field(result, "previewID", "PreviewID") || previewId; + window.location.href = `/onboarding/build-complete?previewId=${encodeURIComponent(returnedPreviewId)}`; + } catch (error) { + button.disabled = false; + document.querySelectorAll("[data-onboarding-build-message]").forEach((node) => { + node.textContent = error?.message || "The project structure could not be built. Check the review and try again."; + }); + } + }); + }); })();