const storybookFoundation = window.SpaceExplorerFoundation || {};
const storybookSafeArray = storybookFoundation.safeArray || ((value) =>
  Array.isArray(value) ? value : []);

function normalizeStoryBookId(bookId) {
  return String(bookId || "").trim();
}

function storyProgressKey(bookId) {
  return "planets_story_progress_" + normalizeStoryBookId(bookId);
}

function storyPageCount(pagesOrLength) {
  const pagesLength = Array.isArray(pagesOrLength)
    ? pagesOrLength.length
    : Number(pagesOrLength);
  return Number.isFinite(pagesLength) ? Math.max(0, Math.trunc(pagesLength)) : 0;
}

function storyBookExists(bookId) {
  const normalizedBookId = normalizeStoryBookId(bookId);
  return Boolean(normalizedBookId && STORYBOOKS[normalizedBookId]);
}

function resolveStoryBookId(bookId) {
  const normalizedBookId = normalizeStoryBookId(bookId);
  if (storyBookExists(normalizedBookId)) return normalizedBookId;
  const firstShelfBookId = storybookSafeArray(STORY_SHELF_ORDER)
    .map(normalizeStoryBookId)
    .find(storyBookExists);
  return storyBookExists("starHome") ? "starHome" : firstShelfBookId || "";
}

function storyBookFor(bookId) {
  return STORYBOOKS[resolveStoryBookId(bookId)] || null;
}

function storyBookFallbackNotice(requestedBookId, resolvedBookId) {
  const normalizedRequestedId = normalizeStoryBookId(requestedBookId);
  return normalizedRequestedId && normalizedRequestedId !== resolvedBookId
    ? "That book floated away, so we opened another story."
    : "";
}

function normalizeStoryClassToken(value, fallback = "star-home") {
  const normalized = String(value || fallback)
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9_-]+/g, "-")
    .replace(/^-+|-+$/g, "");
  return normalized || fallback;
}

function isStoryPage(page) {
  return Boolean(
    page &&
      (String(page.text || "").trim() ||
        String(page.image || "").trim() ||
        String(page.audio || "").trim()),
  );
}

function normalizeStoryPage(page) {
  return {
    ...page,
    text: String(page.text || "").trim(),
    image: String(page.image || "").trim(),
    audio: String(page.audio || "").trim(),
  };
}

function normalizeStoryWord(word) {
  if (typeof word === "string") {
    const label = word.trim();
    return label ? { label, audio: "" } : null;
  }
  if (!word || typeof word !== "object") return null;
  const label = String(word.label || word.text || "").trim();
  const audio = String(word.audio || "").trim();
  return label ? { label, audio } : null;
}

function storyWordsForPage(page) {
  return storybookSafeArray(page && page.words)
    .map(normalizeStoryWord)
    .filter(Boolean);
}

function storyPagesFor(story) {
  return storybookSafeArray(story && story.pages)
    .filter(isStoryPage)
    .map(normalizeStoryPage);
}

function clampStoryPage(page, pagesLength) {
  const pageCount = storyPageCount(pagesLength);
  if (!pageCount) return 0;
  const numericPage = Number(page);
  const nextPage = Number.isFinite(numericPage) ? Math.trunc(numericPage) : 0;
  return Math.max(0, Math.min(pageCount - 1, nextPage));
}

function normalizeStoryProgress(page, pagesOrLength) {
  return clampStoryPage(page, storyPageCount(pagesOrLength));
}

function nextStoryPage(page, step, pagesOrLength) {
  const numericStep = Number(step);
  const safeStep = Number.isFinite(numericStep) ? Math.trunc(numericStep) : 0;
  return normalizeStoryProgress(Number(page) + safeStep, pagesOrLength);
}

function storyCoverFor(story, pages) {
  const safePages = storybookSafeArray(pages);
  return (story && story.coverImage) || (safePages[0] && safePages[0].image) || "";
}

