"use client";

import Link from "next/link";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { apiJson } from "@/lib/client/http";
import { ensureSegmentAudio } from "@/lib/client/tts/audio-client";
import { DEFAULT_EDGE_VOICES, synthesizeWithEdge } from "@/lib/client/tts/edge-browser";
import { createSpeechFallback } from "@/lib/client/player/speech-fallback";
import { configureMediaSession } from "@/lib/client/player/media-session";
import { createSleepTimer } from "@/lib/client/player/sleep-timer";
import { seekAcrossQueue } from "@/lib/client/player/queue";
import { ProgressOutbox, type PendingProgress } from "@/lib/client/player/progress-outbox";

interface Book {
  id: string;
  title: string;
  author: string;
  coverUrl: string | null;
  preferredZhVoice: string | null;
  preferredEnVoice: string | null;
  preferredRate: number | null;
}

interface Segment {
  id: string;
  position: number;
  text: string;
  language: "zh-CN" | "en-US";
  textVersion: number;
  included: boolean;
  warning: string | null;
}

interface Chapter {
  id: string;
  title: string;
  position: number;
  segments: Segment[];
}

interface Progress {
  bookId: string;
  chapterId: string;
  segmentId: string;
  offsetMs: number;
  playbackRate: number;
  updatedAt: string;
}

interface QueueEntry extends Segment {
  chapterId: string;
  chapterTitle: string;
}

type PlaybackMode = "audio" | "speech" | null;
type SleepChoice = "off" | "15" | "30" | "45" | "60" | "chapter";

function formatTime(milliseconds: number): string {
  const seconds = Math.max(0, Math.round(milliseconds / 1000));
  const minutes = Math.floor(seconds / 60);
  return `${minutes}:${String(seconds % 60).padStart(2, "0")}`;
}

