const { useState: useStateLAV, useEffect: useEffectLAV, useMemo: useMemoLAV, useRef: useRefLAV } = React;

const LOCAL_PLAYER_STORAGE_BASE = window.SongfilmApiConfig.getBase("storage");
const LOCAL_PLAYER_RENDERER_BASE = window.SongfilmApiConfig.getBase("remotion");

function estimateLocalSubtitleWords(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}-${index + 1}`, text: token, start: wordStart, end: wordEnd };
  });
}

function collectLocalSubtitleLines(song) {
  const lines = [];
  const pushLine = (line, sentenceId = "", fallback = "") => {
    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 || fallback || `line-${lines.length + 1}`;
    const words = (line.children || [])
      .filter((word) => word?.type === "word" && String(word.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 : estimateLocalSubtitleWords(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((a, b) => a.start - b.start || a.end - b.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-${index + 1}`;
      return { id, text, start, end, words: estimateLocalSubtitleWords(text, start, end, id) };
    })
    .filter(Boolean);
}

function getLocalSubtitleIndex(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 getLocalActiveWordId(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 LocalSubtitleLineText({ line, activeWordId = "", highlightWord = false }) {
  if (!line) return null;
  const words = Array.isArray(line.words) && line.words.length
    ? line.words
    : estimateLocalSubtitleWords(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 LocalAlbumsPlayerView({ albums, initialAlbumId = null, initialSongId = null, onExit }) {
  const { Icon, AlbumCover, SceneImage, fmtTime } = window;
  const [selectedAlbumId, setSelectedAlbumId] = useStateLAV(initialAlbumId);
  const [playerMode, setPlayerMode] = useStateLAV("audio");
  const [playScope, setPlayScope] = useStateLAV("song");
  const [currentIdx, setCurrentIdx] = useStateLAV(0);
  const [playing, setPlaying] = useStateLAV(false);
  const [queueSongs, setQueueSongs] = useStateLAV([]);
  const [currentTimeSec, setCurrentTimeSec] = useStateLAV(0);
  const [mediaUrl, setMediaUrl] = useStateLAV("");
  const [mediaLoading, setMediaLoading] = useStateLAV(false);
  const mediaRef = useRefLAV(null);
  const audioUrlCacheRef = useRefLAV(new Map());
  const selectedAlbum = albums.find((item) => item.id === selectedAlbumId) || null;

  useEffectLAV(() => {
    if (typeof initialAlbumId === "string" && initialAlbumId.trim()) {
      setSelectedAlbumId(initialAlbumId);
      return;
    }
    setSelectedAlbumId((current) => (albums.some((item) => item.id === current) ? current : null));
  }, [initialAlbumId, albums]);

  const hasAudioAsset = (song) => !!(song?.audioServerUrl || song?.audio?.filename || song?.audio?.url || song?.exportState?.audioFilename || song?.audioStorageKey);
  const hasVideoAsset = (song) => !!(song?.exportState?.videoFilename || song?.exportState?.jobId);
  const playableAudioSongs = (selectedAlbum?.songs || []).filter(hasAudioAsset);
  const playableVideoSongs = (selectedAlbum?.songs || []).filter(hasVideoAsset);
  const activeSongs = queueSongs;
  const currentSong = activeSongs[currentIdx] || null;
  const subtitleLines = useMemoLAV(() => collectLocalSubtitleLines(currentSong || {}), [currentSong?.id, currentSong?.lyricTimeline, currentSong?.cues]);
  const activeSubtitleIndex = useMemoLAV(() => getLocalSubtitleIndex(subtitleLines, currentTimeSec), [subtitleLines, currentTimeSec]);
  const activeSubtitleLine = activeSubtitleIndex >= 0 ? subtitleLines[activeSubtitleIndex] : null;
  const activeWordId = useMemoLAV(() => getLocalActiveWordId(activeSubtitleLine, currentTimeSec), [activeSubtitleLine, currentTimeSec]);
  const subtitleWindow = useMemoLAV(() => {
    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]);

  window.useWakeLock(playing);

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

  useEffectLAV(() => {
    if (!selectedAlbum || !initialSongId) return;
    const found = selectedAlbum.songs.find((song) => song.id === initialSongId);
    if (!found) return;
    setQueueSongs([found]);
    setCurrentIdx(0);
    setPlaying(true);
  }, [initialSongId, selectedAlbumId]);

  const resolveLocalAudioUrl = async (song) => {
    const cached = audioUrlCacheRef.current.get(song.id);
    if (cached) return cached;
    const serverRef = String(song?.audioServerUrl || song?.audio?.filename || song?.audio?.url || song?.exportState?.audioFilename || "").trim();
    if (serverRef) {
      const built = window.SongfilmApiConfig?.buildMediaUrl
        ? window.SongfilmApiConfig.buildMediaUrl("audio", serverRef)
        : `${LOCAL_PLAYER_STORAGE_BASE}/audio/${encodeURIComponent(serverRef)}`;
      audioUrlCacheRef.current.set(song.id, built);
      return built;
    }
    const restored = await window.SongfilmSongStorage?.restoreAudioUrl?.(song);
    if (restored) audioUrlCacheRef.current.set(song.id, restored);
    return restored || "";
  };

  const resolveLocalVideoUrl = (song) => {
    const videoFilename = String(song?.exportState?.videoFilename || "").trim();
    if (videoFilename) {
      return window.SongfilmApiConfig?.buildMediaUrl
        ? window.SongfilmApiConfig.buildMediaUrl("video", videoFilename)
        : `${LOCAL_PLAYER_STORAGE_BASE}/video/${encodeURIComponent(videoFilename)}`;
    }
    const jobId = String(song?.exportState?.jobId || "").trim();
    if (jobId) return `${LOCAL_PLAYER_RENDERER_BASE}/render/${jobId}/file`;
    return "";
  };

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

  useEffectLAV(() => {
    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]);

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

  useEffectLAV(() => {
    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]);

  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));
  };

  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;
    }
    const anchorSongId = currentSong?.id || null;
    applyPlayback({ mode: playerMode, scope: playScope, anchorSongId, 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 = selectedAlbumId === id ? null : id;
    setPlaying(false);
    setSelectedAlbumId(nextId);
  };

  return (
    <div className="public-albums-view">
      <div className="public-albums-header">
        <div>
          <div className="label">MY LIBRARY</div>
          <div className="title">앨범 플레이어</div>
          <div className="subtitle">나의 모든 앨범을 목록에서 펼쳐 오디오/비디오로 재생합니다.</div>
        </div>
        <div className="public-albums-header-actions">
          <button className="pill-btn" onClick={onExit}><Icon.X size={12}/> 닫기</button>
        </div>
      </div>

      <div className="public-album-list">
        {!albums.length && <div className="empty-state">등록된 앨범이 없습니다.</div>}
        {albums.map((album) => {
          const expanded = album.id === selectedAlbumId;
          const songCount = album?.songs?.length || 0;
          return (
            <div key={album.id} className={`public-album-row ${expanded ? "expanded" : ""}`}>
              <button className="public-album-card" onClick={() => toggleAlbum(album.id)} aria-expanded={expanded}>
                <div className="public-album-card-cover">
                  <AlbumCover album={album} />
                </div>
                <div className="public-album-card-meta">
                  <div className="public-album-card-title">{album.title || "Untitled Album"}</div>
                  <div className="public-album-card-sub">{album.artist || "Unknown"} · {songCount}곡</div>
                </div>
                <div className="public-album-card-actions">
                  <span className="mono">{album?.year || "LOCAL"}</span>
                  {expanded ? <Icon.ChevronUp size={16}/> : <Icon.ChevronDown size={16}/>}
                </div>
              </button>

              {expanded && (
                <div className="public-album-panel">
                  <div className="public-album-panel-head">
                    <div>
                      <div className="label">내 앨범 플레이어</div>
                      <div className="public-album-panel-title">{album.title}</div>
                      <div className="public-album-panel-publisher">{album.artist || "Unknown Artist"}</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}>
                        {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}><Icon.ArrowLeft size={11}/> <span className="btn-label">이전</span></button>
                        <button type="button" className="pill-btn sm" onClick={goNextTrack} disabled={currentIdx >= activeSongs.length - 1}><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>
                      )}
                    </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 isCurrentAudio = playerMode === "audio" && currentSong?.id === song.id;
                      const isCurrentVideo = playerMode === "video" && currentSong?.id === song.id;
                      const isCurrentRow = isCurrentAudio || isCurrentVideo;
                      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"}`} onClick={canPlay ? () => playSong(song) : undefined} role={canPlay ? "button" : undefined} tabIndex={canPlay ? 0 : -1}>
                          <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}>
                            {isCurrentRow && isActuallyPlaying ? <Icon.Pause size={11}/> : <Icon.Play size={11}/>} <span className="btn-label">{isCurrentRow && isActuallyPlaying ? "재생 중" : "PLAY"}</span>
                          </button>
                        </div>
                      );
                    })}
                  </div>
                </div>
              )}
            </div>
          );
        })}
      </div>

      {playerMode === "audio" && subtitleWindow.length > 0 && (
        <div className="video-subtitle lines-mode public-sticky-subtitles" 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">
                  <LocalSubtitleLineText line={line} activeWordId={activeWordId} highlightWord={slot === 0}/>
                </span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { LocalAlbumsPlayerView });