function storyShelfStatus(pages, resumePage) {
  const pageCount = storyPageCount(storybookSafeArray(pages));
  const safeResumePage = normalizeStoryProgress(resumePage, pageCount);
  if (!pageCount) return "Loading";
  if (safeResumePage === pageCount - 1) return "Finished";
  if (safeResumePage > 0) return `Resume page ${safeResumePage + 1}`;
  return `${pageCount} pages`;
}

function readStoryProgress(bookId, maxPages) {
  const pageCount = storyPageCount(maxPages);
  const normalizedBookId = normalizeStoryBookId(bookId);
  if (!normalizedBookId || !pageCount) return 0;
  return readStorageInt(storyProgressKey(normalizedBookId), 0, 0, pageCount - 1);
}

function writeStoryProgress(bookId, page, pagesOrLength) {
  const normalizedBookId = normalizeStoryBookId(bookId);
  const pageCount = storyPageCount(pagesOrLength);
  if (!normalizedBookId || !pageCount) return 0;
  const safePage = normalizeStoryProgress(page, pageCount);
  writeStorageValue(storyProgressKey(normalizedBookId), String(safePage));
  return safePage;
}

function storyShelfBooks() {
  return storybookSafeArray(STORY_SHELF_ORDER)
    .map(normalizeStoryBookId)
    .filter(storyBookExists)
    .map((id) => {
      const story = STORYBOOKS[id];
      const pages = storyPagesFor(story);
      const coverImage = storyCoverFor(story, pages);
      const resumePage = readStoryProgress(id, storyPageCount(pages));
      return { id, story, pages, coverImage, resumePage };
    });
}

