/**
 * Global audio player state for libnovel.
 *
 * A single shared instance (module singleton) keeps audio playing across
 * SvelteKit navigations.  The layout mounts the <audio> element once and
 * never unmounts it; the per-chapter AudioPlayer component is just a
 * controller that reads/writes this state.
 *
 * Uses Svelte 5 runes ($state / $derived) — import only from .svelte files
 * or other .svelte.ts files.
 *
 * ── State machine ────────────────────────────────────────────────────────────
 *
 * Current chapter (status):
 *   idle  →  loading  →  ready  (fast path: audio exists in MinIO)
 *   idle  →  loading  →  generating  →  ready  (slow path: Kokoro TTS)
 *   any  →  error
 *
 * Next chapter pre-fetch (nextStatus):
 *   'none'        – no next chapter, or auto-next is off
 *   'prefetching' – POST /api/audio running for the next chapter
 *   'prefetched'  – next chapter audio is ready in MinIO
 *   'failed'      – pre-generation failed (will retry on navigate)
 *
 * Auto-next transition:
 *   onended fires  →  navigate to next chapter URL
 *     ↳ new chapter page mounts
 *       • if nextStatus === 'prefetched'  → presign + play immediately
 *       • else                            → normal startPlayback() flow
 *
 * Pre-fetch is triggered when currentTime / duration >= 0.9 (90% mark).
 * It only runs once per chapter (guarded by nextStatus !== 'none').
 */

import type { Voice } from '$lib/types';

export type AudioStatus = 'idle' | 'loading' | 'generating' | 'ready' | 'error';
export type NextStatus = 'none' | 'prefetching' | 'prefetched' | 'failed';

class AudioStore {
	// ── What is loaded ──────────────────────────────────────────────────────
	slug = $state('');
	chapter = $state(0);
	chapterTitle = $state('');
	bookTitle = $state('');
	voice = $state('af_bella');
	speed = $state(1.0);

	/** Cover image URL for the currently loaded book. */
	cover = $state('');

	/** Full chapter list for the currently loaded book (number + title). */
	chapters = $state<{ number: number; title: string }[]>([]);

	/** Available voices (populated by the chapter AudioPlayer on mount). */
	voices = $state<Voice[]>([]);

	// ── Loading/generation state ────────────────────────────────────────────
	status = $state<AudioStatus>('idle');
	audioUrl = $state('');
	errorMsg = $state('');
	/** Pseudo-progress bar value 0–100 during generation */
	progress = $state(0);

	/**
	 * True while playing a short CF AI preview clip (~1-2 min) and the full
	 * audio is still being generated in the background.  Set to false once the
	 * full audio URL has been swapped in.
	 */
	isPreview = $state(false);

	// ── Playback state (kept in sync with the <audio> element) ─────────────
	currentTime = $state(0);
	duration = $state(0);
	isPlaying = $state(false);

	/**
	 * Increment to signal the layout to toggle play/pause.
	 * The layout watches this with $effect and calls audioEl.play()/pause().
	 */
	toggleRequest = $state(0);

	/**
	 * Set to a number to seek the audio element to that time (seconds).
	 * The layout watches this with $effect and sets audioEl.currentTime.
	 * Reset to null after handling.
	 */
	seekRequest = $state<number | null>(null);

	// ── Sleep timer ──────────────────────────────────────────────────────────
	/** Epoch ms when sleep timer should fire. 0 = off. */
	sleepUntil = $state(0);

	/** When true, pause after the current chapter ends instead of navigating. */
	sleepAfterChapter = $state(false);

	// ── Auto-next ────────────────────────────────────────────────────────────
	/**
	 * When true, navigates to the next chapter when the current one ends
	 * and auto-starts its audio.
	 */
	autoNext = $state(false);

	/**
	 * When true, announces the upcoming chapter number and title via the
	 * Web Speech API before auto-next navigation fires.
	 * e.g. "Chapter 12 — The Final Battle"
	 */
	announceChapter = $state(false);

	/**
	 * The next chapter number for the currently playing chapter, or null if
	 * there is no next chapter.  Written by the chapter page's AudioPlayer.
	 * Stored here (not cleared on unmount) so onended can still read it after
	 * the component unmounts due to {#key} re-render on navigation.
	 */
	nextChapter = $state<number | null>(null);

	/**
	 * Set to the chapter number that should auto-start by the layout's onended
	 * handler (when autoNext fires a navigation).  The AudioPlayer on the new
	 * page checks this on mount: if it matches the component's own chapter prop
	 * it starts playback and clears the value.
	 *
	 * Using the target chapter number (instead of a plain boolean) prevents the
	 * still-mounted outgoing AudioPlayer from reacting to the flag before the
	 * navigation completes — it only matches the incoming chapter's component.
	 */
	autoStartChapter = $state<number | null>(null);

	// ── Next-chapter pre-fetch state ─────────────────────────────────────────
	/**
	 * State of the background pre-generation for the next chapter.
	 * 'none'        – nothing started (default / no next chapter)
	 * 'prefetching' – currently running POST /api/audio for next chapter
	 * 'prefetched'  – next chapter audio confirmed ready in MinIO
	 * 'failed'      – pre-generation failed (fallback: generate on navigate)
	 */
	nextStatus = $state<NextStatus>('none');

	/**
	 * The presigned URL obtained during pre-fetch.  When the user navigates
	 * to the next chapter, AudioPlayer picks this up and skips straight to play.
	 */
	nextAudioUrl = $state('');

	/** Progress value (0–100) shown while pre-generating the next chapter. */
	nextProgress = $state(0);

	/** Which chapter number the pre-fetch state above belongs to. */
	nextChapterPrefetched = $state<number | null>(null);

	/** Whether the mini-bar at the bottom is visible */
	get active(): boolean {
		return this.status === 'ready' || this.status === 'generating' || this.status === 'loading';
	}

	/** True when the currently loaded track matches slug+chapter */
	isCurrentChapter(slug: string, chapter: number): boolean {
		return this.slug === slug && this.chapter === chapter;
	}

	// ── Announce-chapter navigation state ────────────────────────────────────
	/**
	 * When true, the <audio> element is playing a short announcement clip
	 * (not chapter audio).  The next `onended` should navigate to
	 * announcePendingSlug / announcePendingChapter instead of the normal
	 * auto-next flow.
	 */
	announceNavigatePending = $state(false);
	/** Target book slug for the pending announce-then-navigate transition. */
	announcePendingSlug    = $state('');
	/** Target chapter number for the pending announce-then-navigate transition. */
	announcePendingChapter = $state(0);

	/** Reset all next-chapter pre-fetch state. */
	resetNextPrefetch() {
		this.nextStatus = 'none';
		this.nextAudioUrl = '';
		this.nextProgress = 0;
		this.nextChapterPrefetched = null;
	}
}

export const audioStore = new AudioStore();