function estimateDuration(text: string): number {
  const han = text.match(/\p{Script=Han}/gu)?.length ?? 0;
  const words = text.match(/[A-Za-z]+(?:'[A-Za-z]+)?/g)?.length ?? 0;
  return Math.max(1_500, Math.round(han * 310 + words * 390 + 700));
}

export function Player({ bookId }: { bookId: string }) {
  const [book, setBook] = useState<Book | null>(null);
  const [chapters, setChapters] = useState<Chapter[]>([]);
  const [queueIndex, setQueueIndex] = useState(0);
  const [offsetMs, setOffsetMs] = useState(0);
  const [durations, setDurations] = useState<number[]>([]);
  const [rate, setRate] = useState(1);
  const [volume, setVolume] = useState(1);
  const [playing, setPlaying] = useState(false);
  const [generating, setGenerating] = useState(false);
  const [fallbackActive, setFallbackActive] = useState(false);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [message, setMessage] = useState("");
  const [sleepChoice, setSleepChoice] = useState<SleepChoice>("off");
  const [sleepRemaining, setSleepRemaining] = useState<number | null>(null);

  const audioRef = useRef<HTMLAudioElement | null>(null);
  const speechRef = useRef<ReturnType<typeof createSpeechFallback> | null>(null);
  const modeRef = useRef<PlaybackMode>(null);
  const requestTokenRef = useRef(0);
  const playAtRef = useRef<(index: number, startOffsetMs?: number, autoPlay?: boolean) => Promise<void>>(async () => undefined);
  const sleepTimerRef = useRef(createSleepTimer());
  const prefetchedChaptersRef = useRef(new Set<string>());
  const queueIndexRef = useRef(0);
  const offsetRef = useRef(0);
  const rateRef = useRef(1);
  const volumeRef = useRef(1);
  const playingRef = useRef(false);
  const progressOutboxRef = useRef<ProgressOutbox | null>(null);

  const queue = useMemo<QueueEntry[]>(() =>
    [...chapters]
      .sort((left, right) => left.position - right.position)
      .flatMap((chapter) => [...chapter.segments]
        .sort((left, right) => left.position - right.position)
        .filter((segment) => segment.included)
        .map((segment) => ({ ...segment, chapterId: chapter.id, chapterTitle: chapter.title }))),
  [chapters]);

  const current = queue[queueIndex] ?? null;
  const currentChapter = chapters.find((chapter) => chapter.id === current?.chapterId) ?? chapters[0] ?? null;

  useEffect(() => { queueIndexRef.current = queueIndex; }, [queueIndex]);
  useEffect(() => { offsetRef.current = offsetMs; }, [offsetMs]);
  useEffect(() => { rateRef.current = rate; if (audioRef.current) audioRef.current.playbackRate = rate; }, [rate]);
  useEffect(() => { volumeRef.current = volume; if (audioRef.current) audioRef.current.volume = volume; }, [volume]);
  useEffect(() => { playingRef.current = playing; }, [playing]);

  useEffect(() => {
    const audio = new Audio();
    audio.preload = "auto";
    audioRef.current = audio;
    speechRef.current = createSpeechFallback();
    return () => {
      requestTokenRef.current += 1;
      audio.pause();
      audio.removeAttribute("src");
      audio.load();
      speechRef.current?.cancel();
      audioRef.current = null;
    };
  }, []);

  useEffect(() => {
    progressOutboxRef.current = new ProgressOutbox(window.localStorage);
    return () => { progressOutboxRef.current = null; };
  }, []);

  useEffect(() => {
    let active = true;
    void Promise.all([
      apiJson<{ book: Book }>(`/api/books/${bookId}`),
      apiJson<{ chapters: Chapter[] }>(`/api/books/${bookId}/chapters`),
      apiJson<{ progress: Progress | null }>(`/api/books/${bookId}/progress`),
    ]).then(([bookResult, chapterResult, progressResult]) => {
      if (!active) return;
      setBook(bookResult.book);
      setChapters(chapterResult.chapters);
      const entries = chapterResult.chapters
        .flatMap((chapter) => chapter.segments.filter((segment) => segment.included)
          .map((segment) => ({ segment, chapter })));
      const restoredIndex = progressResult.progress
        ? entries.findIndex(({ segment }) => segment.id === progressResult.progress?.segmentId)
        : -1;
      setQueueIndex(Math.max(0, restoredIndex));
      setOffsetMs(restoredIndex >= 0 ? progressResult.progress?.offsetMs ?? 0 : 0);
      const savedRate = progressResult.progress?.playbackRate ?? bookResult.book.preferredRate ?? 1;
      setRate(Math.min(3, Math.max(0.5, savedRate)));
      setDurations(entries.map(({ segment }) => estimateDuration(segment.text)));
    }).catch((cause: unknown) => {
      if (active) setError(cause instanceof Error ? cause.message : "播放器无法加载。");
    }).finally(() => {
      if (active) setLoading(false);
    });
    return () => { active = false; };
  }, [bookId]);

  const voiceFor = useCallback((segment: Segment): string => {
    if (segment.language === "zh-CN") return book?.preferredZhVoice || DEFAULT_EDGE_VOICES["zh-CN"];
    return book?.preferredEnVoice || DEFAULT_EDGE_VOICES["en-US"];
  }, [book?.preferredEnVoice, book?.preferredZhVoice]);

  const saveProgress = useCallback(async (index = queueIndexRef.current, position = offsetRef.current) => {
    const entry = queue[index];
    if (!entry) return;
    const pending: PendingProgress = {
      chapterId: entry.chapterId,
      segmentId: entry.id,
      offsetMs: Math.max(0, Math.round(position)),
      playbackRate: rateRef.current,
      updatedAt: new Date().toISOString(),
    };
    progressOutboxRef.current?.save(bookId, pending);
    try {
      await apiJson(`/api/books/${bookId}/progress`, {
        method: "PUT",
        headers: { "content-type": "application/json" },
        body: JSON.stringify(pending),
      });
      progressOutboxRef.current?.clear(bookId, pending.updatedAt);
    } catch {
      setMessage("这次播放位置尚未同步；网络恢复后会再次保存。");
    }
  }, [bookId, queue]);

  useEffect(() => {
    const flushPending = async () => {
      const pending = progressOutboxRef.current?.get(bookId);
      if (!pending) return;
      try {
        await apiJson(`/api/books/${bookId}/progress`, {
          method: "PUT",
          headers: { "content-type": "application/json" },
          body: JSON.stringify(pending),
        });
        progressOutboxRef.current?.clear(bookId, pending.updatedAt);
        setMessage("");
      } catch {
        setMessage("播放位置仍在等待同步。");
      }
    };
    window.addEventListener("online", flushPending);
    void flushPending();
    return () => window.removeEventListener("online", flushPending);
  }, [bookId]);

  const stopPlayback = useCallback((reason?: string) => {
    requestTokenRef.current += 1;
    audioRef.current?.pause();
    speechRef.current?.cancel();
    modeRef.current = null;
    setPlaying(false);
    setGenerating(false);
    if (reason) setMessage(reason);
    void saveProgress();
  }, [saveProgress]);

  const prefetchChapter = useCallback(async (chapterId: string) => {
    if (prefetchedChaptersRef.current.has(chapterId)) return;
    prefetchedChaptersRef.current.add(chapterId);
    const chapter = chapters.find((candidate) => candidate.id === chapterId);
    if (!chapter) return;
    for (const segment of chapter.segments.filter((candidate) => candidate.included)) {
      try {
        await ensureSegmentAudio({
          segmentId: segment.id,
          voice: voiceFor(segment),
          rate: 1,
          synthesize: synthesizeWithEdge,
        });
      } catch {
        prefetchedChaptersRef.current.delete(chapterId);
        return;
      }
    }
  }, [chapters, voiceFor]);

  const prefetchNextChapter = useCallback((chapterId: string) => {
    const ordered = [...chapters].sort((left, right) => left.position - right.position);
    const index = ordered.findIndex((chapter) => chapter.id === chapterId);
    const next = ordered[index + 1];
    if (next) void prefetchChapter(next.id);
  }, [chapters, prefetchChapter]);

  const playAt = useCallback(async (index: number, startOffsetMs = 0, autoPlay = true): Promise<void> => {
    const entry = queue[index];
    const audio = audioRef.current;
    if (!entry || !audio) return;
    const token = ++requestTokenRef.current;
    audio.pause();
    speechRef.current?.cancel();
    modeRef.current = null;
    setQueueIndex(index);
    setOffsetMs(Math.max(0, startOffsetMs));
    setError("");
    setMessage("");
    if (!autoPlay) {
      setPlaying(false);
      return;
    }
    setPlaying(true);
    setGenerating(true);
    try {
      const stored = await ensureSegmentAudio({
        segmentId: entry.id,
        voice: voiceFor(entry),
        rate: 1,
        synthesize: synthesizeWithEdge,
      });
      if (token !== requestTokenRef.current) return;
      audio.src = stored.url;
      audio.playbackRate = rateRef.current;
      audio.volume = volumeRef.current;
      audio.onloadedmetadata = () => {
        const durationMs = Number.isFinite(audio.duration) ? audio.duration * 1000 : estimateDuration(entry.text);
        setDurations((values) => values.map((value, durationIndex) => durationIndex === index ? durationMs : value));
        audio.currentTime = Math.min(Math.max(0, startOffsetMs / 1000), Math.max(0, audio.duration - 0.05));
      };
      audio.ontimeupdate = () => {
        const nextOffset = Math.round(audio.currentTime * 1000);
        offsetRef.current = nextOffset;
        setOffsetMs(nextOffset);
        if ("mediaSession" in navigator && Number.isFinite(audio.duration) && audio.duration > 0) {
          try { navigator.mediaSession.setPositionState({ duration: audio.duration, playbackRate: audio.playbackRate, position: Math.min(audio.currentTime, audio.duration) }); } catch { /* unsupported */ }
        }
      };
      audio.onended = () => {
        const next = queue[index + 1] ?? null;
        if (sleepTimerRef.current.shouldStopAtChapterBoundary(entry.chapterId, next?.chapterId ?? null)) {
          sleepTimerRef.current.cancel();
          setSleepChoice("off");
          stopPlayback("已在本章结束处停止播放。");
          return;
        }
        if (next) void playAtRef.current(index + 1, 0, true);
        else stopPlayback("全书已经播放完毕。");
      };
      audio.onerror = () => setError("同步音频无法播放，请重试或改用浏览器朗读。");
      modeRef.current = "audio";
      setFallbackActive(false);
      setGenerating(false);
      await audio.play();
      void prefetchChapter(entry.chapterId);
      prefetchNextChapter(entry.chapterId);
    } catch (cause) {
      if (token !== requestTokenRef.current) return;
      setGenerating(false);
      setFallbackActive(true);
      setMessage("Edge 在线语音暂时不可用，正在使用设备自带语音；此句不会同步音频。");
      modeRef.current = "speech";
      try {
        await speechRef.current?.speak({
          text: entry.text,
          language: entry.language,
          rate: rateRef.current,
          volume: volumeRef.current,
        });
        if (token !== requestTokenRef.current) return;
        const next = queue[index + 1] ?? null;
        if (sleepTimerRef.current.shouldStopAtChapterBoundary(entry.chapterId, next?.chapterId ?? null)) {
          sleepTimerRef.current.cancel();
          setSleepChoice("off");
          stopPlayback("已在本章结束处停止播放。");
        } else if (next) {
          void playAtRef.current(index + 1, 0, true);
        } else {
          stopPlayback("全书已经播放完毕。");
        }
      } catch (speechError) {
        setError(speechError instanceof Error ? speechError.message : cause instanceof Error ? cause.message : "语音播放失败。");
        stopPlayback();
      }
    }
  }, [prefetchChapter, prefetchNextChapter, queue, stopPlayback, voiceFor]);
  useEffect(() => { playAtRef.current = playAt; }, [playAt]);

  const togglePlayback = useCallback(() => {
    if (!current) return;
    if (playingRef.current) {
      if (modeRef.current === "speech") speechRef.current?.pause();
      else audioRef.current?.pause();
      setPlaying(false);
      void saveProgress();
      return;
    }
    if (modeRef.current === "speech") {
      speechRef.current?.resume();
      setPlaying(true);
    } else if (audioRef.current?.src && modeRef.current === "audio") {
      audioRef.current.playbackRate = rateRef.current;
      void audioRef.current.play().then(() => setPlaying(true)).catch(() => void playAt(queueIndexRef.current, offsetRef.current, true));
    } else {
      void playAt(queueIndexRef.current, offsetRef.current, true);
    }
  }, [current, playAt, saveProgress]);

  const seekBy = useCallback((seconds: number) => {
    if (!queue.length) return;
    if (modeRef.current === "speech") {
      const target = Math.max(0, Math.min(queue.length - 1, queueIndexRef.current + (seconds < 0 ? -1 : 1)));
      void playAt(target, 0, playingRef.current);
      return;
    }
    const target = seekAcrossQueue({
      index: queueIndexRef.current,
      offsetMs: offsetRef.current,
      deltaMs: seconds * 1000,
      durations,
    });
    if (target.index === queueIndexRef.current && audioRef.current?.src) {
      audioRef.current.currentTime = target.offsetMs / 1000;
      setOffsetMs(target.offsetMs);
    } else {
      void playAt(target.index, target.offsetMs, playingRef.current);
    }
  }, [durations, playAt, queue.length]);

  const chooseChapter = useCallback((chapterId: string) => {
    const index = queue.findIndex((entry) => entry.chapterId === chapterId);
    if (index >= 0) void playAt(index, 0, playingRef.current);
  }, [playAt, queue]);

  function setSleep(value: SleepChoice) {
    setSleepChoice(value);
    if (value === "off") sleepTimerRef.current.cancel();
    else if (value === "chapter" && current) sleepTimerRef.current.setChapterEnd(current.chapterId);
    else if (value !== "chapter") sleepTimerRef.current.setMinutes(Number(value) as 15 | 30 | 45 | 60);
    setSleepRemaining(sleepTimerRef.current.remainingMs());
  }

  useEffect(() => {
    if (!playing) return;
    const interval = window.setInterval(() => {
      if (sleepTimerRef.current.shouldStop()) {
        sleepTimerRef.current.cancel();
        setSleepChoice("off");
        setSleepRemaining(null);
        stopPlayback("睡眠定时已到，播放已暂停。");
      } else {
        setSleepRemaining(sleepTimerRef.current.remainingMs());
      }
    }, 1_000);
    return () => window.clearInterval(interval);
  }, [playing, stopPlayback]);

  useEffect(() => {
    const interval = window.setInterval(() => {
      if (playingRef.current) void saveProgress();
    }, 15_000);
    const persist = () => {
      const entry = queue[queueIndexRef.current];
      if (!entry) return;
      void fetch(`/api/books/${bookId}/progress`, {
        method: "PUT",
        keepalive: true,
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          chapterId: entry.chapterId,
          segmentId: entry.id,
          offsetMs: offsetRef.current,
          playbackRate: rateRef.current,
          updatedAt: new Date().toISOString(),
        }),
      });
    };
    window.addEventListener("pagehide", persist);
    return () => { window.clearInterval(interval); window.removeEventListener("pagehide", persist); };
  }, [bookId, queue, saveProgress]);

  useEffect(() => {
    if (!current || !("mediaSession" in navigator)) return;
    configureMediaSession(navigator.mediaSession, {
      title: current.text.slice(0, 80),
      artist: current.chapterTitle,
      album: book?.title ?? "听阅",
      artwork: book?.coverUrl ? [{ src: new URL(book.coverUrl, window.location.origin).href }] : undefined,
    }, {
      play: togglePlayback,
      pause: togglePlayback,
      next: () => void playAt(Math.min(queue.length - 1, queueIndexRef.current + 1), 0, true),
      previous: () => void playAt(Math.max(0, queueIndexRef.current - 1), 0, true),
      seekBy,
    });
    navigator.mediaSession.playbackState = playing ? "playing" : "paused";
  }, [book?.coverUrl, book?.title, current, playAt, playing, queue.length, seekBy, togglePlayback]);

  useEffect(() => {
    if (!current) return;
    document.getElementById(`sentence-${current.id}`)?.scrollIntoView({ block: "center", behavior: "smooth" });
  }, [current]);

  if (loading) return <section className="state-card" role="status"><span className="loading-dot" />正在准备播放器…</section>;
  if (error && !book) return <section className="state-card state-error" role="alert"><strong>播放器没有打开</strong><p>{error}</p></section>;
  if (!queue.length) return <section className="state-card"><strong>还没有可朗读正文</strong><p>请先回到校对页完成解析或 OCR。</p><Link className="button" href={`/books/${bookId}/edit`}>校对正文</Link></section>;

  const duration = durations[queueIndex] ?? estimateDuration(current?.text ?? "");
  const chapterIndex = chapters.findIndex((chapter) => chapter.id === currentChapter?.id);

  return (
    <section className="player-card">
      <aside className="player-chapters">
        <header><span className="format-chip">正在收听</span><h1>{book?.title}</h1><p>{book?.author}</p></header>
        <nav aria-label="章节">
          {[...chapters].sort((a, b) => a.position - b.position).map((chapter) => (
            <button key={chapter.id} className={chapter.id === current?.chapterId ? "active" : ""} onClick={() => chooseChapter(chapter.id)}>
              <span>{chapter.position + 1}</span><span>{chapter.title}<small>{chapter.segments.filter((segment) => segment.included).length} 句</small></span>
            </button>
          ))}
        </nav>
      </aside>

      <div className="player-reading">
        <header className="player-reading-head">
          <div><p className="eyebrow">第 {chapterIndex + 1} 章</p><h2>{currentChapter?.title}</h2></div>
          <Link href={`/books/${bookId}/edit`}>校对正文</Link>
        </header>
        <div className="sentence-list" aria-label="逐句正文">
          {currentChapter?.segments.map((segment) => segment.included ? (
            <button
              id={`sentence-${segment.id}`}
              key={segment.id}
              className={`sentence ${segment.id === current?.id ? "current" : ""}`}
              aria-current={segment.id === current?.id ? "true" : undefined}
              onClick={() => {
                const index = queue.findIndex((entry) => entry.id === segment.id);
                if (index >= 0) void playAt(index, 0, true);
              }}
            >
              <span>{segment.position + 1}</span><span>{segment.text}</span>
            </button>
          ) : (
            <div key={segment.id} className="sentence skipped"><span>跳过</span><span>{segment.text}</span></div>
          ))}
        </div>
      </div>

      <footer className="player-controls">
        <div className="player-status-line">
          <span>{generating ? "正在生成本句音频…" : fallbackActive ? "设备语音 · 未同步" : "云端音频 · 已同步"}</span>
          {sleepChoice !== "off" && <span>睡眠定时：{sleepChoice === "chapter" ? "本章结束" : sleepRemaining === null ? `${sleepChoice} 分钟` : formatTime(sleepRemaining)}</span>}
        </div>
        <div className="seek-line">
          <span>{formatTime(offsetMs)}</span>
          <input aria-label="本句播放位置" type="range" min="0" max={Math.max(1, duration)} value={Math.min(offsetMs, duration)} onChange={(event) => {
            const value = Number(event.target.value);
            if (audioRef.current?.src) audioRef.current.currentTime = value / 1000;
            setOffsetMs(value);
          }} />
          <span>{formatTime(duration)}</span>
        </div>
        <div className="transport-controls">
          <button aria-label="上一章" onClick={() => { const previous = chapters[Math.max(0, chapterIndex - 1)]; if (previous) chooseChapter(previous.id); }}>上一章</button>
          <button aria-label="后退 15 秒" onClick={() => seekBy(-15)}>−15</button>
          <button className="play-toggle" aria-label={playing ? "暂停" : "播放"} onClick={togglePlayback}>{playing ? "Ⅱ" : "▶"}</button>
          <button aria-label="前进 15 秒" onClick={() => seekBy(15)}>+15</button>
          <button aria-label="下一章" onClick={() => { const next = chapters[Math.min(chapters.length - 1, chapterIndex + 1)]; if (next) chooseChapter(next.id); }}>下一章</button>
        </div>
        <div className="secondary-controls">
          <label>播放速度<select value={rate} onChange={(event) => setRate(Number(event.target.value))}>{[0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3].map((value) => <option key={value} value={value}>{value}×</option>)}</select></label>
          <label>音量<input type="range" min="0" max="1" step="0.05" value={volume} onChange={(event) => setVolume(Number(event.target.value))} /></label>
          <label>睡眠定时<select value={sleepChoice} onChange={(event) => setSleep(event.target.value as SleepChoice)}><option value="off">关闭</option><option value="15">15 分钟</option><option value="30">30 分钟</option><option value="45">45 分钟</option><option value="60">60 分钟</option><option value="chapter">本章结束</option></select></label>
        </div>
        {(message || error) && <p className={`player-message ${error ? "error" : ""}`} role={error ? "alert" : "status"}>{error || message}</p>}
      </footer>
    </section>
  );
}