function StoryBookPage({ book, onExit, onShelf }) {
  const requestedBookId = book;
  const bookId = resolveStoryBookId(book);
  const story = storyBookFor(bookId);
  const pages = storyPagesFor(story);
  const [isOpening, setIsOpening] = useState(true);
  const [page, setPage] = useState(() =>
    readStoryProgress(bookId, pages.length),
  );
  const current = pages[page] || pages[0];
  const wordButtons = storyWordsForPage(current);
  const bookClass = normalizeStoryClassToken(story && story.imageShape);
  const hasAudio = hasNarrationClip(current && current.audio);
  const fallbackNotice = storyBookFallbackNotice(requestedBookId, bookId);
  const pageCue =
    page === 0
      ? "First page"
      : page === pages.length - 1
        ? "The end"
        : "Keep going";
  const bigNextLabel =
    page === pages.length - 1
      ? "Read again"
      : page === 0
        ? "Start turning"
        : "Next page";
  const storyComplete = pages.length > 0 && page === pages.length - 1;
  const go = (step) => {
    if (!pages.length) return;
    setPage((p) => nextStoryPage(p, step, pages));
    playKidSound("boop");
  };

  const goToPage = (nextPage) => {
    if (!pages.length) return;
    setPage(normalizeStoryProgress(nextPage, pages));
    playKidSound("boop");
  };

  // Round-2 fix: track which bookId the current `page` state actually belongs
  // to so the persistence effect doesn't write the previous book's page number
  // into the newly-mounted book's key during the brief window between bookId
  // changing and setPage(readStoryProgress(...)) committing. We update the ref
  // only after `page` has been resynced to match the new bookId — which we
  // detect by comparing the post-render page against the freshly-loaded value.
  const persistedBookRef = useRef(bookId);
  useEffect(() => {
    const restoredPage = readStoryProgress(bookId, pages.length);
    setPage(restoredPage);
    setIsOpening(true);
    const timer = window.setTimeout(() => setIsOpening(false), 820);
    return () => window.clearTimeout(timer);
  }, [bookId, pages.length]);

  useEffect(() => {
    // Only persist once the in-state `page` is known to belong to the current
    // bookId. The first render after a bookId switch has the previous book's
    // page still in state; skip that one. After setPage commits, this effect
    // runs again with the correct page and bookId in sync.
    if (persistedBookRef.current !== bookId) {
      persistedBookRef.current = bookId;
      return;
    }
    writeStoryProgress(bookId, page, pages);
  }, [bookId, page, pages.length]);

  useEffect(() => {
    const nearbyPages = [pages[page + 1], pages[page - 1]];
    nearbyPages.forEach((item) => {
      if (!item || !item.image) return;
      const image = new Image();
      image.decoding = "async";
      image.src = item.image;
    });
  }, [page, pages]);

  // Auto-play this page's narration, but let the child turn pages.
  // Stop any old clip immediately so the next page never speaks over stale audio.
  useEffect(() => {
    stopNarration();
    if (!hasAudio) return stopNarration;
    const timer = window.setTimeout(() => {
      playNarration(current.audio);
    }, STORY_NARRATION_DELAY_MS);
    return () => {
      window.clearTimeout(timer);
      stopNarration();
    };
  }, [page, current && current.audio, hasAudio]);

  useEffect(() => {
    return stopNarration;
  }, []);

  // Round-3: Escape now flows through the shared escape-stack helper so
  // App.jsx's base handler doesn't double-fire. Arrows/space/enter stay
  // on a per-storybook listener since they're local to the page nav UI.
  useEscapeHandler(onExit, true);
  useWindowKeyHandler((e) => {
    if (e.key === "ArrowRight" || e.key === " " || e.key === "Enter") {
      e.preventDefault();
      // Round-2 fix: keyboard parity with the bigNext button — wrap from the
      // last page back to the first instead of soft-locking on the last page.
      if (page === pages.length - 1) {
        goToPage(0);
      } else {
        go(1);
      }
    } else if (e.key === "ArrowLeft") {
      e.preventDefault();
      go(-1);
    }
  });

  const replay = () => {
    if (hasAudio) playNarration(current.audio);
  };
  const returnToShelf = typeof onShelf === "function" ? onShelf : onExit;

  if (!current) {
    return (
      <div className="storybook-page">
        <Starfield count={180} />
        <button className="storybook-close" onClick={onExit}>
          ← Back to planets
        </button>
        <div className="storybook-empty" role="status">
          <strong>{story ? "Story pages are loading." : "Story book is unavailable."}</strong>
          <span>
            {story
              ? "Pick another book and try this one again."
              : "Return to the shelf and choose a different book."}
          </span>
          <button className="storybook-shelf-link" onClick={returnToShelf}>
            Back to story shelf
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className="storybook-page">
      <Starfield count={260} />
      <button className="storybook-close" onClick={onExit}>
        ← Back to planets
      </button>
      <button className="storybook-shelf-link storybook-shelf-top" onClick={returnToShelf}>
        Story shelf
      </button>
      <div className={`storybook-shell ${bookClass}`}>
        {fallbackNotice ? (
          <div className="storybook-recovery-note" role="status">
            {fallbackNotice}
          </div>
        ) : null}
        <div className="storybook-title">{story.title || "Story Book"}</div>
        <div className="storybook-counter" aria-live="polite">
          <strong>{pageCue}</strong>
          <span>
            {page + 1} / {pages.length}
          </span>
        </div>
        {story.characterAsset ? (
          <div className="storybook-character-badge" aria-hidden="true">
            <SafeAssetImage
              src={story.characterAsset}
              alt=""
              loading="lazy"
              decoding="async"
              context={`StoryBookCharacter:${bookId}`}
              fallbackClassName="storybook-badge-fallback"
              fallbackText=""
            />
          </div>
        ) : null}
        <button
          className="storybook-nav prev"
          onClick={() => go(-1)}
          disabled={page === 0}
          aria-label="Previous page"
        >
          ‹
        </button>
        <button
          className="storybook-nav next"
          onClick={() => go(1)}
          disabled={page === pages.length - 1}
          aria-label="Next page"
        >
          ›
        </button>
        <button
          className={`storybook-card storybook-open-book ${isOpening ? "opening" : ""}`}
          onClick={() => (page === pages.length - 1 ? goToPage(0) : go(1))}
          aria-label="Turn story page"
        >
          <span className="storybook-page-left">
            <SafeAssetImage
              src={current.image}
              alt=""
              decoding="async"
              context={`StoryBookPage:${bookId}:${page}`}
              fallbackClassName="storybook-image-fallback"
              fallbackText="Story picture is loading."
            />
          </span>
          <span className="storybook-book-gutter" aria-hidden="true" />
          <span className="storybook-page-right">
            <span className="storybook-page-text" aria-live="polite">
              {current.text}
            </span>
          </span>
        </button>
        <button
          className="storybook-big-next"
          onClick={() => {
            if (page === pages.length - 1) {
              goToPage(0);
            } else {
              go(1);
            }
          }}
        >
          {bigNextLabel}
        </button>
        {wordButtons.length ? (
          <div className="storybook-word-tools" aria-label="Hear story words">
            {wordButtons.map((word, i) => {
              const canPlayWord = hasNarrationClip(word.audio);
              return (
                <button
                  key={`${word.label}-${i}`}
                  className="storybook-word-button"
                  type="button"
                  onClick={() => {
                    if (canPlayWord) playNarration(word.audio);
                  }}
                  disabled={!canPlayWord}
                  aria-label={`Hear the word ${word.label}`}
                >
                  <span aria-hidden="true">🔊</span>
                  {word.label}
                </button>
              );
            })}
          </div>
        ) : null}
        {storyComplete ? (
          <div className="storybook-complete" role="status" aria-live="polite">
            <span className="storybook-complete-stars" aria-hidden="true">
              ⭐ ✦ ⭐
            </span>
            <strong>Story finished!</strong>
            <span>You turned every page.</span>
            <div className="storybook-complete-actions">
              <button type="button" onClick={() => goToPage(0)}>
                Read again
              </button>
              <button type="button" onClick={returnToShelf}>
                Story shelf
              </button>
            </div>
          </div>
        ) : null}
      </div>
      {hasAudio ? (
        <div className="storybook-audio-tools">
          <button
            className="storybook-replay"
            onClick={replay}
            aria-label="Read this page again"
          >
            🔊 Read again
          </button>
          <span>AI narrator</span>
        </div>
      ) : null}
      <div className="storybook-dots" aria-label="Story pages">
        {pages.map((_, i) => (
          <button
            key={i}
            className={i === page ? "on" : ""}
            type="button"
            aria-label={`Go to page ${i + 1}`}
            aria-current={i === page ? "page" : undefined}
            onClick={() => goToPage(i)}
          />
        ))}
      </div>
    </div>
  );
}

function StoryShelfPage({ onRead, onExit }) {
  const [selectedBook, setSelectedBook] = useState(null);
  const books = storyShelfBooks();
  const selected = selectedBook
    ? books.find((book) => book.id === selectedBook) || books[0] || null
    : null;
  const shelfHotspotBooks = books.slice(0, 16);
  const showShelfGrid = books.length > shelfHotspotBooks.length;
  const storyShelfAsset =
    window.SpaceExplorerFoundation?.getGeneratedAsset?.(
      "storyShelf",
      GENERATED_ASSETS.storyShelf,
    ) || GENERATED_ASSETS.storyShelf;

  const chooseBook = (id) => {
    const nextBook = resolveStoryBookId(id) || books[0]?.id;
    if (!nextBook) return;
    if (selectedBook === nextBook) {
      onRead(nextBook);
      return;
    }
    setSelectedBook(nextBook);
    playKidSound("boop");
  };

  if (!books.length) {
    return (
      <div className="story-shelf-page">
        <Starfield count={240} />
        <button className="storybook-close" onClick={onExit}>
          ← Back to planets
        </button>
        <div className="storybook-empty" role="status">
          <strong>Story shelf is loading.</strong>
          <span>Come back from the planets screen and try again.</span>
        </div>
      </div>
    );
  }

  return (
    <div className="story-shelf-page">
      <Starfield count={240} />
      <button className="storybook-close" onClick={onExit}>
        ← Back to planets
      </button>
      <section
        className={`story-shelf-panel ${showShelfGrid ? "has-book-grid" : ""}`}
        aria-label="Story books"
      >
        <div className="story-shelf-heading">
          <span>Adam's Books</span>
          <h1>Story shelf</h1>
          {showShelfGrid ? (
            <p>Pick from the shelf or the book cards below.</p>
          ) : null}
        </div>
        <div className={`story-shelf-stage ${selected ? "has-selection" : ""}`}>
          <SafeAssetImage
            className="story-shelf-art"
            src={storyShelfAsset}
            alt=""
            decoding="async"
            context="StoryShelfPage:shelfArt"
            fallbackClassName="story-shelf-art-fallback"
            fallbackText="Story shelf is loading."
          />
          <div className="story-shelf-hotspots" aria-label="Choose a book">
            {shelfHotspotBooks.map(({ id, story }) => {
              const selectedClass = selectedBook === id ? "selected" : "";
              const shapeClass = normalizeStoryClassToken(story.imageShape);
              return (
                <button
                  key={id}
                  className={`story-shelf-hotspot ${shapeClass} ${selectedClass}`}
                  onClick={() => chooseBook(id)}
                  aria-label={`Pull out ${story.title}`}
                >
                  <span>{story.shelfTitle}</span>
                </button>
              );
            })}
          </div>
          {selected ? (
            <button
              className={`story-pulled-book ${normalizeStoryClassToken(
                selected.story.imageShape,
              )}`}
              onClick={() => onRead(selected.id)}
              aria-label={`Open ${selected.story.title}`}
            >
              <div className="story-pulled-cover">
                {selected.coverImage ? (
                  <SafeAssetImage
                    src={selected.coverImage}
                    alt=""
                    loading="lazy"
                    decoding="async"
                    context={`StoryShelfCover:${selected.id}`}
                    fallbackClassName="story-cover-fallback"
                    fallbackText="Cover is loading."
                  />
                ) : null}
              </div>
              <div className="story-pulled-copy">
                <span>{selected.story.shelfIcon}</span>
                <strong>{selected.story.title}</strong>
                <small>{selected.story.shelfCue}</small>
                <em>{storyShelfStatus(selected.pages, selected.resumePage)}</em>
                <span className="story-open-cue">Tap to open</span>
              </div>
            </button>
          ) : null}
        </div>
        <div className="story-shelf-fallback">
          {books.map(({ id, story, pages, coverImage, resumePage }) => {
            const shapeClass = normalizeStoryClassToken(story.imageShape);
            return (
              <button
                key={id}
                className={`story-shelf-book ${shapeClass}`}
                onClick={() => onRead(id)}
                aria-label={`Read ${story.title}`}
              >
                {coverImage ? (
                  <SafeAssetImage
                    src={coverImage}
                    alt=""
                    loading="lazy"
                    decoding="async"
                    context={`StoryShelfBook:${id}`}
                    fallbackClassName="story-cover-fallback"
                    fallbackText="Cover is loading."
                  />
                ) : null}
                <span className="story-shelf-icon">{story.shelfIcon}</span>
                <strong>{story.shelfTitle}</strong>
                <small>{story.shelfCue}</small>
                <em>{storyShelfStatus(pages, resumePage)}</em>
              </button>
            );
          })}
        </div>
      </section>
    </div>
  );
}

window.SpaceExplorerStorybooks = {
  normalizeStoryBookId,
  normalizeStoryClassToken,
  resolveStoryBookId,
  storyBookFor,
  storyPageCount,
  clampStoryPage,
  normalizeStoryProgress,
  nextStoryPage,
  storyPagesFor,
  storyShelfBooks,
  storyCoverFor,
  readStoryProgress,
  writeStoryProgress,
};
