(() => {
  const progressRoots = [...document.querySelectorAll("[data-story-intelligence-progress]")];
  const dashboardRoots = [...document.querySelectorAll("[data-story-intelligence-dashboard]")];
  const onboardingRoots = [...document.querySelectorAll("[data-story-intelligence-onboarding]")];
  const legacyRoots = [...progressRoots, ...dashboardRoots];
  const roots = [...legacyRoots, ...onboardingRoots];
  if (roots.length === 0 || !window.signalR) {
    return;
  }

  const field = (state, camel, pascal) => state?.[camel] ?? state?.[pascal] ?? null;
  const legacyJobIds = [...new Set(legacyRoots.map((root) => root.dataset.storyIntelligenceJobId).filter(Boolean))];
  const runIds = [...new Set(onboardingRoots
    .flatMap((root) => (root.dataset.storyIntelligenceRunIds || "").split(","))
    .map((value) => value.trim())
    .filter(Boolean))];

  const numberText = (value) => {
    const number = Number.parseInt(value ?? "0", 10);
    return Number.isFinite(number) ? number.toLocaleString() : "0";
  };

  const friendlyStatus = (status) => {
    switch (status) {
      case "Pending":
        return "Waiting";
      case "Running":
        return "Reading";
      case "Completed":
        return "Ready to review";
      case "CompletedWithWarnings":
        return "Ready to review, with notes";
      case "Failed":
        return "Needs attention";
      case "Cancelled":
        return "Cancelled";
      default:
        return "Waiting";
    }
  };

  const friendlyStage = (stage, status) => {
    if (status === "Completed" || status === "CompletedWithWarnings") {
      return "Ready to review";
    }
    if (status === "Failed") {
      return "Needs attention";
    }
    if (status === "Cancelled") {
      return "Cancelled";
    }

    switch (stage) {
      case "Queued":
        return "Waiting";
      case "DocumentRead":
        return "Preparing chapter";
      case "ChapterStructure":
        return "Finding scenes";
      case "SceneSplit":
        return "Preparing scenes";
      case "SceneIntelligence":
        return "Reading scenes";
      case "Validation":
        return "Checking results";
      case "Persistence":
        return "Saving analysis";
      default:
        return "Reading";
    }
  };

  const friendlyRemainingFromSeconds = (seconds) => {
    const minutes = Math.ceil(seconds / 60);
    if (!Number.isFinite(minutes) || minutes <= 0) {
      return "Estimating...";
    }

    if (minutes <= 20) {
      return "About 20-30 minutes";
    }
    if (minutes <= 45) {
      return "About 45-60 minutes";
    }
    if (minutes <= 120) {
      return "About 1-2 hours";
    }
    return "More than 2 hours";
  };

  const calculateRemaining = (chapters, hasActiveRuns) => {
    if (!hasActiveRuns) {
      return "Ready to review";
    }

    const completed = chapters.filter((chapter) => {
      const status = chapter.dataset.storyRawStatus || "";
      const words = Number.parseInt(chapter.dataset.storySourceWordCount || "0", 10) || 0;
      const duration = Number.parseInt(chapter.dataset.storyDurationMs || "0", 10) || 0;
      return (status === "Completed" || status === "CompletedWithWarnings") && words > 0 && duration > 0;
    });

    if (completed.length === 0) {
      return "Estimating...";
    }

    const completedWords = completed.reduce((total, chapter) => total + (Number.parseInt(chapter.dataset.storySourceWordCount || "0", 10) || 0), 0);
    const completedSeconds = completed.reduce((total, chapter) => total + ((Number.parseInt(chapter.dataset.storyDurationMs || "0", 10) || 0) / 1000), 0);
    const remainingWords = chapters.reduce((total, chapter) => {
      const status = chapter.dataset.storyRawStatus || "";
      if (status === "Completed" || status === "CompletedWithWarnings" || status === "Failed" || status === "Cancelled") {
        return total;
      }
      return total + (Number.parseInt(chapter.dataset.storySourceWordCount || "0", 10) || 0);
    }, 0);

    if (completedWords <= 0 || completedSeconds <= 0 || remainingWords <= 0) {
      return "Estimating...";
    }

    return friendlyRemainingFromSeconds((completedSeconds / completedWords) * remainingWords * 1.35);
  };

  const possibleDiscoveryText = (text) => String(text || "")
    .replace(/^Character detected:/i, "Possible character found:")
    .replace(/^Possible character detected:/i, "Possible character found:")
    .replace(/^Location detected:/i, "Possible location found:")
    .replace(/^Possible location detected:/i, "Possible location found:")
    .replace(/^Asset detected:/i, "Possible asset found:")
    .replace(/^Possible asset detected:/i, "Possible asset found:")
    .replace(/^Timeline clue detected:/i, "Timeline clue found:")
    .replace(/^Relationship detected:/i, "Relationship signal found:");

  const friendlyMessage = (message, stage, status, sceneNumber) => {
    if (status === "Cancelled") {
      return "Analysis was cancelled. Any completed chapters remain available for review.";
    }
    if (status === "Failed") {
      return "This chapter needs attention before scenes can be created.";
    }
    if (status === "Completed" || status === "CompletedWithWarnings") {
      return "This chapter is ready to review.";
    }

    switch (stage) {
      case "Queued":
        return "Waiting to read this chapter.";
      case "DocumentRead":
        return "Preparing this chapter.";
      case "ChapterStructure":
        return "Finding scenes in this chapter.";
      case "SceneSplit":
        return "Preparing the detected scenes.";
      case "SceneIntelligence":
        return sceneNumber ? `Reading scene ${numberText(sceneNumber)}.` : "Reading the detected scenes.";
      case "Validation":
        return "Checking the results.";
      default:
        return message || "Waiting for analysis to begin.";
    }
  };

  const updateLegacyRoot = (root, state) => {
    const percent = field(state, "progressPercent", "ProgressPercent") ?? 0;
    const status = field(state, "status", "Status") || "Pending";
    const stage = field(state, "currentStage", "CurrentStage") || status;
    const message = field(state, "currentMessage", "CurrentMessage") || "Story Intelligence is preparing.";
    const elapsed = field(state, "elapsedTime", "ElapsedTime") || "0 sec";
    const remaining = field(state, "estimatedRemaining", "EstimatedRemaining") || "Calculating";

    root.querySelectorAll("[data-story-intelligence-message]").forEach((node) => {
      node.textContent = message;
    });
    root.querySelectorAll("[data-story-intelligence-percent]").forEach((node) => {
      node.textContent = `${percent}%`;
    });
    root.querySelectorAll("[data-story-intelligence-progress-bar]").forEach((node) => {
      node.style.width = `${Math.max(0, Math.min(100, Number.parseInt(percent, 10) || 0))}%`;
    });
    root.querySelectorAll("[data-story-intelligence-stage]").forEach((node) => {
      node.textContent = stage;
    });
    root.querySelectorAll("[data-story-intelligence-status]").forEach((node) => {
      node.textContent = status;
    });
    root.querySelectorAll("[data-story-intelligence-elapsed]").forEach((node) => {
      node.textContent = elapsed;
    });
    root.querySelectorAll("[data-story-intelligence-remaining]").forEach((node) => {
      node.textContent = remaining;
    });

    const completed = !!field(state, "isCompleted", "IsCompleted");
    root.querySelectorAll("[data-story-intelligence-complete-link]").forEach((node) => {
      node.classList.toggle("disabled", !completed);
      node.setAttribute("aria-disabled", String(!completed));
    });
  };

  const applyLegacyProgress = (state) => {
    const jobId = field(state, "jobID", "JobID") || field(state, "jobId", "JobId");
    if (!jobId) {
      return;
    }

    legacyRoots
      .filter((root) => root.dataset.storyIntelligenceJobId?.toLowerCase() === String(jobId).toLowerCase())
      .forEach((root) => updateLegacyRoot(root, state));

    if (progressRoots.length > 0 && field(state, "isCompleted", "IsCompleted")) {
      window.location.href = `/onboarding/story-intelligence/complete?jobId=${encodeURIComponent(jobId)}`;
    }
  };

  const statusLabel = (state) => {
    const status = field(state, "status", "Status") || "";
    if (status === "Completed" || status === "CompletedWithWarnings") {
      return "Ready to create";
    }
    if (status === "Failed" || status === "Cancelled") {
      return "Needs attention";
    }
    if (status === "Pending" || status === "Running") {
      return "Analysing";
    }
    return "Reviewing";
  };

  const addFeedItem = (list, text) => {
    if (!list || !text) {
      return;
    }

    const item = document.createElement("li");
    item.textContent = possibleDiscoveryText(text);
    list.prepend(item);
    const placeholder = list.closest(".story-live-discoveries")?.querySelector("[data-story-discovery-empty]");
    if (placeholder) {
      placeholder.hidden = true;
    }
    while (list.children.length > 8) {
      list.lastElementChild?.remove();
    }
  };

  const parseNumber = (value) => Number.parseFloat(String(value || "").replace(/,/g, "")) || 0;

  const parseTime = (value) => {
    const parsed = Date.parse(value || "");
    return Number.isNaN(parsed) ? 0 : parsed;
  };

  const isActiveStatus = (status) => status === "Pending" || status === "Running";

  const authoritativeChapter = (chapters) => {
    const active = chapters
      .filter((chapter) => isActiveStatus(chapter.dataset.storyRawStatus || "")
        && parseNumber(chapter.dataset.storyCurrentSceneNumber) > 0)
      .sort((left, right) => {
        const byUpdated = parseTime(right.dataset.storyUpdatedUtc) - parseTime(left.dataset.storyUpdatedUtc);
        if (byUpdated !== 0) return byUpdated;
        return parseNumber(right.dataset.storyChapterNumber) - parseNumber(left.dataset.storyChapterNumber);
      });
    if (active.length > 0) return active[0];

    const completed = chapters
      .filter((chapter) => {
        const status = chapter.dataset.storyRawStatus || "";
        return status === "Completed" || status === "CompletedWithWarnings";
      })
      .sort((left, right) => {
        const byUpdated = parseTime(right.dataset.storyUpdatedUtc) - parseTime(left.dataset.storyUpdatedUtc);
        if (byUpdated !== 0) return byUpdated;
        return parseNumber(right.dataset.storyChapterNumber) - parseNumber(left.dataset.storyChapterNumber);
      });
    return completed[0] || chapters[0] || null;
  };

  const applyAuthoritativeCursor = (root) => {
    const chapters = [...root.querySelectorAll("[data-story-run-id]")];
    const chapter = authoritativeChapter(chapters);
    if (!chapter) return;

    const status = chapter.dataset.storyRawStatus || "Pending";
    const stage = chapter.dataset.storyCurrentStage || status;
    const currentSceneNumber = chapter.dataset.storyCurrentSceneNumber || "";
    const total = chapter.querySelector("[data-story-run-total]")?.textContent || "";
    const completed = chapter.querySelector("[data-story-run-completed]")?.textContent || "";
    const message = chapter.querySelector("[data-story-run-message]")?.textContent || "";

    root.querySelectorAll("[data-story-current-chapter]").forEach((node) => {
      node.textContent = chapter.dataset.storyChapterTitle || "Current chapter";
    });
    root.querySelectorAll("[data-story-current-stage]").forEach((node) => {
      node.textContent = friendlyStage(stage, status);
    });
    root.querySelectorAll("[data-story-current-scene]").forEach((node) => {
      node.textContent = currentSceneNumber ? numberText(currentSceneNumber) : "Waiting";
    });
    const totalNumber = Number.parseInt(total.replace(/,/g, "") || "0", 10) || 0;
    const completedNumber = Number.parseInt(completed.replace(/,/g, "") || "0", 10) || 0;
    const currentSceneDisplay = currentSceneNumber ? Number.parseInt(currentSceneNumber, 10) || completedNumber : completedNumber;
    root.querySelectorAll("[data-story-chapter-progress-text]").forEach((node) => {
      node.textContent = totalNumber > 0
        ? `Scene ${numberText(Math.min(Math.max(currentSceneDisplay, 0), totalNumber))} of ${numberText(totalNumber)}`
        : "Finding scenes...";
    });
    root.querySelectorAll("[data-story-current-message]").forEach((node) => {
      node.textContent = message;
    });
  };

  const recalculateOnboardingTotals = (root) => {
    const hadActiveRuns = root.dataset.storyIntelligenceHasActiveRuns === "true";
    const chapters = [...root.querySelectorAll("[data-story-run-id]")];
    const totals = chapters.reduce((current, chapter) => {
      const status = chapter.dataset.storyRawStatus || "";
      const completed = Number.parseInt(chapter.querySelector("[data-story-run-completed]")?.textContent?.replace(/,/g, "") || "0", 10) || 0;
      const total = Number.parseInt(chapter.querySelector("[data-story-run-total]")?.textContent?.replace(/,/g, "") || "0", 10) || 0;
      const failed = Number.parseInt(chapter.querySelector("[data-story-run-failed]")?.textContent?.replace(/,/g, "") || "0", 10) || 0;
      current.completedChapters += status === "Completed" || status === "CompletedWithWarnings" ? 1 : 0;
      current.failedChapters += status === "Failed" || status === "Cancelled" ? 1 : 0;
      current.completedScenes += completed;
      current.totalScenes += total;
      current.failedScenes += failed;
      return current;
    }, { completedChapters: 0, failedChapters: 0, completedScenes: 0, totalScenes: 0, failedScenes: 0 });

    const chapterProgress = chapters.reduce((total, chapter) => {
      const status = chapter.dataset.storyRawStatus || "";
      if (status === "Completed" || status === "CompletedWithWarnings" || status === "Failed" || status === "Cancelled") {
        return total + 1;
      }
      if (status === "Pending") {
        return total;
      }
      const completed = Number.parseInt(chapter.querySelector("[data-story-run-completed]")?.textContent?.replace(/,/g, "") || "0", 10) || 0;
      const detected = Number.parseInt(chapter.querySelector("[data-story-run-total]")?.textContent?.replace(/,/g, "") || "0", 10) || 0;
      if (detected > 0) {
        return total + Math.min(0.95, 0.25 + ((completed / Math.max(1, detected)) * 0.7));
      }
      return total + 0.15;
    }, 0);
    const calculatedPercent = Math.floor((chapterProgress * 100) / Math.max(1, chapters.length));
    const previousPercent = Number.parseInt(root.dataset.storyMaxProgressPercent || "0", 10) || 0;
    const percent = Math.max(previousPercent, Math.max(0, Math.min(100, calculatedPercent)));
    root.dataset.storyMaxProgressPercent = String(percent);

    root.querySelectorAll("[data-story-overall-percent]").forEach((node) => {
      node.textContent = `${Math.max(0, Math.min(100, percent))}%`;
    });
    root.querySelectorAll("[data-story-overall-progress-bar]").forEach((node) => {
      node.style.width = `${Math.max(0, Math.min(100, percent))}%`;
    });
    root.querySelectorAll("[data-story-chapters-read-value]").forEach((node) => {
      node.textContent = `${numberText(totals.completedChapters)} of ${numberText(chapters.length)}`;
    });
    root.querySelectorAll("[data-story-scenes-read-value]").forEach((node) => {
      node.textContent = `${numberText(totals.completedScenes)} so far`;
    });
    root.querySelectorAll("[data-story-scenes-total]").forEach((node) => {
      node.textContent = numberText(totals.totalScenes);
    });
    root.querySelectorAll("[data-story-failures]").forEach((node) => {
      node.textContent = numberText(totals.failedChapters + totals.failedScenes);
    });

    const active = chapters.some((chapter) => {
      const status = chapter.dataset.storyRawStatus || "";
      return status === "Pending" || status === "Running";
    });
    root.dataset.storyIntelligenceHasActiveRuns = String(active);
    root.querySelectorAll("[data-story-remaining]").forEach((node) => {
      node.textContent = calculateRemaining(chapters, active);
    });
    applyAuthoritativeCursor(root);

    if (hadActiveRuns && !active && root.dataset.reloadScheduled !== "true") {
      root.dataset.reloadScheduled = "true";
      window.setTimeout(() => window.location.reload(), 1200);
    }
  };

  const applyRunProgress = (state) => {
    const runId = field(state, "runID", "RunID") || field(state, "runId", "RunId");
    if (!runId) {
      return;
    }

    onboardingRoots.forEach((root) => {
      const chapter = root.querySelector(`[data-story-run-id="${runId}"]`);
      if (!chapter) {
        return;
      }

      const status = field(state, "status", "Status") || "Pending";
      const stage = field(state, "currentStage", "CurrentStage") || status;
      const message = field(state, "currentMessage", "CurrentMessage") || "";
      const completed = field(state, "completedScenes", "CompletedScenes");
      const total = field(state, "totalDetectedScenes", "TotalDetectedScenes");
      const failed = field(state, "failedScenes", "FailedScenes");
      const tokens = field(state, "totalTokens", "TotalTokens");
      const totalDurationMs = field(state, "totalDurationMs", "TotalDurationMs");
      const elapsed = field(state, "elapsedTime", "ElapsedTime") || "0 sec";
      const currentSceneNumber = field(state, "currentSceneNumber", "CurrentSceneNumber");
      const updatedUtc = field(state, "updatedUtc", "UpdatedUtc");
      const displayMessage = friendlyMessage(message, stage, status, currentSceneNumber);
      const latestSummary = field(state, "latestSceneSummary", "LatestSceneSummary");
      const discoveries = field(state, "recentDiscoveries", "RecentDiscoveries") || [];
      const error = field(state, "errorMessage", "ErrorMessage");

      chapter.querySelectorAll("[data-story-chapter-status-label]").forEach((node) => {
        node.textContent = statusLabel(state);
      });
      chapter.dataset.storyRawStatus = status;
      chapter.dataset.storyCurrentStage = stage;
      chapter.dataset.storyCurrentSceneNumber = completed || "0";
      if (updatedUtc) {
        chapter.dataset.storyUpdatedUtc = updatedUtc;
      }
      if (totalDurationMs !== null) {
        chapter.dataset.storyDurationMs = String(totalDurationMs);
      }
      chapter.querySelectorAll("[data-story-run-status]").forEach((node) => {
        node.textContent = friendlyStatus(status);
      });
      chapter.querySelectorAll("[data-story-run-raw-status]").forEach((node) => {
        node.textContent = status;
      });
      chapter.querySelectorAll("[data-story-run-raw-stage]").forEach((node) => {
        node.textContent = stage;
      });
      chapter.querySelectorAll("[data-story-run-message]").forEach((node) => {
        node.textContent = displayMessage;
      });
      chapter.querySelectorAll("[data-story-run-completed]").forEach((node) => {
        if (completed !== null) {
          node.textContent = numberText(completed);
        }
      });
      chapter.querySelectorAll("[data-story-run-total]").forEach((node) => {
        if (total !== null) {
          node.textContent = numberText(total);
        }
      });
      chapter.querySelectorAll("[data-story-run-failed]").forEach((node) => {
        if (failed !== null) {
          node.textContent = numberText(failed);
        }
      });
      chapter.querySelectorAll("[data-story-run-review-note]").forEach((node) => {
        const failedCount = Number.parseInt(failed || "0", 10) || 0;
        node.textContent = failedCount > 0 ? "Needs review after analysis completes." : "";
        node.hidden = failedCount <= 0;
      });
      chapter.querySelectorAll("[data-story-run-tokens]").forEach((node) => {
        node.textContent = tokens === null ? "-" : numberText(tokens);
      });
      chapter.querySelectorAll("[data-story-run-error]").forEach((node) => {
        node.textContent = error || "";
        node.hidden = !error;
      });
      chapter.querySelectorAll("[data-story-run-latest-summary]").forEach((node) => {
        if (latestSummary) {
          node.textContent = latestSummary;
        }
      });

      const totalNumber = Number.parseInt(total || "0", 10) || 0;
      const completedNumber = Number.parseInt(completed || "0", 10) || 0;
      root.querySelectorAll("[data-story-chapter-progress-bar]").forEach((node) => {
        const percent = totalNumber > 0 ? Math.floor((Math.min(completedNumber, totalNumber) * 100) / totalNumber) : 0;
        node.style.width = `${Math.max(0, Math.min(100, percent))}%`;
      });
      root.querySelectorAll("[data-story-elapsed]").forEach((node) => {
        node.textContent = elapsed;
      });
      const summaryNode = root.querySelector("[data-story-latest-summary]");
      if (summaryNode && latestSummary) {
        summaryNode.textContent = latestSummary;
        summaryNode.hidden = false;
        const emptyNode = root.querySelector("[data-story-latest-empty]");
        if (emptyNode) {
          emptyNode.hidden = true;
        }
      }

      const feed = root.querySelector("[data-story-discovery-feed]");
      [...discoveries].reverse().forEach((discovery) => addFeedItem(feed, discovery));

      recalculateOnboardingTotals(root);
    });
  };

  const connection = new signalR.HubConnectionBuilder()
    .withUrl("/hubs/story-intelligence")
    .withAutomaticReconnect()
    .build();

  connection.on("StoryIntelligenceProgressChanged", applyLegacyProgress);
  connection.on("StoryIntelligenceRunProgressChanged", applyRunProgress);
  connection.onreconnected(() => {
    legacyJobIds.forEach((jobId) => connection.invoke("WatchStoryIntelligenceJob", jobId).then(applyLegacyProgress).catch(() => {}));
    runIds.forEach((runId) => connection.invoke("WatchStoryIntelligenceRun", Number.parseInt(runId, 10)).then(applyRunProgress).catch(() => {}));
  });

  connection.start()
    .then(() => Promise.all([
      ...legacyJobIds.map((jobId) => connection.invoke("WatchStoryIntelligenceJob", jobId)),
      ...runIds.map((runId) => connection.invoke("WatchStoryIntelligenceRun", Number.parseInt(runId, 10)))
    ]))
    .then((states) => {
      states.filter(Boolean).forEach((state) => {
        if (field(state, "runID", "RunID") || field(state, "runId", "RunId")) {
          applyRunProgress(state);
        } else {
          applyLegacyProgress(state);
        }
      });
    })
    .catch(() => {});
})();
