diff --git a/PlotLine/Models/WordCompanionApiModels.cs b/PlotLine/Models/WordCompanionApiModels.cs index 610cd1c..59d7889 100644 --- a/PlotLine/Models/WordCompanionApiModels.cs +++ b/PlotLine/Models/WordCompanionApiModels.cs @@ -338,6 +338,8 @@ public sealed class WordCompanionCharacterCandidateDto { public string Text { get; init; } = string.Empty; public int MentionCount { get; init; } + public string Confidence { get; init; } = "Medium"; + public string Reason { get; init; } = string.Empty; } public sealed class WordCompanionDiscoverCharactersRequest diff --git a/PlotLine/Services/WordCompanionService.cs b/PlotLine/Services/WordCompanionService.cs index a3ca6d3..b684af9 100644 --- a/PlotLine/Services/WordCompanionService.cs +++ b/PlotLine/Services/WordCompanionService.cs @@ -35,19 +35,42 @@ public sealed class WordCompanionService( private const int CharacterDiscoveryMinimumMentions = 5; private const int CharacterDiscoverySuggestionLimit = 50; private static readonly Regex CharacterCandidateRegex = new( - @"\b(?:(?:Mr|Mrs|Ms|Miss|Dr|Rev|Fr|Sir|Lady|Lord|DS|DI|DC|PC)\.?\s+)?[A-Z][a-z]+(?:[-'][A-Z]?[a-z]+)?(?:\s+[A-Z][a-z]+(?:[-'][A-Z]?[a-z]+)?){0,2}\b", + @"(? CharacterDiscoveryStopWords = new(StringComparer.OrdinalIgnoreCase) { "A", "An", "And", "As", "At", "But", "By", "For", "From", "He", "Her", "His", "I", "If", "In", "Into", "It", "Its", "Of", "On", "Or", "Our", "She", "So", "That", "The", "Their", "Then", "There", "They", "This", "To", "We", "When", "Where", "Who", "With", "You", "Your", - "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", - "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "Christmas", "Easter", "Morning", "Afternoon", "Evening", "Night", "Today", "Tomorrow", "Yesterday", "Chapter", "Scene", "Part", "Prologue", "Epilogue", "Book", "Volume", "Act", "Mum", "Dad", "Mother", "Father", "Grandma", "Grandad", "Nan", "Nana", "Granny", - "Yes", "No", "Okay", "Ok", "Hello", "Goodbye" + "Yes", "No", "Okay", "Ok", "Hello", "Goodbye", + "What", "Not", "Just", "Yeah", "How", "Well", "Are", "Like", "One", "Can", "Thank", "Why", "Come", + "Let", "Maybe", "Please", "God", "Something", "After", "Did", "Even", "All", "Nothing", "Have", + "Don", "Won", "Could", "Would", "Should", "Do", "Does", "Had", "Has", "Was", "Were", "Is", "Am", + "Right", "Really", "Now", "Look", "Listen", "Sorry", "Thanks", "Fine", "Great", "Sure", "Course", + "Thing", "Things", "Everyone", "Everything", "Anything", "Someone", "Nobody", "People" + }; + private static readonly HashSet CharacterDiscoveryTemporalWords = new(StringComparer.OrdinalIgnoreCase) + { + "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", + "Spring", "Summer", "Autumn", "Fall", "Winter" + }; + private static readonly HashSet CharacterDiscoveryNameLikeWords = new(StringComparer.OrdinalIgnoreCase) + { + "Grace", "Jack", "Rose", "May", "Will", "Mark", "Hope", "Faith", "Pat", "Rob", "Bill", "Harry", + "November", "Autumn", "Rain" + }; + private static readonly HashSet CharacterDiscoveryHonorifics = new(StringComparer.OrdinalIgnoreCase) + { + "Mr", "Mrs", "Ms", "Miss", "Dr", "Rev", "Fr", "Sir", "Lady", "Lord", "DS", "DI", "DC", "PC", "Professor", "Aunt", "Uncle" + }; + private static readonly HashSet CharacterDiscoveryDialogueTags = new(StringComparer.OrdinalIgnoreCase) + { + "said", "asked", "replied", "answered", "whispered", "shouted", "called", "muttered", "cried", "snapped", + "sighed", "laughed", "continued", "began", "told", "yelled" }; public async Task ListProjectsAsync() { @@ -343,7 +366,7 @@ public sealed class WordCompanionService( return []; } - var mentions = new Dictionary(StringComparer.Ordinal); + var candidates = new Dictionary(StringComparer.Ordinal); foreach (Match match in CharacterCandidateRegex.Matches(manuscriptText)) { var candidate = NormalizeCandidateName(match.Value); @@ -352,15 +375,33 @@ public sealed class WordCompanionService( continue; } - mentions[candidate] = mentions.TryGetValue(candidate, out var count) ? count + 1 : 1; + if (!candidates.TryGetValue(candidate, out var evidence)) + { + evidence = new CharacterCandidateEvidence(candidate); + candidates[candidate] = evidence; + } + + evidence.MentionCount += 1; + if (AppearsAtSentenceStart(manuscriptText, match.Index)) + { + evidence.SentenceStartMentions += 1; + } + + if (HasDialogueTagEvidence(manuscriptText, match.Index, match.Length)) + { + evidence.DialogueEvidenceCount += 1; + } } - return mentions - .Where(item => item.Value >= CharacterDiscoveryMinimumMentions) - .OrderByDescending(item => item.Value) - .ThenBy(item => item.Key, StringComparer.OrdinalIgnoreCase) + return candidates.Values + .Where(item => item.MentionCount >= CharacterDiscoveryMinimumMentions) + .Select(BuildCandidateDto) + .Where(item => item is not null) + .Select(item => item!) + .OrderByDescending(item => ConfidenceSortValue(item.Confidence)) + .ThenByDescending(item => item.MentionCount) + .ThenBy(item => item.Text, StringComparer.OrdinalIgnoreCase) .Take(CharacterDiscoverySuggestionLimit) - .Select(item => new WordCompanionCharacterCandidateDto { Text = item.Key, MentionCount = item.Value }) .ToList(); } @@ -381,9 +422,159 @@ public sealed class WordCompanionService( return words.Length > 1 || first.Length > 2; } + private static WordCompanionCharacterCandidateDto? BuildCandidateDto(CharacterCandidateEvidence evidence) + { + var score = 0; + var words = evidence.Text.Split(' ', StringSplitOptions.RemoveEmptyEntries); + var first = words.Length > 0 ? TrimHonorificPunctuation(words[0]) : string.Empty; + var hasHonorific = words.Length > 1 && CharacterDiscoveryHonorifics.Contains(first); + var hasMultiWordName = words.Length > 1; + var hasNameLikeWord = words.Any(word => CharacterDiscoveryNameLikeWords.Contains(TrimHonorificPunctuation(word))); + var hasTemporalWord = words.Any(word => CharacterDiscoveryTemporalWords.Contains(TrimHonorificPunctuation(word))); + var nonSentenceStartMentions = evidence.MentionCount - evidence.SentenceStartMentions; + var mostlySentenceStart = evidence.MentionCount > 0 && evidence.SentenceStartMentions >= Math.Ceiling(evidence.MentionCount * 0.8m); + + if (evidence.MentionCount >= CharacterDiscoveryMinimumMentions) + { + score += 1; + } + if (evidence.MentionCount >= 10) + { + score += 1; + } + if (nonSentenceStartMentions > 0) + { + score += 2; + } + if (evidence.DialogueEvidenceCount > 0) + { + score += 3; + } + if (hasMultiWordName) + { + score += 2; + } + if (hasHonorific) + { + score += 2; + } + if (hasNameLikeWord) + { + score += 3; + } + if (hasTemporalWord) + { + score -= 1; + } + if (mostlySentenceStart) + { + score -= 2; + } + if (!hasMultiWordName && evidence.DialogueEvidenceCount == 0 && nonSentenceStartMentions == 0) + { + score -= 1; + } + + if (score < 0) + { + return null; + } + + var confidence = score >= 5 ? "High" : score >= 2 ? "Medium" : "Low"; + return new WordCompanionCharacterCandidateDto + { + Text = evidence.Text, + MentionCount = evidence.MentionCount, + Confidence = confidence, + Reason = BuildCandidateReason(evidence, hasHonorific, hasMultiWordName, hasTemporalWord, mostlySentenceStart) + }; + } + + private static string BuildCandidateReason( + CharacterCandidateEvidence evidence, + bool hasHonorific, + bool hasMultiWordName, + bool hasTemporalWord, + bool mostlySentenceStart) + { + if (evidence.DialogueEvidenceCount > 0) + { + return "Dialogue evidence"; + } + if (hasHonorific) + { + return "Title or honorific"; + } + if (hasMultiWordName) + { + return "Proper-name phrase"; + } + if (hasTemporalWord) + { + return "Possible name"; + } + if (mostlySentenceStart) + { + return "Mostly sentence starts"; + } + return evidence.MentionCount >= 10 ? "Frequent possible name" : "Possible name"; + } + + private static int ConfidenceSortValue(string confidence) + { + return string.Equals(confidence, "High", StringComparison.OrdinalIgnoreCase) ? 3 + : string.Equals(confidence, "Medium", StringComparison.OrdinalIgnoreCase) ? 2 + : 1; + } + + private static bool AppearsAtSentenceStart(string manuscriptText, int matchIndex) + { + for (var i = matchIndex - 1; i >= 0; i--) + { + var character = manuscriptText[i]; + if (char.IsWhiteSpace(character)) + { + if (character is '\n' or '\r') + { + return true; + } + continue; + } + + if (character is '"' or '\'' or '’' or '“' or '”' or '(' or '[') + { + continue; + } + + return character is '.' or '!' or '?' or ':' or ';'; + } + + return true; + } + + private static bool HasDialogueTagEvidence(string manuscriptText, int matchIndex, int matchLength) + { + var before = manuscriptText[Math.Max(0, matchIndex - 48)..matchIndex]; + var afterStart = matchIndex + matchLength; + var after = manuscriptText[afterStart..Math.Min(manuscriptText.Length, afterStart + 48)]; + return HasTagAfterCandidate(after) || HasTagBeforeCandidate(before); + } + + private static bool HasTagAfterCandidate(string textAfterCandidate) + { + var match = Regex.Match(textAfterCandidate, @"^\s*,?\s*(?[a-z]+)\b", RegexOptions.IgnoreCase); + return match.Success && CharacterDiscoveryDialogueTags.Contains(match.Groups["tag"].Value); + } + + private static bool HasTagBeforeCandidate(string textBeforeCandidate) + { + var match = Regex.Match(textBeforeCandidate, @"\b(?[a-z]+)\s*,?\s*[""'\u2019\u201d]?\s*$", RegexOptions.IgnoreCase); + return match.Success && CharacterDiscoveryDialogueTags.Contains(match.Groups["tag"].Value); + } + private static string NormalizeCandidateName(string candidate) { - return Regex.Replace(candidate ?? string.Empty, @"\s+", " ").Trim().Trim(',', '.', ';', ':', '!', '?', '"', '\''); + return Regex.Replace(candidate ?? string.Empty, @"\s+", " ").Trim().Trim(',', '.', ';', ':', '!', '?', '"', '\'', '’'); } private static string TrimHonorificPunctuation(string word) @@ -391,6 +582,14 @@ public sealed class WordCompanionService( return (word ?? string.Empty).Trim().TrimEnd('.'); } + private sealed class CharacterCandidateEvidence(string text) + { + public string Text { get; } = text; + public int MentionCount { get; set; } + public int SentenceStartMentions { get; set; } + public int DialogueEvidenceCount { get; set; } + } + private static WordCompanionManuscriptDocumentDto ToDto(ManuscriptDocumentModel document) => new() { ManuscriptDocumentId = document.ManuscriptDocumentID, diff --git a/PlotLine/wwwroot/js/word-companion-host.js b/PlotLine/wwwroot/js/word-companion-host.js index 30c1243..43164a9 100644 --- a/PlotLine/wwwroot/js/word-companion-host.js +++ b/PlotLine/wwwroot/js/word-companion-host.js @@ -1102,14 +1102,17 @@ const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.value = candidate.text || ""; - checkbox.checked = true; + checkbox.checked = String(candidate.confidence || "").toLowerCase() !== "very low"; checkbox.addEventListener("change", updateFirstRunButtons); const name = document.createElement("strong"); name.textContent = candidate.text || ""; const mentions = document.createElement("em"); - mentions.textContent = `${Number(candidate.mentionCount || 0).toLocaleString()} mentions`; + const reason = String(candidate.reason || "").trim(); + mentions.textContent = reason + ? `${Number(candidate.mentionCount || 0).toLocaleString()} mentions, ${reason.toLowerCase()}` + : `${Number(candidate.mentionCount || 0).toLocaleString()} mentions`; label.append(checkbox, name, mentions); firstRunCharacterCandidates.append(label); diff --git a/PlotLine/wwwroot/js/word-companion-host.min.js b/PlotLine/wwwroot/js/word-companion-host.min.js index 8af6a6c..07443df 100644 --- a/PlotLine/wwwroot/js/word-companion-host.min.js +++ b/PlotLine/wwwroot/js/word-companion-host.min.js @@ -1,4 +1,4 @@ -(()=>{const f={projectId:"plotdirector.word.projectId",bookId:"plotdirector.word.bookId",activeTab:"plotdirector.word.activeTab"},pi={documentGuid:"plotdirector.word.documentGuid",bookId:"plotdirector.word.boundBookId",projectId:"plotdirector.word.boundProjectId",bindingVersion:"plotdirector.word.bindingVersion"},es=new Set(["scene","links","structure","diagnostics"]),fl=document.querySelector(".word-companion-shell"),el=document.querySelector(".word-companion-tabs"),le=[...document.querySelectorAll("[data-tab-button]")],os=[...document.querySelectorAll("[data-tab-panel]")],ol=document.querySelector("[data-first-run-wizard]"),tt=document.querySelector("[data-first-run-project-select]"),y=document.querySelector("[data-first-run-book-select]"),uu=document.querySelector("[data-first-run-new-book-title]"),uf=document.querySelector("[data-first-run-create-book]"),wi=document.querySelector("[data-first-run-scan]"),sl=document.querySelector("[data-first-run-cancel]"),hl=document.querySelector("[data-first-run-status]"),bi=document.querySelector("[data-first-run-summary]"),ae=document.querySelector("[data-first-run-character-section]"),ur=document.querySelector("[data-first-run-character-status]"),fr=document.querySelector("[data-first-run-character-candidates]"),ff=document.querySelector("[data-first-run-character-actions]"),ve=document.querySelector("[data-first-run-create-characters]"),cl=document.querySelector("[data-first-run-skip-characters]"),ye=document.querySelector("[data-first-run-import-actions]"),pe=document.querySelector("[data-first-run-import]"),ll=document.querySelector("[data-first-run-import-cancel]"),kr=document.querySelector("[data-first-run-replace-dialog]"),ss=document.querySelector("[data-first-run-replace-confirm]"),hs=document.querySelector("[data-first-run-replace-cancel]"),cs=document.querySelector("[data-office-status]"),ls=document.querySelector("[data-connection-status]"),as=document.querySelector("[data-summary-connection]"),vs=document.querySelector("[data-summary-project]"),ys=document.querySelector("[data-summary-book]"),v=document.querySelector("[data-project-select]"),g=document.querySelector("[data-book-select]"),ps=document.querySelector("[data-project-message]"),ws=document.querySelector("[data-book-message]"),ef=document.querySelector("[data-refresh-current-scene]"),er=document.querySelector("[data-refresh-document]"),bs=document.querySelector("[data-current-chapter]"),ks=document.querySelector("[data-current-scene]"),ds=document.querySelector("[data-current-word-count]"),gs=document.querySelector("[data-scene-tracking-status]"),nh=document.querySelector("[data-document-message]"),th=document.querySelector("[data-sync-note]"),fu=document.querySelector("[data-document-outline]"),ki=document.querySelector("[data-analyse-manuscript-structure]"),we=document.querySelector("[data-apply-structure-sync]"),al=document.querySelector("[data-structure-preview-status]"),dr=document.querySelector("[data-structure-preview-results]"),or=document.querySelector("[data-refresh-plotdirector-scene]"),ih=document.querySelector("[data-plotdirector-scene-status]"),vl=document.querySelector("[data-anchor-status]"),yl=document.querySelector("[data-anchor-book-id]"),pl=document.querySelector("[data-anchor-chapter-id]"),wl=document.querySelector("[data-anchor-scene-id]"),ui=document.querySelector("[data-attach-plotdirector-ids]"),rh=document.querySelector("[data-plotdirector-scene-details]"),uh=document.querySelector("[data-plotdirector-scene-title]"),fh=document.querySelector("[data-plotdirector-revision-status]"),eh=document.querySelector("[data-plotdirector-estimated-words]"),be=document.querySelector("[data-plotdirector-actual-words]"),oh=document.querySelector("[data-plotdirector-blocked-status]"),ke=document.querySelector("[data-plotdirector-blocked-reason-row]"),de=document.querySelector("[data-plotdirector-blocked-reason]"),fi=document.querySelector("[data-sync-scene-progress]"),bl=document.querySelector("[data-sync-word-count]"),ge=document.querySelector("[data-sync-plotdirector-count]"),kl=document.querySelector("[data-sync-last-synced]"),dl=document.querySelector("[data-sync-status]"),sh=document.querySelector("[data-writing-brief-block]"),hh=document.querySelector("[data-writing-brief]"),ch=document.querySelector("[data-plotdirector-link-groups]"),eu=document.querySelector("[data-character-chips]"),ou=document.querySelector("[data-asset-chips]"),pw=document.querySelector("[data-location-chips]"),di=document.querySelector("[data-primary-location-select]"),ei=document.querySelector("[data-save-scene-links]"),gl=document.querySelector("[data-scene-links-status]"),lh=document.querySelector("[data-links-editing-scene]"),ah=document.querySelector("[data-chapter-create-link]"),na=document.querySelector("[data-chapter-create-link-chapter]"),ft=document.querySelector("[data-create-plotdirector-chapter]"),et=document.querySelector("[data-link-existing-chapter]"),ta=document.querySelector("[data-chapter-create-link-status]"),vh=document.querySelector("[data-scene-create-link]"),ia=document.querySelector("[data-create-link-chapter]"),ra=document.querySelector("[data-create-link-scene]"),ot=document.querySelector("[data-create-plotdirector-scene]"),st=document.querySelector("[data-link-existing-scene]"),ua=document.querySelector("[data-create-link-status]"),gi=document.querySelector("[data-link-existing-dialog]"),sf=document.querySelector("[data-link-scene-search]"),hf=document.querySelector("[data-link-scene-options]"),wt=document.querySelector("[data-link-selected-scene]"),fa=document.querySelector("[data-link-dialog-cancel]"),no=document.querySelector("[data-link-dialog-status]"),oi=document.querySelector("[data-create-scene-dialog]"),ea=document.querySelector("[data-create-dialog-scene]"),oa=document.querySelector("[data-create-dialog-chapter]"),sa=document.querySelector("[data-create-dialog-confirm]"),ha=document.querySelector("[data-create-dialog-cancel]"),nr=document.querySelector("[data-link-existing-chapter-dialog]"),cf=document.querySelector("[data-link-chapter-search]"),lf=document.querySelector("[data-link-chapter-options]"),bt=document.querySelector("[data-link-selected-chapter]"),ca=document.querySelector("[data-link-chapter-dialog-cancel]"),to=document.querySelector("[data-link-chapter-dialog-status]"),si=document.querySelector("[data-create-chapter-dialog]"),la=document.querySelector("[data-create-chapter-dialog-chapter]"),aa=document.querySelector("[data-create-chapter-dialog-book]"),va=document.querySelector("[data-create-chapter-dialog-confirm]"),ya=document.querySelector("[data-create-chapter-dialog-cancel]"),hi=document.querySelector("[data-apply-structure-sync-dialog]"),io=document.querySelector("[data-apply-structure-sync-summary]"),pa=document.querySelector("[data-apply-structure-sync-confirm]"),wa=document.querySelector("[data-apply-structure-sync-cancel]"),ba=document.querySelector("[data-resolve-project-id]"),ka=document.querySelector("[data-resolve-project-title]"),da=document.querySelector("[data-resolve-book-id]"),ga=document.querySelector("[data-resolve-book-title]"),nv=document.querySelector("[data-resolve-detected-chapter]"),tv=document.querySelector("[data-resolve-detected-scene]"),iv=document.querySelector("[data-resolve-request-chapter]"),rv=document.querySelector("[data-resolve-request-scene]"),uv=document.querySelector("[data-resolve-result]"),fv=document.querySelector("[data-resolve-chapter-id]"),ev=document.querySelector("[data-resolve-scene-id]"),sr=document.querySelector("[data-run-diagnostics]"),ov=document.querySelector("[data-diagnostic-host]"),sv=document.querySelector("[data-diagnostic-platform]"),hv=document.querySelector("[data-diagnostic-ready]"),cv=document.querySelector("[data-diagnostic-word-api]"),lv=document.querySelector("[data-diagnostic-last-scan]"),av=document.querySelector("[data-diagnostic-last-error]"),vv=document.querySelector("[data-diagnostic-anchor-type]"),yv=document.querySelector("[data-diagnostic-stored-scene-id]"),pv=document.querySelector("[data-diagnostic-stored-chapter-id]"),wv=document.querySelector("[data-diagnostic-resolution-method]"),bv=document.querySelector("[data-diagnostic-paragraphs]"),kv=document.querySelector("[data-diagnostic-heading1]"),dv=document.querySelector("[data-diagnostic-heading2]"),su=fl?.dataset.authenticated==="true";let ci=[],ct=[],kt=!1,rt=!1,af="Unknown",vf="Unknown",p="",nt="",hu=null,yf=null,a=null,w=null,r=null,e=null,cu="",li="Title Match",lt=!1,ro=[],lu=null,pf=null,uo=[],au=null,wf=null,dt=null,vu=null,tr=null,yu=null,ir=[],bf=!1,pu=!1,fo=!1,wu=!1,kf=null,eo="Manual",ht=!1,oo=null,so=!1,ho=!1,df="",co="",gt={characters:[],assets:[],locations:[],primaryLocationId:null};const lo=n=>{cs&&(cs.textContent=n)},gv=n=>{try{return window.localStorage.getItem(n)||""}catch{return""}},ny=(n,t)=>{try{t?window.localStorage.setItem(n,String(t)):window.localStorage.removeItem(n)}catch{}},yh=(n,t=true)=>{const i=es.has(n)&&le.some(t=>t.dataset.tabButton===n)?n:"scene";for(const n of le){const t=n.dataset.tabButton===i;n.classList.toggle("is-active",t);n.setAttribute("aria-selected",t?"true":"false")}for(const n of os){const t=n.dataset.tabPanel===i;n.classList.toggle("is-active",t);n.hidden=!t}t&&ny(f.activeTab,i)},ph=()=>{const n=gv(f.activeTab);yh(es.has(n)?n:"scene",!1)},gf=n=>{ls&&(ls.textContent=n),as&&(as.textContent=n)},ao=n=>{ps&&(ps.textContent=n)},bu=n=>{ws&&(ws.textContent=n)},t=n=>{nh&&(nh.textContent=n)},gr=n=>{ih&&(ih.textContent=n)},vo=t=>{const i=n=>Object.prototype.hasOwnProperty.call(t,n);i("anchorType")&&n(vv,o(t.anchorType));i("storedSceneId")&&n(yv,o(t.storedSceneId));i("storedChapterId")&&n(pv,o(t.storedChapterId));i("resolutionMethod")&&n(wv,o(t.resolutionMethod))},ai=()=>{const i=l(),u=Number.isInteger(r)&&r>0&&w===r,f=!Number.isInteger(e)||e<=0||a===e,t=u&&f;n(vl,t?"Attached to PlotDirector":"Not attached");n(yl,i?.bookId?String(i.bookId):"-");n(pl,Number.isInteger(e)&&e>0?String(e):"-");n(wl,Number.isInteger(r)&&r>0?String(r):"-");ui&&(ui.disabled=!r||t&&!lt,ui.textContent=w&&!t?"Reattach PlotDirector IDs":"Attach PlotDirector IDs");vo({anchorType:li||"Title Match",storedSceneId:w,storedChapterId:a,resolutionMethod:cu})},ku=(t,i,r,u=null,f=null,e=null)=>{p=t||"",nt=i||"",hu=Number.isInteger(r)?r:null,yf=Number.isInteger(e)&&e>0?e:null,a=Number.isInteger(u)&&u>0?u:null,w=Number.isInteger(f)&&f>0?f:null,bs&&(bs.textContent=t||"Not detected"),ks&&(ks.textContent=i||"Not detected"),ds&&(ds.textContent=Number.isInteger(r)?String(r):"-"),n(bl,Number.isInteger(r)?String(r):"-"),ai()},u=(n,t)=>{n&&(n.hidden=t)},nu=t=>{n(dl,t)},vi=t=>{n(al,t)},tu=(n="Select a project and book to analyse manuscript structure.")=>{if(dt=null,dr){dr.innerHTML="";const n=document.createElement("p");n.textContent="No preview generated.";dr.append(n)}vi(n);ne()},ty=(n=dt)=>sc.flatMap(t=>Array.isArray(n?.[t.key])?n[t.key]:[]),hr=(n=dt)=>ty(n).filter(n=>n.category==="couldLink"||n.category==="wouldCreateChapters"||n.category==="wouldCreateScenes"),ne=()=>{we&&(we.disabled=wu||hr().length===0)},te=()=>{ki&&(ki.disabled=wu||!iu()||!l()),ne()},yo=()=>{lh&&(lh.textContent=r?`Editing links for: ${df||nt||"Current scene"}`:"Link a PlotDirector scene first.")},wh=()=>{const n=ht||!r;fi&&(fi.disabled=n);ei&&(ei.disabled=n);di&&(di.disabled=n);for(const t of[eu,ou])for(const i of t?[...t.querySelectorAll("input[type='checkbox']")]:[])i.disabled=n},ni=t=>{(t==="Live"||t==="Manual")&&(eo=t),n(gs,t||eo||"Manual")},yi=(i,r="")=>{ht=!!i,n(gs,ht?"Stale":eo),wh(),r&&(t(r),cr(r))},rr=(n,t=null,i="")=>{r=Number.isInteger(n)&&n>0?n:null,e=Number.isInteger(t)&&t>0?t:null,cu=i||"",wh(),nu(ht?"Refresh Current Scene before syncing.":r?"Ready to sync.":"No resolved PlotDirector scene."),cr(ht?"Refresh Current Scene before saving.":r?"Ready to save.":"Link a PlotDirector scene first."),yo(),ai()},bh=n=>{ef&&(ef.disabled=n,ef.textContent=n?"Refreshing...":"Refresh Current Scene")},o=n=>n===null||n===undefined||n===""?"-":String(n),cr=t=>{n(gl,t)},lr=t=>{n(ua,t)},ti=t=>{n(ta,t)},po=()=>!!l()&&!!p&&!e,wo=()=>!!l()&&!!nt&&Number.isInteger(e)&&e>0,du=()=>{u(ah,!0),ti(""),ft&&(ft.disabled=!0,ft.textContent="Create Chapter in PlotDirector"),et&&(et.disabled=!0,et.textContent="Link to Existing Chapter")},ie=(t="")=>{n(na,o(p));u(ah,!1);ti(t);const i=po();ft&&(ft.disabled=!i,ft.textContent="Create Chapter in PlotDirector");et&&(et.disabled=!i,et.textContent="Link to Existing Chapter")},re=()=>{u(vh,!0),lr(""),ot&&(ot.disabled=!0,ot.textContent="Create Scene in PlotDirector"),st&&(st.disabled=!0,st.textContent="Link to Existing Scene")},gu=(t="")=>{n(ia,o(p));n(ra,o(nt));u(vh,!1);lr(t);const i=wo();ot&&(ot.disabled=!i,ot.textContent="Create Scene in PlotDirector");st&&(st.disabled=!i,st.textContent="Link to Existing Scene")},k=t=>{gr(t),u(rh,!0),u(sh,!0),u(ch,!1),du(),re(),cu="",df="",co="",li=w?"SceneID Anchor":a?"ChapterID Anchor":"Title Match",lt=!1,gt={characters:[],assets:[],locations:[],primaryLocationId:null},fe(eu,[],"character"),fe(ou,[],"asset"),kh([],null,!1),rr(null),n(ge,"-"),n(uh,"-"),n(fh,"-"),n(eh,"-"),n(be,"-"),n(oh,"-"),n(de,"-"),u(ke,!0),ai(),yo()},ue=(n,t)=>t==="asset"?n?.assetId||n?.AssetId||n?.storyAssetId||n?.StoryAssetId||0:t==="location"?n?.locationId||n?.LocationId||0:n?.characterId||n?.CharacterId||0,fe=(n,t,i,r=false)=>{if(n){n.innerHTML="";const u=Array.isArray(t)?t:[];if(u.length===0){const t=document.createElement("p");t.className="word-companion-empty-list";t.textContent="None";n.append(t);return}for(const t of u){const f=Number.parseInt(ue(t,i),10);if(Number.isInteger(f)&&!(f<=0)){const e=document.createElement("label");e.className="word-companion-check-item";const u=document.createElement("input");u.type="checkbox";u.value=String(f);u.checked=!!t.linked;u.disabled=!r;const o=document.createElement("span");o.textContent=t.name||"Unnamed";e.append(u,o);n.append(e)}}}},kh=(n,t,i=false)=>{if(di){di.innerHTML="";const r=document.createElement("option");r.value="";r.textContent="None";di.append(r);const u=Number.parseInt(t||"",10);for(const t of Array.isArray(n)?n:[]){const n=Number.parseInt(ue(t,"location"),10);if(Number.isInteger(n)&&!(n<=0)){const i=document.createElement("option");i.value=String(n);i.textContent=t.name||"Unnamed";i.selected=n===u;di.append(i)}}di.disabled=!i}},iy=t=>{df=t?.sceneTitle||nt||"",co=t?.chapterTitle||p||"",n(uh,o(t?.sceneTitle)),n(fh,o(t?.revisionStatus||t?.revisionStatusName||"Not provided")),n(eh,o(t?.estimatedWords)),n(be,o(t?.actualWords)),n(ge,o(t?.actualWords)),n(oh,t?.blocked?"Blocked":"Not blocked"),t?.blockedReason?(n(de,t.blockedReason),u(ke,!1)):(n(de,"-"),u(ke,!0)),hh&&(hh.textContent=t?.writingBrief||"No writing brief has been added for this scene."),gt={characters:Array.isArray(t?.characters)?t.characters:[],assets:Array.isArray(t?.assets)?t.assets:[],locations:Array.isArray(t?.locations)?t.locations:[],primaryLocationId:t?.primaryLocationId??null},fe(eu,gt.characters,"character",!!r&&!ht),fe(ou,gt.assets,"asset",!!r&&!ht),kh(gt.locations,gt.primaryLocationId,!!r&&!ht),yo(),cr(ht?"Refresh Current Scene before saving.":r?"Ready to save.":"No resolved PlotDirector scene."),u(rh,!1),u(sh,!1),u(ch,!1)},ar=n=>String(n||"").trim().replace(/\s+/g," "),b=(n,t)=>{const i=ar(n);if(!i)return"";const r=t==="scene"?"scene":"chapter",u=new RegExp(`^${r}\\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"),f=ar(i.replace(u,""));return f||i},ut=t=>{const i=n=>Object.prototype.hasOwnProperty.call(t,n);i("projectId")&&n(ba,o(t.projectId));i("projectTitle")&&n(ka,o(t.projectTitle));i("bookId")&&n(da,o(t.bookId));i("bookTitle")&&n(ga,o(t.bookTitle));i("detectedChapter")&&n(nv,o(t.detectedChapter));i("detectedScene")&&n(tv,o(t.detectedScene));i("requestChapter")&&n(iv,o(t.requestChapter));i("requestScene")&&n(rv,o(t.requestScene));i("result")&&n(uv,o(t.result));i("chapterId")&&n(fv,o(t.chapterId));i("sceneId")&&n(ev,o(t.sceneId))},ry=async(n,t)=>{const i=await pt(`/api/word-companion/books/${n}/structure`),r=Array.isArray(i?.chapters)?i.chapters:[];for(const n of r){const r=Array.isArray(n.scenes)?n.scenes:[],i=r.find(n=>n.sceneId===t);if(i)return i.revisionStatus||""}return""},uy=async(n,t)=>{const i=await pt(`/api/word-companion/books/${n}/structure`),r=Array.isArray(i?.chapters)?i.chapters:[];for(const n of r){const i=Array.isArray(n.scenes)?n.scenes:[];for(const r of i)if(t(n,r))return{chapter:n,scene:r}}return null},bo=async n=>{const t=await pt(`/api/word-companion/books/${n}/structure`);return Array.isArray(t?.chapters)?t.chapters:[]},dh=async n=>{const t=await pt(`/api/word-companion/books/${n}/chapters`);return Array.isArray(t?.chapters)?t.chapters:[]},gh=n=>{if(Number.isInteger(e)&&e>0){const t=n.find(n=>n.chapterId===e);if(t)return t}const t=b(p,"chapter").toLocaleLowerCase();return t?n.find(n=>b(n.title,"chapter").toLocaleLowerCase()===t)||null:null},nc=async()=>{const n=l();return n?gh(await bo(n.bookId)):null},ee=async(n,i,r,u,f)=>{const e=await pt(`/api/word-companion/scenes/${i}/companion`);try{e.revisionStatus=await ry(n,i)}catch{e.revisionStatus=""}li=f||"Title Match";lt=!1;yi(!1);gr("Linked to PlotDirector");t("");du();re();rr(i,r,u);iy(e)},n=(n,t)=>{n&&(n.textContent=t)},i=t=>{const i=n=>Object.prototype.hasOwnProperty.call(t,n);i("host")&&n(ov,t.host);i("platform")&&n(sv,t.platform);i("ready")&&n(hv,t.ready);i("wordApi")&&n(cv,t.wordApi);i("lastScan")&&n(lv,t.lastScan);i("lastError")&&n(av,t.lastError);i("paragraphs")&&n(bv,t.paragraphs);i("heading1")&&n(kv,t.heading1);i("heading2")&&n(dv,t.heading2)},s=n=>{const t=[];return n?.name&&t.push(n.name),n?.code&&n.code!==n.name&&t.push(n.code),n?.message&&t.push(n.message),n?.debugInfo?.errorLocation&&t.push(`Location: ${n.debugInfo.errorLocation}`),t.length>0?t.join(": "):String(n)},tc=async()=>{if(!window.Office||typeof window.Office.onReady!="function")return kt=!1,rt=!1,af="Unavailable",vf="Unavailable",i({host:af,platform:vf,ready:"No",wordApi:"No"}),null;const n=await window.Office.onReady();return kt=!0,af=n?.host||"Unknown",vf=n?.platform||"Unknown",rt=!!window.Word&&!!window.Office.HostType&&n?.host===window.Office.HostType.Word,i({host:af,platform:vf,ready:"Yes",wordApi:rt?"Yes":"No"}),n},ic=n=>{try{const i=window.localStorage.getItem(n),t=Number.parseInt(i||"",10);return Number.isInteger(t)&&t>0?t:null}catch{return null}},c=(n,t)=>{try{t?window.localStorage.setItem(n,String(t)):window.localStorage.removeItem(n)}catch{}},h=(n,t)=>{if(n){n.innerHTML="";const i=document.createElement("option");i.value="";i.textContent=t;n.append(i);n.disabled=!0}},oe=(n,t,i,r,u)=>{if(n){n.innerHTML="";const f=document.createElement("option");f.value="";f.textContent=t;n.append(f);for(const t of i){const i=document.createElement("option");i.value=String(t[r]);i.textContent=t[u];n.append(i)}n.disabled=i.length===0}},vr=n=>{const t=String(n?.title||"").trim(),i=String(n?.subtitle||"").trim();return i?`${t}: ${i}`:t},iu=()=>{const n=Number.parseInt(v?.value||"",10);return ci.find(t=>t.projectId===n)||null},l=()=>{const n=Number.parseInt(g?.value||"",10);return ct.find(t=>t.bookId===n)||null},yr=()=>{const n=Number.parseInt(tt?.value||"",10);return ci.find(t=>t.projectId===n)||null},ru=()=>{const n=Number.parseInt(y?.value||"",10);return ct.find(t=>t.bookId===n)||null},at=t=>n(hl,t),d=()=>{const n=yr(),t=ru();wi&&(wi.disabled=!n||!t||pu);uf&&(uf.disabled=!n||!String(uu?.value||"").trim()||pu);pe&&(pe.disabled=!tr?.canImport||!yu||pu);ve&&(ve.disabled=fo||uc().length===0)},pr=n=>{u(ol,!n);u(el,n);for(const t of os)u(t,n?!0:t.dataset.tabPanel!=="scene"&&!t.classList.contains("is-active"));n||ph()},rc=()=>{if(tt){if(ci.length===0){h(tt,"No projects available.");return}oe(tt,"Choose a project",ci,"projectId","title");const n=Number.parseInt(v?.value||"",10);Number.isInteger(n)&&n>0&&(tt.value=String(n))}},fy=(n=null)=>{if(y){if(ct.length===0){h(y,"No books available.");return}oe(y,"Choose a book",ct.map(n=>({...n,displayTitle:vr(n)})),"bookId","displayTitle");const i=n||Number.parseInt(g?.value||"",10),t=ct.find(n=>n.bookId===i);t&&(y.value=String(t.bookId));d()}},it=(t="Select a Project and Book to begin.")=>{tr=null,yu=null,ir=[],bf=!1,u(bi,!0),u(ye,!0),u(ae,!0),u(ff,!0),bi&&(bi.innerHTML=""),fr&&(fr.innerHTML=""),n(ur,""),at(t),d()},uc=()=>fr?[...fr.querySelectorAll("input[type='checkbox']:checked")].map(n=>String(n.value||"").trim()).filter(Boolean):[],ko=t=>{if(ir=Array.isArray(t)?t:[],ae&&fr){if(fr.innerHTML="",u(ae,!1),u(ff,!bf||ir.length===0),ir.length===0){n(ur,"No likely major characters found.");d();return}n(ur,`${ir.length} likely major characters found.`);for(const n of ir){const i=document.createElement("label");i.className="word-companion-character-candidate";const t=document.createElement("input");t.type="checkbox";t.value=n.text||"";t.checked=!0;t.addEventListener("change",d);const r=document.createElement("strong");r.textContent=n.text||"";const u=document.createElement("em");u.textContent=`${Number(n.mentionCount||0).toLocaleString()} mentions`;i.append(t,r,u);fr.append(i)}d()}},ey=n=>{if(bi){bi.innerHTML="";const t=document.createElement("strong");t.textContent="Manuscript Analysis";bi.append(t);const i=document.createElement("dl"),r=[["Project",n.projectTitle],["Book",vr({title:n.bookTitle,subtitle:n.bookSubtitle})],["Chapters",Number(n.chapterCount||0).toLocaleString()],["Scenes",Number(n.sceneCount||0).toLocaleString()],["Words",Number(n.wordCount||0).toLocaleString()]];for(const[n,t]of r){const r=document.createElement("dt");r.textContent=n;const u=document.createElement("dd");u.textContent=t||"-";i.append(r,u)}bi.append(i);u(bi,!1);u(ye,!1);at(n.message||"Preview generated.");d()}},oy=()=>{const n=window.Office?.context?.document?.settings;if(!n)return null;const u=String(n.get(pi.documentGuid)||"").trim(),t=Number.parseInt(n.get(pi.bookId)||"",10),i=Number.parseInt(n.get(pi.projectId)||"",10),r=Number.parseInt(n.get(pi.bindingVersion)||"",10);return!u||!Number.isInteger(t)||t<=0||!Number.isInteger(i)||i<=0?null:{documentGuid:u,bookId:t,projectId:i,bindingVersion:Number.isInteger(r)&&r>0?r:1}},sy=n=>new Promise((t,i)=>{const r=window.Office?.context?.document?.settings;if(!r){i(new Error("Word document settings are unavailable."));return}r.set(pi.documentGuid,n.documentGuid);r.set(pi.bookId,String(n.bookId));r.set(pi.projectId,String(n.projectId));r.set(pi.bindingVersion,String(n.bindingVersion||1));r.saveAsync(n=>{n.status===window.Office.AsyncResultStatus.Succeeded?t():i(n.error||new Error("Unable to save Word document metadata."))})}),fc=()=>vu?.documentGuid?vu.documentGuid:window.crypto?.randomUUID?window.crypto.randomUUID():"10000000-1000-4000-8000-100000000000".replace(/[018]/g,n=>(Number(n)^Math.random()*16>>Number(n)/4).toString(16)),ii=()=>{const t=iu(),n=l();vs&&(vs.textContent=t?.title||"(Not connected)");ys&&(ys.textContent=n?vr(n):"(Not connected)");th&&(th.textContent=n?"":"Select a PlotDirector book before refreshing.");te()},go=n=>{const t=n?.styleBuiltIn||n?.style||"";return String(t).replace(/\s+/g,"").toLowerCase()},nf=(n,t)=>{const i=go(n);return i===`heading${t}`||i.endsWith(`.heading${t}`)||i.includes(`heading${t}`)},ns=n=>{const t=String(n||"").trim().split(/\s+/).filter(Boolean);return t.length},hy=(n,t)=>String(n?.text||"").trim()===String(t?.text||"").trim()&&go(n)===go(t),ec=(n,t)=>{const r=String(n||"").match(new RegExp(`^${t}-(\\d+)$`,"i"));if(!r)return null;const i=Number.parseInt(r[1],10);return Number.isInteger(i)&&i>0?i:null},oc=(n,t)=>{const i=Array.isArray(n?.contentControls?.items)?n.contentControls.items:[];for(const n of i){const i=ec(n.tag,t);if(i)return i}return null},cy=(n,t)=>{const u=[];let o=0,s=0,i=null,r=null;n.forEach((n,t)=>{const f=String(n.text||"").trim();if(f){if(nf(n,1)){o+=1;i={index:u.length+1,paragraphIndex:t,title:f,anchorId:oc(n,"PD-CHAPTER"),scenes:[]};u.push(i);r=null;return}if(nf(n,2)){if(s+=1,!i)return;r={index:i.scenes.length+1,paragraphIndex:t,title:f,anchorId:oc(n,"PD-SCENE"),wordCount:0};i.scenes.push(r);return}r&&(r.wordCount+=ns(f))}});const h=t.find(n=>String(n.text||"").trim()),f=h?n.findIndex(n=>hy(n,h)):-1,e=f>=0?u.filter(n=>n.paragraphIndex<=f).at(-1)||null:null,c=e&&f>=0?e.scenes.filter(n=>n.paragraphIndex<=f).at(-1)||null:null;return{chapters:u,currentChapter:e,currentScene:c,paragraphCount:n.length,heading1Count:o,heading2Count:s}},ly=(n,t)=>{const r=[];let i=null,u=null,f=0;const e=[],s=new Set(["***","###"]),o=()=>{if(!i)return null;const n={title:`Scene ${i.scenes.length+1}`,sortOrder:(i.scenes.length+1)*10,wordCount:0};return i.scenes.push(n),n};return n.forEach(n=>{const h=String(n.text||"").trim();if(h){if(e.push(h),nf(n,1)){i={title:h,sortOrder:(r.length+1)*10,wordCount:0,scenes:[]};r.push(i);u=o();f+=ns(h);return}if(i&&u){if(t&&s.has(h)){u=o();return}const c=ns(h);i.wordCount+=c;u.wordCount+=c;f+=c}}}),{chapters:r,chapterCount:r.length,sceneCount:r.reduce((n,t)=>n+t.scenes.length,0),wordCount:f,documentText:e.join("\n")}},ts=n=>{if(fu){if(fu.innerHTML="",n.chapters.length===0){const n=document.createElement("p");n.textContent="No chapters detected. Use Heading 1 for chapter titles.";fu.append(n);return}const t=n.chapters.some(n=>n.scenes.length>0);if(!t){const n=document.createElement("p");n.textContent="No scenes detected. Use Heading 2 for scene titles.";fu.append(n)}for(const t of n.chapters){const n=document.createElement("div");n.className="word-companion-outline-chapter";const i=document.createElement("strong");if(i.textContent=t.title,n.append(i),t.scenes.length>0){const i=document.createElement("ul");for(const n of t.scenes){const t=document.createElement("li");t.textContent=n.title;i.append(t)}n.append(i)}fu.append(n)}}},sc=[{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"}],ay={alreadyLinked:"Already Linked",couldLink:"Could Link",wouldCreateChapter:"Would Create Chapter",wouldCreateScene:"Would Create Scene",manualReview:"Manual Review Required"},vy={alreadyLinked:"✓",couldLink:"~",wouldCreateChapter:"+",wouldCreateScene:"+",manualReview:"?"},se=(n,t)=>b(n,t).toLocaleLowerCase(),vt=({category:i,action:n,type:s,wordTitle:l,match:r="",anchorStatus:t="None",matches:u=[],pdChapter:e=null,pdScene:o=null,wordChapter:h=null,wordScene:c=null,parentItem:f=null})=>({id:`${s}-${h?.index||0}-${c?.index||0}-${h?.paragraphIndex??c?.paragraphIndex??0}`,category:i,action:n,type:s,wordTitle:l,match:r,anchorStatus:t,matches:u,pdChapter:e,pdScene:o,wordChapter:h,wordScene:c,parentItem:f,result:"Pending",error:""}),yt=(n,t)=>{Array.isArray(n[t.category])&&n[t.category].push(t)},is=n=>Array.isArray(n?.scenes)?n.scenes:[],yy=(n,t)=>{for(const i of n){const n=is(i).find(n=>n.sceneId===t);if(n)return{chapter:i,scene:n}}return null},py=(n,t,i)=>{const u=n.anchorId?`PD-CHAPTER-${n.anchorId}`:"None";if(n.anchorId){const r=t.find(t=>t.chapterId===n.anchorId);if(r){const t=vt({category:"alreadyLinked",action:"alreadyLinked",type:"Chapter",wordTitle:n.title,match:`Chapter ${r.chapterId}: ${r.title}`,anchorStatus:u,pdChapter:r,wordChapter:n});return yt(i,t),t}const f=vt({category:"manualReview",action:"manualReview",type:"Chapter",wordTitle:n.title,anchorStatus:`${u} not found`,matches:[],pdChapter:null,wordChapter:n});return yt(i,f),f}const r=t.filter(t=>se(t.title,"chapter")===se(n.title,"chapter"));if(r.length===1){const t=vt({category:"couldLink",action:"couldLink",type:"Chapter",wordTitle:n.title,match:`Chapter ${r[0].chapterId}: ${r[0].title}`,anchorStatus:u,pdChapter:r[0],wordChapter:n});return yt(i,t),t}if(r.length>1){const t=vt({category:"manualReview",action:"manualReview",type:"Chapter",wordTitle:n.title,anchorStatus:u,matches:r.map(n=>`Chapter ${n.chapterId}: ${n.title}`),pdChapter:null,wordChapter:n});return yt(i,t),t}const f=vt({category:"wouldCreateChapters",action:"wouldCreateChapter",type:"Chapter",wordTitle:n.title,anchorStatus:u,pdChapter:null,wordChapter:n});return yt(i,f),f},wy=(n,t,i,r)=>{const u=n.anchorId?`PD-SCENE-${n.anchorId}`:"None";if(n.anchorId){const f=yy(i,n.anchorId);if(f){yt(r,vt({category:"alreadyLinked",action:"alreadyLinked",type:"Scene",wordTitle:n.title,match:`Scene ${f.scene.sceneId}: ${f.scene.title}`,anchorStatus:u,pdChapter:f.chapter,pdScene:f.scene,wordChapter:t?.wordChapter,wordScene:n,parentItem:t}));return}yt(r,vt({category:"manualReview",action:"manualReview",type:"Scene",wordTitle:n.title,anchorStatus:`${u} not found`,wordChapter:t?.wordChapter,wordScene:n,parentItem:t}));return}if(!t?.pdChapter&&t?.category==="manualReview"){yt(r,vt({category:"manualReview",action:"manualReview",type:"Scene",wordTitle:n.title,anchorStatus:u,matches:["Parent chapter requires manual review."],wordChapter:t?.wordChapter,wordScene:n,parentItem:t}));return}if(!t?.pdChapter){yt(r,vt({category:"wouldCreateScenes",action:"wouldCreateScene",type:"Scene",wordTitle:n.title,anchorStatus:u,wordChapter:t?.wordChapter,wordScene:n,parentItem:t}));return}const f=is(t.pdChapter).filter(t=>se(t.title,"scene")===se(n.title,"scene"));if(f.length===1){yt(r,vt({category:"couldLink",action:"couldLink",type:"Scene",wordTitle:n.title,match:`Scene ${f[0].sceneId}: ${f[0].title}`,anchorStatus:u,pdChapter:t.pdChapter,pdScene:f[0],wordChapter:t.wordChapter,wordScene:n,parentItem:t}));return}if(f.length>1){yt(r,vt({category:"manualReview",action:"manualReview",type:"Scene",wordTitle:n.title,anchorStatus:u,matches:f.map(n=>`Scene ${n.sceneId}: ${n.title}`),wordChapter:t.wordChapter,wordScene:n,parentItem:t}));return}yt(r,vt({category:"wouldCreateScenes",action:"wouldCreateScene",type:"Scene",wordTitle:n.title,anchorStatus:u,pdChapter:t.pdChapter,wordChapter:t.wordChapter,wordScene:n,parentItem:t}))},by=(n,t)=>{const i={alreadyLinked:[],couldLink:[],wouldCreateChapters:[],wouldCreateScenes:[],manualReview:[]},r=Array.isArray(t?.chapters)?t.chapters:[];for(const t of Array.isArray(n?.chapters)?n.chapters:[]){const n=py(t,r,i);for(const u of is(t))wy(u,n,r,i)}return i},ky=n=>{const t=document.createElement("article");t.className="word-companion-preview-item";const i=document.createElement("strong");i.textContent=`${vy[n.action]||""} ${n.type}: ${n.wordTitle}`;t.append(i);const r=document.createElement("span");if(r.textContent=`Anchor: ${n.anchorStatus||"None"}`,t.append(r),n.match){const i=document.createElement("span");i.textContent=`Match: ${n.match}`;t.append(i)}if(Array.isArray(n.matches)&&n.matches.length>0){const i=document.createElement("div");i.className="word-companion-preview-matches";const r=document.createElement("span");r.textContent="Matches:";i.append(r);const u=document.createElement("ul");for(const t of n.matches){const n=document.createElement("li");n.textContent=t;u.append(n)}i.append(u);t.append(i)}const u=document.createElement("span");if(u.textContent=`Action: ${ay[n.action]||n.action}`,t.append(u),n.result&&n.result!=="Pending"){const i=document.createElement("span");i.textContent=n.error?`Result: ${n.result} - ${n.error}`:`Result: ${n.result}`;t.append(i)}return t},hc=n=>{if(dr){dr.innerHTML="";for(const t of sc){const i=document.createElement("section");i.className="word-companion-preview-group";const r=document.createElement("strong");r.textContent=t.title;i.append(r);const u=Array.isArray(n?.[t.key])?n[t.key]:[];if(u.length===0){const n=document.createElement("p");n.className="word-companion-empty-list";n.textContent="None";i.append(n)}else for(const n of u)i.append(ky(n));dr.append(i)}ne()}},wr=async n=>{const t=n.document.body.paragraphs,i=n.document.getSelection().paragraphs;t.load("text,styleBuiltIn,style");i.load("text,styleBuiltIn,style");await n.sync();for(const n of t.items)(nf(n,1)||nf(n,2))&&n.contentControls.load("items/tag,title");await n.sync();const r=cy(t.items,i.items);return r.bodyParagraphs=t.items,r},cc=async()=>{if(t(""),!kt||!rt||!window.Word||typeof window.Word.run!="function")return ku(null,null,null),t("Word document access is unavailable."),i({lastScan:"Failure",lastError:"Word document access is unavailable."}),!1;er&&(er.disabled=!0,er.textContent="Scanning...");t("Scanning document structure...");try{const n=await window.Word.run(async n=>await wr(n));return ku(n.currentChapter?.title,n.currentScene?.title,n.currentScene?.wordCount,n.currentChapter?.anchorId,n.currentScene?.anchorId,n.currentChapter?.index),k("Not detected"),ts(n),t(""),i({lastScan:"Success",lastError:"-",paragraphs:String(n.paragraphCount),heading1:String(n.heading1Count),heading2:String(n.heading2Count)}),!0}catch(n){const r=s(n);return console.error("Unable to scan the Word document.",n),ku(null,null,null),t(`Unable to scan the Word document: ${r}`),i({lastScan:"Failure",lastError:r}),!1}finally{er&&(er.disabled=!1,er.textContent="Refresh Document Structure")}},lc=async()=>{const n=l();if(!iu()||!n)return vi("Select a project and book to analyse manuscript structure."),!1;if(!kt||!rt||!window.Word||typeof window.Word.run!="function")return vi("Word document access is unavailable."),i({lastScan:"Failure",lastError:"Word document access is unavailable."}),!1;ki&&(ki.disabled=!0,ki.textContent="Analysing...");vi("Analysing manuscript structure...");try{const t=await window.Word.run(async n=>await wr(n)),r=await pt(`/api/word-companion/books/${n.bookId}/structure`);return ku(t.currentChapter?.title,t.currentScene?.title,t.currentScene?.wordCount,t.currentChapter?.anchorId,t.currentScene?.anchorId,t.currentChapter?.index),ts(t),dt=by(t,r),hc(dt),vi("Preview generated. No changes have been applied."),i({lastScan:"Success",lastError:"-",paragraphs:String(t.paragraphCount),heading1:String(t.heading1Count),heading2:String(t.heading2Count)}),!0}catch(t){return console.error("Unable to analyse manuscript structure.",t),vi("Unable to analyse manuscript structure."),i({lastScan:"Failure",lastError:s(t)}),!1}finally{ki&&(ki.textContent="Analyse Manuscript Structure",te())}},dy=(n=dt)=>{const t=hr(n),i=t.filter(n=>n.category==="couldLink"&&n.type==="Chapter").length,r=t.filter(n=>n.category==="couldLink"&&n.type==="Scene").length,u=t.filter(n=>n.category==="wouldCreateChapters").length,f=t.filter(n=>n.category==="wouldCreateScenes").length,e=Array.isArray(n?.manualReview)?n.manualReview.length:0;return{linkChapters:i,linkScenes:r,createChapters:u,createScenes:f,manualReview:e}},gy=n=>{if(io){io.innerHTML="";const i=[`Link ${n.linkChapters+n.linkScenes} chapters/scenes`,`Create ${n.createChapters} chapters`,`Create ${n.createScenes} scenes`],t=document.createElement("ul");for(const n of i){const i=document.createElement("li");i.textContent=n;t.append(i)}io.append(t)}},rs=n=>{kf&&(kf(n),kf=null),hi?.close?hi.close():hi&&(hi.hidden=!0)},np=()=>{const n=dy();return(gy(n),!hi)?Promise.resolve(window.confirm(`Apply Structure Sync? +(()=>{const f={projectId:"plotdirector.word.projectId",bookId:"plotdirector.word.bookId",activeTab:"plotdirector.word.activeTab"},pi={documentGuid:"plotdirector.word.documentGuid",bookId:"plotdirector.word.boundBookId",projectId:"plotdirector.word.boundProjectId",bindingVersion:"plotdirector.word.bindingVersion"},es=new Set(["scene","links","structure","diagnostics"]),fl=document.querySelector(".word-companion-shell"),el=document.querySelector(".word-companion-tabs"),le=[...document.querySelectorAll("[data-tab-button]")],os=[...document.querySelectorAll("[data-tab-panel]")],ol=document.querySelector("[data-first-run-wizard]"),tt=document.querySelector("[data-first-run-project-select]"),y=document.querySelector("[data-first-run-book-select]"),uu=document.querySelector("[data-first-run-new-book-title]"),uf=document.querySelector("[data-first-run-create-book]"),wi=document.querySelector("[data-first-run-scan]"),sl=document.querySelector("[data-first-run-cancel]"),hl=document.querySelector("[data-first-run-status]"),bi=document.querySelector("[data-first-run-summary]"),ae=document.querySelector("[data-first-run-character-section]"),ur=document.querySelector("[data-first-run-character-status]"),fr=document.querySelector("[data-first-run-character-candidates]"),ff=document.querySelector("[data-first-run-character-actions]"),ve=document.querySelector("[data-first-run-create-characters]"),cl=document.querySelector("[data-first-run-skip-characters]"),ye=document.querySelector("[data-first-run-import-actions]"),pe=document.querySelector("[data-first-run-import]"),ll=document.querySelector("[data-first-run-import-cancel]"),kr=document.querySelector("[data-first-run-replace-dialog]"),ss=document.querySelector("[data-first-run-replace-confirm]"),hs=document.querySelector("[data-first-run-replace-cancel]"),cs=document.querySelector("[data-office-status]"),ls=document.querySelector("[data-connection-status]"),as=document.querySelector("[data-summary-connection]"),vs=document.querySelector("[data-summary-project]"),ys=document.querySelector("[data-summary-book]"),v=document.querySelector("[data-project-select]"),g=document.querySelector("[data-book-select]"),ps=document.querySelector("[data-project-message]"),ws=document.querySelector("[data-book-message]"),ef=document.querySelector("[data-refresh-current-scene]"),er=document.querySelector("[data-refresh-document]"),bs=document.querySelector("[data-current-chapter]"),ks=document.querySelector("[data-current-scene]"),ds=document.querySelector("[data-current-word-count]"),gs=document.querySelector("[data-scene-tracking-status]"),nh=document.querySelector("[data-document-message]"),th=document.querySelector("[data-sync-note]"),fu=document.querySelector("[data-document-outline]"),ki=document.querySelector("[data-analyse-manuscript-structure]"),we=document.querySelector("[data-apply-structure-sync]"),al=document.querySelector("[data-structure-preview-status]"),dr=document.querySelector("[data-structure-preview-results]"),or=document.querySelector("[data-refresh-plotdirector-scene]"),ih=document.querySelector("[data-plotdirector-scene-status]"),vl=document.querySelector("[data-anchor-status]"),yl=document.querySelector("[data-anchor-book-id]"),pl=document.querySelector("[data-anchor-chapter-id]"),wl=document.querySelector("[data-anchor-scene-id]"),ui=document.querySelector("[data-attach-plotdirector-ids]"),rh=document.querySelector("[data-plotdirector-scene-details]"),uh=document.querySelector("[data-plotdirector-scene-title]"),fh=document.querySelector("[data-plotdirector-revision-status]"),eh=document.querySelector("[data-plotdirector-estimated-words]"),be=document.querySelector("[data-plotdirector-actual-words]"),oh=document.querySelector("[data-plotdirector-blocked-status]"),ke=document.querySelector("[data-plotdirector-blocked-reason-row]"),de=document.querySelector("[data-plotdirector-blocked-reason]"),fi=document.querySelector("[data-sync-scene-progress]"),bl=document.querySelector("[data-sync-word-count]"),ge=document.querySelector("[data-sync-plotdirector-count]"),kl=document.querySelector("[data-sync-last-synced]"),dl=document.querySelector("[data-sync-status]"),sh=document.querySelector("[data-writing-brief-block]"),hh=document.querySelector("[data-writing-brief]"),ch=document.querySelector("[data-plotdirector-link-groups]"),eu=document.querySelector("[data-character-chips]"),ou=document.querySelector("[data-asset-chips]"),pw=document.querySelector("[data-location-chips]"),di=document.querySelector("[data-primary-location-select]"),ei=document.querySelector("[data-save-scene-links]"),gl=document.querySelector("[data-scene-links-status]"),lh=document.querySelector("[data-links-editing-scene]"),ah=document.querySelector("[data-chapter-create-link]"),na=document.querySelector("[data-chapter-create-link-chapter]"),ft=document.querySelector("[data-create-plotdirector-chapter]"),et=document.querySelector("[data-link-existing-chapter]"),ta=document.querySelector("[data-chapter-create-link-status]"),vh=document.querySelector("[data-scene-create-link]"),ia=document.querySelector("[data-create-link-chapter]"),ra=document.querySelector("[data-create-link-scene]"),ot=document.querySelector("[data-create-plotdirector-scene]"),st=document.querySelector("[data-link-existing-scene]"),ua=document.querySelector("[data-create-link-status]"),gi=document.querySelector("[data-link-existing-dialog]"),sf=document.querySelector("[data-link-scene-search]"),hf=document.querySelector("[data-link-scene-options]"),wt=document.querySelector("[data-link-selected-scene]"),fa=document.querySelector("[data-link-dialog-cancel]"),no=document.querySelector("[data-link-dialog-status]"),oi=document.querySelector("[data-create-scene-dialog]"),ea=document.querySelector("[data-create-dialog-scene]"),oa=document.querySelector("[data-create-dialog-chapter]"),sa=document.querySelector("[data-create-dialog-confirm]"),ha=document.querySelector("[data-create-dialog-cancel]"),nr=document.querySelector("[data-link-existing-chapter-dialog]"),cf=document.querySelector("[data-link-chapter-search]"),lf=document.querySelector("[data-link-chapter-options]"),bt=document.querySelector("[data-link-selected-chapter]"),ca=document.querySelector("[data-link-chapter-dialog-cancel]"),to=document.querySelector("[data-link-chapter-dialog-status]"),si=document.querySelector("[data-create-chapter-dialog]"),la=document.querySelector("[data-create-chapter-dialog-chapter]"),aa=document.querySelector("[data-create-chapter-dialog-book]"),va=document.querySelector("[data-create-chapter-dialog-confirm]"),ya=document.querySelector("[data-create-chapter-dialog-cancel]"),hi=document.querySelector("[data-apply-structure-sync-dialog]"),io=document.querySelector("[data-apply-structure-sync-summary]"),pa=document.querySelector("[data-apply-structure-sync-confirm]"),wa=document.querySelector("[data-apply-structure-sync-cancel]"),ba=document.querySelector("[data-resolve-project-id]"),ka=document.querySelector("[data-resolve-project-title]"),da=document.querySelector("[data-resolve-book-id]"),ga=document.querySelector("[data-resolve-book-title]"),nv=document.querySelector("[data-resolve-detected-chapter]"),tv=document.querySelector("[data-resolve-detected-scene]"),iv=document.querySelector("[data-resolve-request-chapter]"),rv=document.querySelector("[data-resolve-request-scene]"),uv=document.querySelector("[data-resolve-result]"),fv=document.querySelector("[data-resolve-chapter-id]"),ev=document.querySelector("[data-resolve-scene-id]"),sr=document.querySelector("[data-run-diagnostics]"),ov=document.querySelector("[data-diagnostic-host]"),sv=document.querySelector("[data-diagnostic-platform]"),hv=document.querySelector("[data-diagnostic-ready]"),cv=document.querySelector("[data-diagnostic-word-api]"),lv=document.querySelector("[data-diagnostic-last-scan]"),av=document.querySelector("[data-diagnostic-last-error]"),vv=document.querySelector("[data-diagnostic-anchor-type]"),yv=document.querySelector("[data-diagnostic-stored-scene-id]"),pv=document.querySelector("[data-diagnostic-stored-chapter-id]"),wv=document.querySelector("[data-diagnostic-resolution-method]"),bv=document.querySelector("[data-diagnostic-paragraphs]"),kv=document.querySelector("[data-diagnostic-heading1]"),dv=document.querySelector("[data-diagnostic-heading2]"),su=fl?.dataset.authenticated==="true";let ci=[],ct=[],kt=!1,rt=!1,af="Unknown",vf="Unknown",p="",nt="",hu=null,yf=null,a=null,w=null,r=null,e=null,cu="",li="Title Match",lt=!1,ro=[],lu=null,pf=null,uo=[],au=null,wf=null,dt=null,vu=null,tr=null,yu=null,ir=[],bf=!1,pu=!1,fo=!1,wu=!1,kf=null,eo="Manual",ht=!1,oo=null,so=!1,ho=!1,df="",co="",gt={characters:[],assets:[],locations:[],primaryLocationId:null};const lo=n=>{cs&&(cs.textContent=n)},gv=n=>{try{return window.localStorage.getItem(n)||""}catch{return""}},ny=(n,t)=>{try{t?window.localStorage.setItem(n,String(t)):window.localStorage.removeItem(n)}catch{}},yh=(n,t=true)=>{const i=es.has(n)&&le.some(t=>t.dataset.tabButton===n)?n:"scene";for(const n of le){const t=n.dataset.tabButton===i;n.classList.toggle("is-active",t);n.setAttribute("aria-selected",t?"true":"false")}for(const n of os){const t=n.dataset.tabPanel===i;n.classList.toggle("is-active",t);n.hidden=!t}t&&ny(f.activeTab,i)},ph=()=>{const n=gv(f.activeTab);yh(es.has(n)?n:"scene",!1)},gf=n=>{ls&&(ls.textContent=n),as&&(as.textContent=n)},ao=n=>{ps&&(ps.textContent=n)},bu=n=>{ws&&(ws.textContent=n)},t=n=>{nh&&(nh.textContent=n)},gr=n=>{ih&&(ih.textContent=n)},vo=t=>{const i=n=>Object.prototype.hasOwnProperty.call(t,n);i("anchorType")&&n(vv,o(t.anchorType));i("storedSceneId")&&n(yv,o(t.storedSceneId));i("storedChapterId")&&n(pv,o(t.storedChapterId));i("resolutionMethod")&&n(wv,o(t.resolutionMethod))},ai=()=>{const i=l(),u=Number.isInteger(r)&&r>0&&w===r,f=!Number.isInteger(e)||e<=0||a===e,t=u&&f;n(vl,t?"Attached to PlotDirector":"Not attached");n(yl,i?.bookId?String(i.bookId):"-");n(pl,Number.isInteger(e)&&e>0?String(e):"-");n(wl,Number.isInteger(r)&&r>0?String(r):"-");ui&&(ui.disabled=!r||t&&!lt,ui.textContent=w&&!t?"Reattach PlotDirector IDs":"Attach PlotDirector IDs");vo({anchorType:li||"Title Match",storedSceneId:w,storedChapterId:a,resolutionMethod:cu})},ku=(t,i,r,u=null,f=null,e=null)=>{p=t||"",nt=i||"",hu=Number.isInteger(r)?r:null,yf=Number.isInteger(e)&&e>0?e:null,a=Number.isInteger(u)&&u>0?u:null,w=Number.isInteger(f)&&f>0?f:null,bs&&(bs.textContent=t||"Not detected"),ks&&(ks.textContent=i||"Not detected"),ds&&(ds.textContent=Number.isInteger(r)?String(r):"-"),n(bl,Number.isInteger(r)?String(r):"-"),ai()},u=(n,t)=>{n&&(n.hidden=t)},nu=t=>{n(dl,t)},vi=t=>{n(al,t)},tu=(n="Select a project and book to analyse manuscript structure.")=>{if(dt=null,dr){dr.innerHTML="";const n=document.createElement("p");n.textContent="No preview generated.";dr.append(n)}vi(n);ne()},ty=(n=dt)=>sc.flatMap(t=>Array.isArray(n?.[t.key])?n[t.key]:[]),hr=(n=dt)=>ty(n).filter(n=>n.category==="couldLink"||n.category==="wouldCreateChapters"||n.category==="wouldCreateScenes"),ne=()=>{we&&(we.disabled=wu||hr().length===0)},te=()=>{ki&&(ki.disabled=wu||!iu()||!l()),ne()},yo=()=>{lh&&(lh.textContent=r?`Editing links for: ${df||nt||"Current scene"}`:"Link a PlotDirector scene first.")},wh=()=>{const n=ht||!r;fi&&(fi.disabled=n);ei&&(ei.disabled=n);di&&(di.disabled=n);for(const t of[eu,ou])for(const i of t?[...t.querySelectorAll("input[type='checkbox']")]:[])i.disabled=n},ni=t=>{(t==="Live"||t==="Manual")&&(eo=t),n(gs,t||eo||"Manual")},yi=(i,r="")=>{ht=!!i,n(gs,ht?"Stale":eo),wh(),r&&(t(r),cr(r))},rr=(n,t=null,i="")=>{r=Number.isInteger(n)&&n>0?n:null,e=Number.isInteger(t)&&t>0?t:null,cu=i||"",wh(),nu(ht?"Refresh Current Scene before syncing.":r?"Ready to sync.":"No resolved PlotDirector scene."),cr(ht?"Refresh Current Scene before saving.":r?"Ready to save.":"Link a PlotDirector scene first."),yo(),ai()},bh=n=>{ef&&(ef.disabled=n,ef.textContent=n?"Refreshing...":"Refresh Current Scene")},o=n=>n===null||n===undefined||n===""?"-":String(n),cr=t=>{n(gl,t)},lr=t=>{n(ua,t)},ti=t=>{n(ta,t)},po=()=>!!l()&&!!p&&!e,wo=()=>!!l()&&!!nt&&Number.isInteger(e)&&e>0,du=()=>{u(ah,!0),ti(""),ft&&(ft.disabled=!0,ft.textContent="Create Chapter in PlotDirector"),et&&(et.disabled=!0,et.textContent="Link to Existing Chapter")},ie=(t="")=>{n(na,o(p));u(ah,!1);ti(t);const i=po();ft&&(ft.disabled=!i,ft.textContent="Create Chapter in PlotDirector");et&&(et.disabled=!i,et.textContent="Link to Existing Chapter")},re=()=>{u(vh,!0),lr(""),ot&&(ot.disabled=!0,ot.textContent="Create Scene in PlotDirector"),st&&(st.disabled=!0,st.textContent="Link to Existing Scene")},gu=(t="")=>{n(ia,o(p));n(ra,o(nt));u(vh,!1);lr(t);const i=wo();ot&&(ot.disabled=!i,ot.textContent="Create Scene in PlotDirector");st&&(st.disabled=!i,st.textContent="Link to Existing Scene")},k=t=>{gr(t),u(rh,!0),u(sh,!0),u(ch,!1),du(),re(),cu="",df="",co="",li=w?"SceneID Anchor":a?"ChapterID Anchor":"Title Match",lt=!1,gt={characters:[],assets:[],locations:[],primaryLocationId:null},fe(eu,[],"character"),fe(ou,[],"asset"),kh([],null,!1),rr(null),n(ge,"-"),n(uh,"-"),n(fh,"-"),n(eh,"-"),n(be,"-"),n(oh,"-"),n(de,"-"),u(ke,!0),ai(),yo()},ue=(n,t)=>t==="asset"?n?.assetId||n?.AssetId||n?.storyAssetId||n?.StoryAssetId||0:t==="location"?n?.locationId||n?.LocationId||0:n?.characterId||n?.CharacterId||0,fe=(n,t,i,r=false)=>{if(n){n.innerHTML="";const u=Array.isArray(t)?t:[];if(u.length===0){const t=document.createElement("p");t.className="word-companion-empty-list";t.textContent="None";n.append(t);return}for(const t of u){const f=Number.parseInt(ue(t,i),10);if(Number.isInteger(f)&&!(f<=0)){const e=document.createElement("label");e.className="word-companion-check-item";const u=document.createElement("input");u.type="checkbox";u.value=String(f);u.checked=!!t.linked;u.disabled=!r;const o=document.createElement("span");o.textContent=t.name||"Unnamed";e.append(u,o);n.append(e)}}}},kh=(n,t,i=false)=>{if(di){di.innerHTML="";const r=document.createElement("option");r.value="";r.textContent="None";di.append(r);const u=Number.parseInt(t||"",10);for(const t of Array.isArray(n)?n:[]){const n=Number.parseInt(ue(t,"location"),10);if(Number.isInteger(n)&&!(n<=0)){const i=document.createElement("option");i.value=String(n);i.textContent=t.name||"Unnamed";i.selected=n===u;di.append(i)}}di.disabled=!i}},iy=t=>{df=t?.sceneTitle||nt||"",co=t?.chapterTitle||p||"",n(uh,o(t?.sceneTitle)),n(fh,o(t?.revisionStatus||t?.revisionStatusName||"Not provided")),n(eh,o(t?.estimatedWords)),n(be,o(t?.actualWords)),n(ge,o(t?.actualWords)),n(oh,t?.blocked?"Blocked":"Not blocked"),t?.blockedReason?(n(de,t.blockedReason),u(ke,!1)):(n(de,"-"),u(ke,!0)),hh&&(hh.textContent=t?.writingBrief||"No writing brief has been added for this scene."),gt={characters:Array.isArray(t?.characters)?t.characters:[],assets:Array.isArray(t?.assets)?t.assets:[],locations:Array.isArray(t?.locations)?t.locations:[],primaryLocationId:t?.primaryLocationId??null},fe(eu,gt.characters,"character",!!r&&!ht),fe(ou,gt.assets,"asset",!!r&&!ht),kh(gt.locations,gt.primaryLocationId,!!r&&!ht),yo(),cr(ht?"Refresh Current Scene before saving.":r?"Ready to save.":"No resolved PlotDirector scene."),u(rh,!1),u(sh,!1),u(ch,!1)},ar=n=>String(n||"").trim().replace(/\s+/g," "),b=(n,t)=>{const i=ar(n);if(!i)return"";const r=t==="scene"?"scene":"chapter",u=new RegExp(`^${r}\\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"),f=ar(i.replace(u,""));return f||i},ut=t=>{const i=n=>Object.prototype.hasOwnProperty.call(t,n);i("projectId")&&n(ba,o(t.projectId));i("projectTitle")&&n(ka,o(t.projectTitle));i("bookId")&&n(da,o(t.bookId));i("bookTitle")&&n(ga,o(t.bookTitle));i("detectedChapter")&&n(nv,o(t.detectedChapter));i("detectedScene")&&n(tv,o(t.detectedScene));i("requestChapter")&&n(iv,o(t.requestChapter));i("requestScene")&&n(rv,o(t.requestScene));i("result")&&n(uv,o(t.result));i("chapterId")&&n(fv,o(t.chapterId));i("sceneId")&&n(ev,o(t.sceneId))},ry=async(n,t)=>{const i=await pt(`/api/word-companion/books/${n}/structure`),r=Array.isArray(i?.chapters)?i.chapters:[];for(const n of r){const r=Array.isArray(n.scenes)?n.scenes:[],i=r.find(n=>n.sceneId===t);if(i)return i.revisionStatus||""}return""},uy=async(n,t)=>{const i=await pt(`/api/word-companion/books/${n}/structure`),r=Array.isArray(i?.chapters)?i.chapters:[];for(const n of r){const i=Array.isArray(n.scenes)?n.scenes:[];for(const r of i)if(t(n,r))return{chapter:n,scene:r}}return null},bo=async n=>{const t=await pt(`/api/word-companion/books/${n}/structure`);return Array.isArray(t?.chapters)?t.chapters:[]},dh=async n=>{const t=await pt(`/api/word-companion/books/${n}/chapters`);return Array.isArray(t?.chapters)?t.chapters:[]},gh=n=>{if(Number.isInteger(e)&&e>0){const t=n.find(n=>n.chapterId===e);if(t)return t}const t=b(p,"chapter").toLocaleLowerCase();return t?n.find(n=>b(n.title,"chapter").toLocaleLowerCase()===t)||null:null},nc=async()=>{const n=l();return n?gh(await bo(n.bookId)):null},ee=async(n,i,r,u,f)=>{const e=await pt(`/api/word-companion/scenes/${i}/companion`);try{e.revisionStatus=await ry(n,i)}catch{e.revisionStatus=""}li=f||"Title Match";lt=!1;yi(!1);gr("Linked to PlotDirector");t("");du();re();rr(i,r,u);iy(e)},n=(n,t)=>{n&&(n.textContent=t)},i=t=>{const i=n=>Object.prototype.hasOwnProperty.call(t,n);i("host")&&n(ov,t.host);i("platform")&&n(sv,t.platform);i("ready")&&n(hv,t.ready);i("wordApi")&&n(cv,t.wordApi);i("lastScan")&&n(lv,t.lastScan);i("lastError")&&n(av,t.lastError);i("paragraphs")&&n(bv,t.paragraphs);i("heading1")&&n(kv,t.heading1);i("heading2")&&n(dv,t.heading2)},s=n=>{const t=[];return n?.name&&t.push(n.name),n?.code&&n.code!==n.name&&t.push(n.code),n?.message&&t.push(n.message),n?.debugInfo?.errorLocation&&t.push(`Location: ${n.debugInfo.errorLocation}`),t.length>0?t.join(": "):String(n)},tc=async()=>{if(!window.Office||typeof window.Office.onReady!="function")return kt=!1,rt=!1,af="Unavailable",vf="Unavailable",i({host:af,platform:vf,ready:"No",wordApi:"No"}),null;const n=await window.Office.onReady();return kt=!0,af=n?.host||"Unknown",vf=n?.platform||"Unknown",rt=!!window.Word&&!!window.Office.HostType&&n?.host===window.Office.HostType.Word,i({host:af,platform:vf,ready:"Yes",wordApi:rt?"Yes":"No"}),n},ic=n=>{try{const i=window.localStorage.getItem(n),t=Number.parseInt(i||"",10);return Number.isInteger(t)&&t>0?t:null}catch{return null}},c=(n,t)=>{try{t?window.localStorage.setItem(n,String(t)):window.localStorage.removeItem(n)}catch{}},h=(n,t)=>{if(n){n.innerHTML="";const i=document.createElement("option");i.value="";i.textContent=t;n.append(i);n.disabled=!0}},oe=(n,t,i,r,u)=>{if(n){n.innerHTML="";const f=document.createElement("option");f.value="";f.textContent=t;n.append(f);for(const t of i){const i=document.createElement("option");i.value=String(t[r]);i.textContent=t[u];n.append(i)}n.disabled=i.length===0}},vr=n=>{const t=String(n?.title||"").trim(),i=String(n?.subtitle||"").trim();return i?`${t}: ${i}`:t},iu=()=>{const n=Number.parseInt(v?.value||"",10);return ci.find(t=>t.projectId===n)||null},l=()=>{const n=Number.parseInt(g?.value||"",10);return ct.find(t=>t.bookId===n)||null},yr=()=>{const n=Number.parseInt(tt?.value||"",10);return ci.find(t=>t.projectId===n)||null},ru=()=>{const n=Number.parseInt(y?.value||"",10);return ct.find(t=>t.bookId===n)||null},at=t=>n(hl,t),d=()=>{const n=yr(),t=ru();wi&&(wi.disabled=!n||!t||pu);uf&&(uf.disabled=!n||!String(uu?.value||"").trim()||pu);pe&&(pe.disabled=!tr?.canImport||!yu||pu);ve&&(ve.disabled=fo||uc().length===0)},pr=n=>{u(ol,!n);u(el,n);for(const t of os)u(t,n?!0:t.dataset.tabPanel!=="scene"&&!t.classList.contains("is-active"));n||ph()},rc=()=>{if(tt){if(ci.length===0){h(tt,"No projects available.");return}oe(tt,"Choose a project",ci,"projectId","title");const n=Number.parseInt(v?.value||"",10);Number.isInteger(n)&&n>0&&(tt.value=String(n))}},fy=(n=null)=>{if(y){if(ct.length===0){h(y,"No books available.");return}oe(y,"Choose a book",ct.map(n=>({...n,displayTitle:vr(n)})),"bookId","displayTitle");const i=n||Number.parseInt(g?.value||"",10),t=ct.find(n=>n.bookId===i);t&&(y.value=String(t.bookId));d()}},it=(t="Select a Project and Book to begin.")=>{tr=null,yu=null,ir=[],bf=!1,u(bi,!0),u(ye,!0),u(ae,!0),u(ff,!0),bi&&(bi.innerHTML=""),fr&&(fr.innerHTML=""),n(ur,""),at(t),d()},uc=()=>fr?[...fr.querySelectorAll("input[type='checkbox']:checked")].map(n=>String(n.value||"").trim()).filter(Boolean):[],ko=t=>{if(ir=Array.isArray(t)?t:[],ae&&fr){if(fr.innerHTML="",u(ae,!1),u(ff,!bf||ir.length===0),ir.length===0){n(ur,"No likely major characters found.");d();return}n(ur,`${ir.length} likely major characters found.`);for(const n of ir){const i=document.createElement("label");i.className="word-companion-character-candidate";const t=document.createElement("input");t.type="checkbox";t.value=n.text||"";t.checked=String(n.confidence||"").toLowerCase()!=="very low";t.addEventListener("change",d);const r=document.createElement("strong");r.textContent=n.text||"";const u=document.createElement("em"),f=String(n.reason||"").trim();u.textContent=f?`${Number(n.mentionCount||0).toLocaleString()} mentions, ${f.toLowerCase()}`:`${Number(n.mentionCount||0).toLocaleString()} mentions`;i.append(t,r,u);fr.append(i)}d()}},ey=n=>{if(bi){bi.innerHTML="";const t=document.createElement("strong");t.textContent="Manuscript Analysis";bi.append(t);const i=document.createElement("dl"),r=[["Project",n.projectTitle],["Book",vr({title:n.bookTitle,subtitle:n.bookSubtitle})],["Chapters",Number(n.chapterCount||0).toLocaleString()],["Scenes",Number(n.sceneCount||0).toLocaleString()],["Words",Number(n.wordCount||0).toLocaleString()]];for(const[n,t]of r){const r=document.createElement("dt");r.textContent=n;const u=document.createElement("dd");u.textContent=t||"-";i.append(r,u)}bi.append(i);u(bi,!1);u(ye,!1);at(n.message||"Preview generated.");d()}},oy=()=>{const n=window.Office?.context?.document?.settings;if(!n)return null;const u=String(n.get(pi.documentGuid)||"").trim(),t=Number.parseInt(n.get(pi.bookId)||"",10),i=Number.parseInt(n.get(pi.projectId)||"",10),r=Number.parseInt(n.get(pi.bindingVersion)||"",10);return!u||!Number.isInteger(t)||t<=0||!Number.isInteger(i)||i<=0?null:{documentGuid:u,bookId:t,projectId:i,bindingVersion:Number.isInteger(r)&&r>0?r:1}},sy=n=>new Promise((t,i)=>{const r=window.Office?.context?.document?.settings;if(!r){i(new Error("Word document settings are unavailable."));return}r.set(pi.documentGuid,n.documentGuid);r.set(pi.bookId,String(n.bookId));r.set(pi.projectId,String(n.projectId));r.set(pi.bindingVersion,String(n.bindingVersion||1));r.saveAsync(n=>{n.status===window.Office.AsyncResultStatus.Succeeded?t():i(n.error||new Error("Unable to save Word document metadata."))})}),fc=()=>vu?.documentGuid?vu.documentGuid:window.crypto?.randomUUID?window.crypto.randomUUID():"10000000-1000-4000-8000-100000000000".replace(/[018]/g,n=>(Number(n)^Math.random()*16>>Number(n)/4).toString(16)),ii=()=>{const t=iu(),n=l();vs&&(vs.textContent=t?.title||"(Not connected)");ys&&(ys.textContent=n?vr(n):"(Not connected)");th&&(th.textContent=n?"":"Select a PlotDirector book before refreshing.");te()},go=n=>{const t=n?.styleBuiltIn||n?.style||"";return String(t).replace(/\s+/g,"").toLowerCase()},nf=(n,t)=>{const i=go(n);return i===`heading${t}`||i.endsWith(`.heading${t}`)||i.includes(`heading${t}`)},ns=n=>{const t=String(n||"").trim().split(/\s+/).filter(Boolean);return t.length},hy=(n,t)=>String(n?.text||"").trim()===String(t?.text||"").trim()&&go(n)===go(t),ec=(n,t)=>{const r=String(n||"").match(new RegExp(`^${t}-(\\d+)$`,"i"));if(!r)return null;const i=Number.parseInt(r[1],10);return Number.isInteger(i)&&i>0?i:null},oc=(n,t)=>{const i=Array.isArray(n?.contentControls?.items)?n.contentControls.items:[];for(const n of i){const i=ec(n.tag,t);if(i)return i}return null},cy=(n,t)=>{const u=[];let o=0,s=0,i=null,r=null;n.forEach((n,t)=>{const f=String(n.text||"").trim();if(f){if(nf(n,1)){o+=1;i={index:u.length+1,paragraphIndex:t,title:f,anchorId:oc(n,"PD-CHAPTER"),scenes:[]};u.push(i);r=null;return}if(nf(n,2)){if(s+=1,!i)return;r={index:i.scenes.length+1,paragraphIndex:t,title:f,anchorId:oc(n,"PD-SCENE"),wordCount:0};i.scenes.push(r);return}r&&(r.wordCount+=ns(f))}});const h=t.find(n=>String(n.text||"").trim()),f=h?n.findIndex(n=>hy(n,h)):-1,e=f>=0?u.filter(n=>n.paragraphIndex<=f).at(-1)||null:null,c=e&&f>=0?e.scenes.filter(n=>n.paragraphIndex<=f).at(-1)||null:null;return{chapters:u,currentChapter:e,currentScene:c,paragraphCount:n.length,heading1Count:o,heading2Count:s}},ly=(n,t)=>{const r=[];let i=null,u=null,f=0;const e=[],s=new Set(["***","###"]),o=()=>{if(!i)return null;const n={title:`Scene ${i.scenes.length+1}`,sortOrder:(i.scenes.length+1)*10,wordCount:0};return i.scenes.push(n),n};return n.forEach(n=>{const h=String(n.text||"").trim();if(h){if(e.push(h),nf(n,1)){i={title:h,sortOrder:(r.length+1)*10,wordCount:0,scenes:[]};r.push(i);u=o();f+=ns(h);return}if(i&&u){if(t&&s.has(h)){u=o();return}const c=ns(h);i.wordCount+=c;u.wordCount+=c;f+=c}}}),{chapters:r,chapterCount:r.length,sceneCount:r.reduce((n,t)=>n+t.scenes.length,0),wordCount:f,documentText:e.join("\n")}},ts=n=>{if(fu){if(fu.innerHTML="",n.chapters.length===0){const n=document.createElement("p");n.textContent="No chapters detected. Use Heading 1 for chapter titles.";fu.append(n);return}const t=n.chapters.some(n=>n.scenes.length>0);if(!t){const n=document.createElement("p");n.textContent="No scenes detected. Use Heading 2 for scene titles.";fu.append(n)}for(const t of n.chapters){const n=document.createElement("div");n.className="word-companion-outline-chapter";const i=document.createElement("strong");if(i.textContent=t.title,n.append(i),t.scenes.length>0){const i=document.createElement("ul");for(const n of t.scenes){const t=document.createElement("li");t.textContent=n.title;i.append(t)}n.append(i)}fu.append(n)}}},sc=[{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"}],ay={alreadyLinked:"Already Linked",couldLink:"Could Link",wouldCreateChapter:"Would Create Chapter",wouldCreateScene:"Would Create Scene",manualReview:"Manual Review Required"},vy={alreadyLinked:"✓",couldLink:"~",wouldCreateChapter:"+",wouldCreateScene:"+",manualReview:"?"},se=(n,t)=>b(n,t).toLocaleLowerCase(),vt=({category:i,action:n,type:s,wordTitle:l,match:r="",anchorStatus:t="None",matches:u=[],pdChapter:e=null,pdScene:o=null,wordChapter:h=null,wordScene:c=null,parentItem:f=null})=>({id:`${s}-${h?.index||0}-${c?.index||0}-${h?.paragraphIndex??c?.paragraphIndex??0}`,category:i,action:n,type:s,wordTitle:l,match:r,anchorStatus:t,matches:u,pdChapter:e,pdScene:o,wordChapter:h,wordScene:c,parentItem:f,result:"Pending",error:""}),yt=(n,t)=>{Array.isArray(n[t.category])&&n[t.category].push(t)},is=n=>Array.isArray(n?.scenes)?n.scenes:[],yy=(n,t)=>{for(const i of n){const n=is(i).find(n=>n.sceneId===t);if(n)return{chapter:i,scene:n}}return null},py=(n,t,i)=>{const u=n.anchorId?`PD-CHAPTER-${n.anchorId}`:"None";if(n.anchorId){const r=t.find(t=>t.chapterId===n.anchorId);if(r){const t=vt({category:"alreadyLinked",action:"alreadyLinked",type:"Chapter",wordTitle:n.title,match:`Chapter ${r.chapterId}: ${r.title}`,anchorStatus:u,pdChapter:r,wordChapter:n});return yt(i,t),t}const f=vt({category:"manualReview",action:"manualReview",type:"Chapter",wordTitle:n.title,anchorStatus:`${u} not found`,matches:[],pdChapter:null,wordChapter:n});return yt(i,f),f}const r=t.filter(t=>se(t.title,"chapter")===se(n.title,"chapter"));if(r.length===1){const t=vt({category:"couldLink",action:"couldLink",type:"Chapter",wordTitle:n.title,match:`Chapter ${r[0].chapterId}: ${r[0].title}`,anchorStatus:u,pdChapter:r[0],wordChapter:n});return yt(i,t),t}if(r.length>1){const t=vt({category:"manualReview",action:"manualReview",type:"Chapter",wordTitle:n.title,anchorStatus:u,matches:r.map(n=>`Chapter ${n.chapterId}: ${n.title}`),pdChapter:null,wordChapter:n});return yt(i,t),t}const f=vt({category:"wouldCreateChapters",action:"wouldCreateChapter",type:"Chapter",wordTitle:n.title,anchorStatus:u,pdChapter:null,wordChapter:n});return yt(i,f),f},wy=(n,t,i,r)=>{const u=n.anchorId?`PD-SCENE-${n.anchorId}`:"None";if(n.anchorId){const f=yy(i,n.anchorId);if(f){yt(r,vt({category:"alreadyLinked",action:"alreadyLinked",type:"Scene",wordTitle:n.title,match:`Scene ${f.scene.sceneId}: ${f.scene.title}`,anchorStatus:u,pdChapter:f.chapter,pdScene:f.scene,wordChapter:t?.wordChapter,wordScene:n,parentItem:t}));return}yt(r,vt({category:"manualReview",action:"manualReview",type:"Scene",wordTitle:n.title,anchorStatus:`${u} not found`,wordChapter:t?.wordChapter,wordScene:n,parentItem:t}));return}if(!t?.pdChapter&&t?.category==="manualReview"){yt(r,vt({category:"manualReview",action:"manualReview",type:"Scene",wordTitle:n.title,anchorStatus:u,matches:["Parent chapter requires manual review."],wordChapter:t?.wordChapter,wordScene:n,parentItem:t}));return}if(!t?.pdChapter){yt(r,vt({category:"wouldCreateScenes",action:"wouldCreateScene",type:"Scene",wordTitle:n.title,anchorStatus:u,wordChapter:t?.wordChapter,wordScene:n,parentItem:t}));return}const f=is(t.pdChapter).filter(t=>se(t.title,"scene")===se(n.title,"scene"));if(f.length===1){yt(r,vt({category:"couldLink",action:"couldLink",type:"Scene",wordTitle:n.title,match:`Scene ${f[0].sceneId}: ${f[0].title}`,anchorStatus:u,pdChapter:t.pdChapter,pdScene:f[0],wordChapter:t.wordChapter,wordScene:n,parentItem:t}));return}if(f.length>1){yt(r,vt({category:"manualReview",action:"manualReview",type:"Scene",wordTitle:n.title,anchorStatus:u,matches:f.map(n=>`Scene ${n.sceneId}: ${n.title}`),wordChapter:t.wordChapter,wordScene:n,parentItem:t}));return}yt(r,vt({category:"wouldCreateScenes",action:"wouldCreateScene",type:"Scene",wordTitle:n.title,anchorStatus:u,pdChapter:t.pdChapter,wordChapter:t.wordChapter,wordScene:n,parentItem:t}))},by=(n,t)=>{const i={alreadyLinked:[],couldLink:[],wouldCreateChapters:[],wouldCreateScenes:[],manualReview:[]},r=Array.isArray(t?.chapters)?t.chapters:[];for(const t of Array.isArray(n?.chapters)?n.chapters:[]){const n=py(t,r,i);for(const u of is(t))wy(u,n,r,i)}return i},ky=n=>{const t=document.createElement("article");t.className="word-companion-preview-item";const i=document.createElement("strong");i.textContent=`${vy[n.action]||""} ${n.type}: ${n.wordTitle}`;t.append(i);const r=document.createElement("span");if(r.textContent=`Anchor: ${n.anchorStatus||"None"}`,t.append(r),n.match){const i=document.createElement("span");i.textContent=`Match: ${n.match}`;t.append(i)}if(Array.isArray(n.matches)&&n.matches.length>0){const i=document.createElement("div");i.className="word-companion-preview-matches";const r=document.createElement("span");r.textContent="Matches:";i.append(r);const u=document.createElement("ul");for(const t of n.matches){const n=document.createElement("li");n.textContent=t;u.append(n)}i.append(u);t.append(i)}const u=document.createElement("span");if(u.textContent=`Action: ${ay[n.action]||n.action}`,t.append(u),n.result&&n.result!=="Pending"){const i=document.createElement("span");i.textContent=n.error?`Result: ${n.result} - ${n.error}`:`Result: ${n.result}`;t.append(i)}return t},hc=n=>{if(dr){dr.innerHTML="";for(const t of sc){const i=document.createElement("section");i.className="word-companion-preview-group";const r=document.createElement("strong");r.textContent=t.title;i.append(r);const u=Array.isArray(n?.[t.key])?n[t.key]:[];if(u.length===0){const n=document.createElement("p");n.className="word-companion-empty-list";n.textContent="None";i.append(n)}else for(const n of u)i.append(ky(n));dr.append(i)}ne()}},wr=async n=>{const t=n.document.body.paragraphs,i=n.document.getSelection().paragraphs;t.load("text,styleBuiltIn,style");i.load("text,styleBuiltIn,style");await n.sync();for(const n of t.items)(nf(n,1)||nf(n,2))&&n.contentControls.load("items/tag,title");await n.sync();const r=cy(t.items,i.items);return r.bodyParagraphs=t.items,r},cc=async()=>{if(t(""),!kt||!rt||!window.Word||typeof window.Word.run!="function")return ku(null,null,null),t("Word document access is unavailable."),i({lastScan:"Failure",lastError:"Word document access is unavailable."}),!1;er&&(er.disabled=!0,er.textContent="Scanning...");t("Scanning document structure...");try{const n=await window.Word.run(async n=>await wr(n));return ku(n.currentChapter?.title,n.currentScene?.title,n.currentScene?.wordCount,n.currentChapter?.anchorId,n.currentScene?.anchorId,n.currentChapter?.index),k("Not detected"),ts(n),t(""),i({lastScan:"Success",lastError:"-",paragraphs:String(n.paragraphCount),heading1:String(n.heading1Count),heading2:String(n.heading2Count)}),!0}catch(n){const r=s(n);return console.error("Unable to scan the Word document.",n),ku(null,null,null),t(`Unable to scan the Word document: ${r}`),i({lastScan:"Failure",lastError:r}),!1}finally{er&&(er.disabled=!1,er.textContent="Refresh Document Structure")}},lc=async()=>{const n=l();if(!iu()||!n)return vi("Select a project and book to analyse manuscript structure."),!1;if(!kt||!rt||!window.Word||typeof window.Word.run!="function")return vi("Word document access is unavailable."),i({lastScan:"Failure",lastError:"Word document access is unavailable."}),!1;ki&&(ki.disabled=!0,ki.textContent="Analysing...");vi("Analysing manuscript structure...");try{const t=await window.Word.run(async n=>await wr(n)),r=await pt(`/api/word-companion/books/${n.bookId}/structure`);return ku(t.currentChapter?.title,t.currentScene?.title,t.currentScene?.wordCount,t.currentChapter?.anchorId,t.currentScene?.anchorId,t.currentChapter?.index),ts(t),dt=by(t,r),hc(dt),vi("Preview generated. No changes have been applied."),i({lastScan:"Success",lastError:"-",paragraphs:String(t.paragraphCount),heading1:String(t.heading1Count),heading2:String(t.heading2Count)}),!0}catch(t){return console.error("Unable to analyse manuscript structure.",t),vi("Unable to analyse manuscript structure."),i({lastScan:"Failure",lastError:s(t)}),!1}finally{ki&&(ki.textContent="Analyse Manuscript Structure",te())}},dy=(n=dt)=>{const t=hr(n),i=t.filter(n=>n.category==="couldLink"&&n.type==="Chapter").length,r=t.filter(n=>n.category==="couldLink"&&n.type==="Scene").length,u=t.filter(n=>n.category==="wouldCreateChapters").length,f=t.filter(n=>n.category==="wouldCreateScenes").length,e=Array.isArray(n?.manualReview)?n.manualReview.length:0;return{linkChapters:i,linkScenes:r,createChapters:u,createScenes:f,manualReview:e}},gy=n=>{if(io){io.innerHTML="";const i=[`Link ${n.linkChapters+n.linkScenes} chapters/scenes`,`Create ${n.createChapters} chapters`,`Create ${n.createScenes} scenes`],t=document.createElement("ul");for(const n of i){const i=document.createElement("li");i.textContent=n;t.append(i)}io.append(t)}},rs=n=>{kf&&(kf(n),kf=null),hi?.close?hi.close():hi&&(hi.hidden=!0)},np=()=>{const n=dy();return(gy(n),!hi)?Promise.resolve(window.confirm(`Apply Structure Sync? This will: