const {
  useState: useStateAPV,
  useEffect: useEffectAPV,
  useMemo: useMemoAPV,
  useRef: useRefAPV,
} = React;

function estimateAlbumPlayerSubtitleWords(text, start, end, prefix = "line") {
  const tokens = String(text || "").trim().split(/\s+/).filter(Boolean);
  if (!tokens.length) return [];

  const safeStart = Number.isFinite(start) ? start : 0;
  const safeEnd = Math.max(safeStart, Number.isFinite(end) ? end : safeStart + 0.1);
  const duration = Math.max(0.1, safeEnd - safeStart);

  return tokens.map((token, index) => {
    const wordStart = safeStart + (duration * index) / tokens.length;
    const wordEnd = safeStart + (duration * (index + 1)) / tokens.length;
    return {
      id: `${prefix}-word-${index + 1}`,
      text: token,
      start: wordStart,
      end: wordEnd,
    };
  });
}

function collectAlbumPlayerSubtitleLines(song) {
  const lines = [];

  const pushLine = (line, sentenceId = "", fallbackId = "") => {
    if (!line || line.kind === "comment") return;
    const text = String(line.text || "").trim();
    if (!text) return;

    const start = Number(line.start_ms || 0) / 1000;
    const end = Number(line.end_ms || 0) / 1000;
    const id = line.id || fallbackId || `line-${lines.length + 1}`;
    const words = (line.children || [])
      .filter((child) => child?.type === "word" && String(child.text || "").trim())
      .map((word, index) => ({
        id: word.id || `${id}-word-${index + 1}`,
        text: String(word.text || "").trim(),
        start: Number(word.start_ms || 0) / 1000,
        end: Number(word.end_ms || 0) / 1000,
      }));
    const normalizedWords = words.length
      ? words
      : estimateAlbumPlayerSubtitleWords(text, start, end, id);

    lines.push({
      id,
      sentenceId,
      text,
      start,
      end: Math.max(start, end, normalizedWords[normalizedWords.length - 1]?.end || end),
      words: normalizedWords,
    });
  };

  const walk = (node, sentenceId = "") => {
    if (!node) return;
    const nextSentenceId = node.type === "sentence" ? (node.id || sentenceId) : sentenceId;
    if (node.type === "line") {
      pushLine(node, nextSentenceId, `${nextSentenceId || "sentence"}-line-${lines.length + 1}`);
      return;
    }
    (node.children || []).forEach((child) => walk(child, nextSentenceId));
  };

  if (song?.lyricTimeline?.root) {
    walk(song.lyricTimeline.root);
  }

  if (lines.length) {
    return lines.sort((left, right) => left.start - right.start || left.end - right.end);
  }

  return (song?.cues || [])
    .map((cue, index) => {
      const text = String(cue?.text || "").trim();
      if (!text) return null;
      const start = Number(cue.start || 0);
      const end = Number(cue.end || 0);
      const id = cue.id || cue.sentenceId || `cue-line-${index + 1}`;
      const words = estimateAlbumPlayerSubtitleWords(text, start, end, id);
      return {
        id,
        sentenceId: cue.sentenceId || cue.id || "",
        text,
        start,
        end: Math.max(start, end, words[words.length - 1]?.end || end),
        words,
      };
    })
    .filter(Boolean);
}

function getAlbumPlayerSubtitleIndex(lines, currentTime) {
  if (!Array.isArray(lines) || !lines.length) return -1;
  if (currentTime < lines[0].start) return -1;

  for (let index = lines.length - 1; index >= 0; index -= 1) {
    if (currentTime >= lines[index].start) return index;
  }

  return -1;
}

function getAlbumPlayerActiveWordId(line, currentTime) {
  if (!line?.words?.length || currentTime < line.start || currentTime > line.end) return "";

  for (let index = 0; index < line.words.length; index += 1) {
    const word = line.words[index];
    const nextStart = line.words[index + 1]?.start;
    const effectiveEnd = Number.isFinite(nextStart)
      ? nextStart
      : Math.max(line.end, word.end, word.start);
    if (currentTime >= word.start && currentTime < effectiveEnd) return word.id;
  }

  return line.words[line.words.length - 1]?.id || "";
}

function AlbumPlayerSubtitleLineText({ line, activeWordId = "", highlightWord = false }) {
  if (!line) return null;
  const words = Array.isArray(line.words) && line.words.length
    ? line.words
    : estimateAlbumPlayerSubtitleWords(line.text, line.start, line.end, line.id);

  return (
    <>
      {words.map((word, index) => (
        <React.Fragment key={word.id || `${line.id}-word-${index + 1}`}>
          <span className={`subtitle-word ${highlightWord && activeWordId === word.id ? "is-active" : ""}`}>
            {word.text}
          </span>
          {index < words.length - 1 && <span className="subtitle-gap" aria-hidden="true"> </span>}
        </React.Fragment>
      ))}
    </>
  );
}

function AlbumPlayerView({
  albums,
  selectedAlbumId,
  onSelectAlbum,
  initialAlbumId = null,
  initialSongId = null,
  loading = false,
  error = "",
  onRefresh = null,
  onExit,
  copy,
  hasAudioAsset,
  hasVideoAsset,
  resolveAudioUrl,
  resolveVideoUrl,
  getAlbumItems,
  getAlbumFromItem,
  getItemId,
  getCardTitle,
  getCardSubtitle,
  getCardBadge,
  getPanelPublisher,
  getPanelLabel,
  getSelectedMissingText,
}) {
  const { Icon, AlbumCover, SceneImage, fmtTime } = window;
  const [internalSelectedAlbumId, setInternalSelectedAlbumId] = useStateAPV(initialAlbumId);
  const [playerMode, setPlayerMode] = useStateAPV("audio");
  const [playScope, setPlayScope] = useStateAPV("song");
  const [currentIdx, setCurrentIdx] = useStateAPV(0);
  const [playing, setPlaying] = useStateAPV(false);
  const [queueSongs, setQueueSongs] = useStateAPV([]);
  const [currentTimeSec, setCurrentTimeSec] = useStateAPV(0);
  const [mediaUrl, setMediaUrl] = useStateAPV("");
  const [mediaLoading, setMediaLoading] = useStateAPV(false);
  const mediaRef = useRefAPV(null);
  const controlledSelection = typeof selectedAlbumId !== "undefined";
  const activeSelectedAlbumId = controlledSelection ? selectedAlbumId : internalSelectedAlbumId;
  const albumItems = useMemoAPV(() => getAlbumItems(albums || []), [albums, getAlbumItems]);
  const selectedItem = albumItems.find((item) => getItemId(item) === activeSelectedAlbumId) || null;
  const selectedAlbum = selectedItem ? getAlbumFromItem(selectedItem) : null;
  const playableAudioSongs = (selectedAlbum?.songs || []).filter(hasAudioAsset);
  const playableVideoSongs = (selectedAlbum?.songs || []).filter(hasVideoAsset);
  const activeSongs = queueSongs;
  const currentSong = activeSongs[currentIdx] || null;
  const subtitleLines = useMemoAPV(
    () => collectAlbumPlayerSubtitleLines(currentSong || {}),
    [currentSong?.id, currentSong?.lyricTimeline, currentSong?.cues]
  );
  const activeSubtitleIndex = useMemoAPV(
    () => getAlbumPlayerSubtitleIndex(subtitleLines, currentTimeSec),
    [subtitleLines, currentTimeSec]
  );
  const activeSubtitleLine = activeSubtitleIndex >= 0 ? subtitleLines[activeSubtitleIndex] : null;
  const activeWordId = useMemoAPV(
    () => getAlbumPlayerActiveWordId(activeSubtitleLine, currentTimeSec),
    [activeSubtitleLine, currentTimeSec]
  );
  const subtitleWindow = useMemoAPV(() => {
    if (!activeSubtitleLine || activeSubtitleIndex < 0) return [];
    return [activeSubtitleIndex - 1, activeSubtitleIndex, activeSubtitleIndex + 1]
      .map((index) => ({ line: subtitleLines[index] || null, slot: index - activeSubtitleIndex }))
      .filter((item) => item.line);
  }, [activeSubtitleLine, activeSubtitleIndex, subtitleLines]);
  const renderSubtitleWindow = (className) => (
    <div
      className={`video-subtitle lines-mode ${className}`}
      style={{
        "--subtitle-color": "#ffffff",
        "--subtitle-shadow": "0 2px 14px rgba(0,0,0,0.72)",
        "--subtitle-cloud": "none",
      }}
    >
      <div className="subtitle-stack">
        {subtitleWindow.map(({ line, slot }) => (
          <div key={line.id} className={`subtitle-line ${slot === 0 ? "is-current" : slot < 0 ? "is-prev" : "is-next"}`}>
            <span className="subtitle-line-chip">
              <AlbumPlayerSubtitleLineText line={line} activeWordId={activeWordId} highlightWord={slot === 0}/>
            </span>
          </div>
        ))}
      </div>
    </div>
  );

  window.useWakeLock(playing);

  const setSelectedAlbum = (id) => {
    if (controlledSelection) {
      onSelectAlbum?.(id);
      return;
    }
    setInternalSelectedAlbumId(id);
  };

  const resolvePlayableMode = (song, preferredMode) => {
    const preferredAvailable = preferredMode === "video" ? hasVideoAsset(song) : hasAudioAsset(song);
    if (preferredAvailable) return preferredMode;
    if (preferredMode === "video" && hasAudioAsset(song)) return "audio";
    if (preferredMode === "audio" && hasVideoAsset(song)) return "video";
    return null;
  };

  const buildQueue = (mode, scope, anchorSongId) => {
    const source = mode === "video" ? playableVideoSongs : playableAudioSongs;
    if (!source.length) return { songs: [], startIndex: 0 };
    const index = anchorSongId ? source.findIndex((item) => item.id === anchorSongId) : -1;
    const anchorIdx = index >= 0 ? index : 0;
    if (scope === "song") return { songs: [source[anchorIdx]], startIndex: 0 };
    return { songs: source, startIndex: anchorIdx };
  };

  const applyPlayback = ({ mode, scope, anchorSongId, autoplay }) => {
    const { songs, startIndex } = buildQueue(mode, scope, anchorSongId);
    setPlayerMode(mode);
    setPlayScope(scope);
    setQueueSongs(songs);
    setCurrentIdx(startIndex);
    setPlaying(Boolean(autoplay && songs.length));
  };

  useEffectAPV(() => {
    if (controlledSelection) return;
    if (typeof initialAlbumId === "string" && initialAlbumId.trim()) {
      setInternalSelectedAlbumId(initialAlbumId);
      return;
    }
    setInternalSelectedAlbumId((current) => (albumItems.some((item) => getItemId(item) === current) ? current : null));
  }, [controlledSelection, initialAlbumId, albumItems, getItemId]);

  useEffectAPV(() => {
    setCurrentIdx(0);
    setPlaying(false);
    setQueueSongs([]);
    setPlayerMode("audio");
    setPlayScope("song");
    setCurrentTimeSec(0);
    setMediaUrl("");
  }, [activeSelectedAlbumId]);

  useEffectAPV(() => {
    if (!selectedAlbum || !initialSongId) return;
    const found = selectedAlbum.songs.find((song) => song.id === initialSongId);
    const mode = found ? resolvePlayableMode(found, playerMode) : null;
    if (!found || !mode) return;
    setPlayerMode(mode);
    setQueueSongs([found]);
    setCurrentIdx(0);
    setPlaying(true);
  }, [initialSongId, activeSelectedAlbumId, selectedAlbum]);

  useEffectAPV(() => {
    let cancelled = false;
    const run = async () => {
      if (!currentSong) {
        setMediaUrl("");
        return;
      }
      setMediaLoading(true);
      try {
        const nextUrl = playerMode === "video"
          ? await resolveVideoUrl(currentSong)
          : await resolveAudioUrl(currentSong);
        if (!cancelled) setMediaUrl(nextUrl || "");
      } catch {
        if (!cancelled) setMediaUrl("");
      } finally {
        if (!cancelled) setMediaLoading(false);
      }
    };
    run();
    return () => { cancelled = true; };
  }, [currentSong?.id, playerMode, resolveAudioUrl, resolveVideoUrl]);

  useEffectAPV(() => {
    const media = mediaRef.current;
    if (!media) return;
    if (playing) {
      const promise = media.play();
      if (promise?.catch) promise.catch(() => setPlaying(false));
      return;
    }
    media.pause();
  }, [playing, currentSong?.id, playerMode, mediaUrl]);

  useEffectAPV(() => {
    setCurrentTimeSec(0);
  }, [currentSong?.id, playerMode]);

  useEffectAPV(() => {
    const media = mediaRef.current;
    if (!media) return;
    const handleTimeUpdate = () => setCurrentTimeSec(Number(media.currentTime || 0));
    media.addEventListener("timeupdate", handleTimeUpdate);
    return () => media.removeEventListener("timeupdate", handleTimeUpdate);
  }, [currentSong?.id, playerMode, mediaUrl]);

  const handleModeChange = (nextMode) => {
    const anchorSongId = currentSong?.id || selectedAlbum?.songs?.[0]?.id || null;
    applyPlayback({ mode: nextMode, scope: playScope, anchorSongId, autoplay: playing });
  };

  const handleScopeChange = (nextScope) => {
    const anchorSongId = currentSong?.id || selectedAlbum?.songs?.[0]?.id || null;
    applyPlayback({ mode: playerMode, scope: nextScope, anchorSongId, autoplay: playing });
  };

  const startPlaybackFromTop = () => {
    if (playing) {
      setPlaying(false);
      return;
    }
    applyPlayback({
      mode: playerMode,
      scope: playScope,
      anchorSongId: currentSong?.id || null,
      autoplay: true,
    });
  };

  const playSong = (song) => {
    const mode = resolvePlayableMode(song, playerMode);
    if (!mode) return;
    const source = mode === "video" ? playableVideoSongs : playableAudioSongs;
    const anchorSongId = source.find((item) => item.id === song.id)?.id || source[0]?.id || null;
    if (!anchorSongId) return;
    const queue = playScope === "album" ? source : source.filter((item) => item.id === anchorSongId);
    const nextIdx = playScope === "album"
      ? Math.max(0, queue.findIndex((item) => item.id === anchorSongId))
      : 0;
    setPlayerMode(mode);
    setQueueSongs(queue);
    setCurrentIdx(nextIdx);
    setPlaying(true);
  };

  const handleEnded = () => {
    if (currentIdx < activeSongs.length - 1) {
      setCurrentIdx((prev) => prev + 1);
      setPlaying(true);
      return;
    }
    setPlaying(false);
    setCurrentTimeSec(0);
  };

  const goPrevTrack = () => {
    if (currentIdx <= 0) return;
    setCurrentIdx((prev) => Math.max(0, prev - 1));
    setPlaying(true);
  };

  const goNextTrack = () => {
    if (currentIdx >= activeSongs.length - 1) return;
    setCurrentIdx((prev) => Math.min(activeSongs.length - 1, prev + 1));
    setPlaying(true);
  };

  const toggleAlbum = (id) => {
    const nextId = activeSelectedAlbumId === id ? null : id;
    setPlaying(false);
    setSelectedAlbum(nextId);
  };

  return (
    <div className="public-albums-view">
      <div className="public-albums-header">
        <div>
          <div className="label">{copy.headerLabel}</div>
          <div className="title">{copy.title}</div>
          <div className="subtitle">{copy.subtitle}</div>
        </div>
        <div className="public-albums-header-actions">
          {onRefresh && (
            <button className="pill-btn" onClick={onRefresh} disabled={loading}>
              <Icon.Refresh size={12}/> {loading ? "새로고침 중..." : "새로고침"}
            </button>
          )}
          <button className="pill-btn" onClick={onExit}><Icon.X size={12}/> 닫기</button>
        </div>
      </div>

      {error && <div className="public-albums-error">{error}</div>}

      <div className="public-album-list">
        {loading && !albumItems.length && <div className="empty-state">{copy.loadingText}</div>}
        {!loading && !albumItems.length && <div className="empty-state">{copy.emptyText}</div>}
        {albumItems.map((item) => {
          const album = getAlbumFromItem(item);
          const itemId = getItemId(item);
          const expanded = itemId === activeSelectedAlbumId;
          return (
            <div key={itemId} className={`public-album-row ${expanded ? "expanded" : ""}`}>
              <button
                className="public-album-card"
                onClick={() => toggleAlbum(itemId)}
                aria-expanded={expanded}
              >
                <div className="public-album-card-cover">
                  {album ? <AlbumCover album={album} /> : <Icon.Album size={22}/>}
                </div>
                <div className="public-album-card-meta">
                  <div className="public-album-card-title">{getCardTitle(item, album)}</div>
                  <div className="public-album-card-sub">{getCardSubtitle(item, album)}</div>
                </div>
                <div className="public-album-card-actions">
                  <span className="mono">{getCardBadge(item, album)}</span>
                  {expanded ? <Icon.ChevronUp size={16}/> : <Icon.ChevronDown size={16}/>}
                </div>
              </button>

              {expanded && album && (
                <div className="public-album-panel">
                  <div className="public-album-panel-head">
                    <div>
                      <div className="label">{getPanelLabel(item, album)}</div>
                      <div className="public-album-panel-title">{album.title}</div>
                      {getPanelPublisher(item, album) && (
                        <div className="public-album-panel-publisher">{getPanelPublisher(item, album)}</div>
                      )}
                    </div>
                    <div className="public-album-play-actions">
                      <div className="public-play-toggle-group" role="radiogroup" aria-label="재생 범위">
                        <button type="button" className={`pill-btn sm ${playScope === "song" ? "accent" : ""}`} onClick={() => handleScopeChange("song")} aria-pressed={playScope === "song"}>
                          <span className="btn-label">한 곡</span>
                        </button>
                        <button type="button" className={`pill-btn sm ${playScope === "album" ? "accent" : ""}`} onClick={() => handleScopeChange("album")} aria-pressed={playScope === "album"}>
                          <span className="btn-label">앨범</span>
                        </button>
                      </div>
                      <div className="public-play-segment" role="radiogroup" aria-label="재생 미디어">
                        <button type="button" className={playerMode === "audio" ? "active" : ""} onClick={() => handleModeChange("audio")} aria-pressed={playerMode === "audio"}>Audio</button>
                        <button type="button" className={playerMode === "video" ? "active" : ""} onClick={() => handleModeChange("video")} aria-pressed={playerMode === "video"}>Video</button>
                      </div>
                      <button
                        className="pill-btn accent"
                        onClick={startPlaybackFromTop}
                        disabled={!playableAudioSongs.length && !playableVideoSongs.length}
                        aria-label={playing ? "일시정지" : "현재 설정으로 재생 시작"}
                        title={playing ? "일시정지" : "현재 설정으로 재생 시작"}
                      >
                        {playing ? <Icon.Pause size={12}/> : <Icon.Play size={12}/>} <span className="btn-label">{playing ? "일시정지" : "재생 시작"}</span>
                      </button>
                    </div>
                  </div>

                  {currentSong && mediaUrl && (
                    <div className="public-player">
                      <div className="public-player-meta">
                        <span className="mono">{playerMode === "video" ? "VIDEO" : "MP3"}</span>
                        <strong>{currentSong.title}</strong>
                        <span>{activeSongs.length ? `${currentIdx + 1}/${activeSongs.length}` : ""}</span>
                      </div>
                      <div className="public-player-controls">
                        <button type="button" className="pill-btn sm" onClick={goPrevTrack} disabled={currentIdx <= 0} aria-label="이전 곡" title="이전 곡">
                          <Icon.ArrowLeft size={11}/> <span className="btn-label">이전</span>
                        </button>
                        <button type="button" className="pill-btn sm" onClick={goNextTrack} disabled={currentIdx >= activeSongs.length - 1} aria-label="다음 곡" title="다음 곡">
                          <span className="btn-label">다음</span> <Icon.ArrowRight size={11}/>
                        </button>
                      </div>
                      {playerMode === "video" ? (
                        <video key={mediaUrl} ref={mediaRef} className="public-video-player" src={mediaUrl} controls preload="metadata" onPlay={() => setPlaying(true)} onPause={() => setPlaying(false)} onEnded={handleEnded} />
                      ) : (
                        <div className="public-audio-player-wrap">
                          <audio key={mediaUrl} ref={mediaRef} src={mediaUrl} controls preload="metadata" onPlay={() => setPlaying(true)} onPause={() => setPlaying(false)} onEnded={handleEnded} />
                        </div>
                      )}
                      {playerMode === "audio" && subtitleWindow.length > 0 && renderSubtitleWindow("public-inline-subtitles")}
                    </div>
                  )}
                  {mediaLoading && <div className="public-player-meta"><span className="mono">로딩 중...</span></div>}

                  <div className="public-song-table">
                    {(album.songs || []).map((song, index) => {
                      const hasAudio = hasAudioAsset(song);
                      const hasVideo = hasVideoAsset(song);
                      const isCurrentRow = currentSong?.id === song.id && (playerMode === "audio" || playerMode === "video");
                      const isActuallyPlaying = isCurrentRow && playing;
                      const canPlay = hasAudio || hasVideo;
                      return (
                        <div
                          key={song.id || index}
                          className={`public-song-row ${isCurrentRow ? "is-current" : ""} ${isActuallyPlaying ? "is-playing" : ""} ${canPlay ? "can-play" : "is-disabled"}`}
                          aria-current={isCurrentRow ? "true" : undefined}
                          onClick={canPlay ? () => playSong(song) : undefined}
                          role={canPlay ? "button" : undefined}
                          tabIndex={canPlay ? 0 : -1}
                          onKeyDown={canPlay ? (event) => {
                            if (event.key === "Enter" || event.key === " ") {
                              event.preventDefault();
                              playSong(song);
                            }
                          } : undefined}
                        >
                          <div className="mono">{String(index + 1).padStart(2, "0")}</div>
                          <div>
                            <div className="song-title">
                              {song.title || song.filename || "Untitled"}
                              {isActuallyPlaying && <span className="now-playing-badge">재생 중</span>}
                            </div>
                            <div className="song-sub mono">{song.filename || fmtTime(song.duration || 0)}</div>
                          </div>
                          <div className="public-song-thumb">
                            {song.scenes?.[0] && <SceneImage scene={song.scenes[0]} paletteName={song.paletteName || "midnight-violet"}/>}
                          </div>
                          <button
                            className="pill-btn sm"
                            onClick={(event) => {
                              event.stopPropagation();
                              playSong(song);
                            }}
                            disabled={!canPlay}
                            aria-label={`${song.title || song.filename || "Untitled"} 재생`}
                            title="재생"
                          >
                            {isCurrentRow && isActuallyPlaying ? <Icon.Pause size={11}/> : <Icon.Play size={11}/>} <span className="btn-label">{isCurrentRow && isActuallyPlaying ? "재생 중" : "PLAY"}</span>
                          </button>
                        </div>
                      );
                    })}
                  </div>
                </div>
              )}
            </div>
          );
        })}
        {activeSelectedAlbumId && !selectedAlbum && !loading && (
          <div className="empty-state">{getSelectedMissingText()}</div>
        )}
      </div>

      {playerMode === "audio" && subtitleWindow.length > 0 && renderSubtitleWindow("public-sticky-subtitles")}
    </div>
  );
}

Object.assign(window, { AlbumPlayerView });
