/**
 * Player state store - Thin wrapper over Rust PlayerController
 *
 * This store is display-only for most fields, receiving updates from
 * backend events via playerEvents.ts. User actions are sent as commands
 * to the Rust backend, which drives state changes.
 *
 * TRACES: UR-005 | DR-001, DR-009
 */

import { writable, derived } from "svelte/store";
import type { MediaItem, ItemType } from "$lib/api/types";
import type { NowPlayingItem } from "$lib/api/bindings";
import { isRemoteMode } from "./playbackMode";
import { selectedSession } from "./sessions";
import { currentQueueItem } from "./queue";
import { ticksToSeconds } from "$lib/utils/playbackUnits";

// Merged media item from backend (matches Rust MergedMediaItem)
export interface MergedMediaItem {
  id: string;
  title: string;
  artist: string | null;
  album: string | null;
  albumId: string | null;
  duration: number | null;
  primaryImageTag: string | null;
  mediaType: "audio" | "video";
}

// TRACES: UR-005 | DR-001
export type PlayerState =
  | { kind: "idle" }
  | { kind: "loading"; media: MediaItem }
  | { kind: "playing"; media: MediaItem; position: number; duration: number }
  | { kind: "paused"; media: MediaItem; position: number; duration: number }
  | { kind: "seeking"; media: MediaItem; target: number }
  | { kind: "error"; media: MediaItem | null; error: string };

export type RepeatMode = "off" | "all" | "one";

interface PlayerStore {
  state: PlayerState;
  volume: number;
  muted: boolean;
}

function createPlayerStore() {
  const initialState: PlayerStore = {
    state: { kind: "idle" },
    volume: 1.0,
    muted: false,
  };

  const { subscribe, set, update } = writable<PlayerStore>(initialState);

  function setIdle() {
    update((s) => ({ ...s, state: { kind: "idle" } }));
  }

  function setLoading(media: MediaItem) {
    update((s) => ({ ...s, state: { kind: "loading", media } }));
  }

  function setPlaying(media: MediaItem, position: number, duration: number) {
    update((s) => ({
      ...s,
      state: { kind: "playing", media, position, duration },
    }));
  }

  function setPaused(media: MediaItem, position: number, duration: number) {
    update((s) => ({
      ...s,
      state: { kind: "paused", media, position, duration },
    }));
  }

  function setSeeking(media: MediaItem, target: number) {
    update((s) => ({ ...s, state: { kind: "seeking", media, target } }));
  }

  function setError(error: string, media: MediaItem | null = null) {
    update((s) => ({ ...s, state: { kind: "error", media, error } }));
  }

  function updatePosition(position: number, duration?: number) {
    update((s) => {
      if (s.state.kind === "playing" || s.state.kind === "paused") {
        return {
          ...s,
          state: {
            ...s.state,
            position,
            // Update duration if provided and valid
            duration: duration !== undefined && duration > 0 ? duration : s.state.duration
          },
        };
      }
      return s;
    });
  }

  function setVolume(volume: number) {
    update((s) => ({ ...s, volume: Math.max(0, Math.min(1, volume)) }));
  }

  function setMuted(muted: boolean) {
    update((s) => ({ ...s, muted }));
  }

  function toggleMute() {
    update((s) => ({ ...s, muted: !s.muted }));
  }

  return {
    subscribe,
    setIdle,
    setLoading,
    setPlaying,
    setPaused,
    setSeeking,
    setError,
    updatePosition,
    setVolume,
    setMuted,
    toggleMute,
  };
}

export const player = createPlayerStore();

// Derived stores
export const playerState = derived(player, ($p) => $p.state);
export const currentMedia = derived(player, ($p) => {
  const state = $p.state;
  if (state.kind === "idle") return null;
  return state.media;
});
export const isPlaying = derived(player, ($p) => $p.state.kind === "playing");
export const isPaused = derived(player, ($p) => $p.state.kind === "paused");
export const isLoading = derived(player, ($p) => $p.state.kind === "loading");
export const playbackPosition = derived(player, ($p) => {
  const state = $p.state;
  if (state.kind === "playing" || state.kind === "paused") {
    return state.position;
  }
  return 0;
});
export const playbackDuration = derived(player, ($p) => {
  const state = $p.state;
  if (state.kind === "playing" || state.kind === "paused") {
    return state.duration;
  }
  return 0;
});
export const volume = derived(player, ($p) => $p.volume);
export const isMuted = derived(player, ($p) => $p.muted);

// Merged playback state (combines local and remote based on playback mode)
// These stores replace the mergedPlaybackState.ts helper functions

/**
 * Merged media item - prefers remote session when in remote mode
 */
/**
 * Normalize a remote session's NowPlayingItem into the MediaItem shape the UI
 * renders, so display components can treat local and remote items uniformly.
 * (NowPlayingItem has `album`/`artists` but no `albumName`/`artistItems`; the UI
 * falls back to the `artists` string list when `artistItems` is absent.)
 */
function nowPlayingToMediaItem(npi: NowPlayingItem): MediaItem {
  return {
    id: npi.id ?? "",
    name: npi.name ?? "",
    type: (npi.Type ?? "Audio") as ItemType,
    serverId: "",
    albumName: npi.album,
    albumId: npi.albumId,
    artists: npi.artists,
    primaryImageTag: npi.primaryImageTag ?? npi.albumPrimaryImageTag,
    runTimeTicks: npi.runTimeTicks,
  } as MediaItem;
}

export const mergedMedia = derived<
  [typeof isRemoteMode, typeof selectedSession, typeof currentMedia],
  MediaItem | null
>(
  [isRemoteMode, selectedSession, currentMedia],
  ([$isRemote, $session, $local]) => {
    if ($isRemote && $session?.nowPlayingItem) {
      return nowPlayingToMediaItem($session.nowPlayingItem);
    }
    return $local ?? null;
  }
);

/**
 * Merged isPlaying state - prefers remote session when in remote mode
 */
export const mergedIsPlaying = derived(
  [isRemoteMode, selectedSession, isPlaying],
  ([$isRemote, $session, $localIsPlaying]) => {
    if ($isRemote && $session?.playState) {
      return !$session.playState.isPaused;
    }
    return $localIsPlaying;
  }
);

/**
 * Merged position - prefers remote session when in remote mode
 */
export const mergedPosition = derived(
  [isRemoteMode, selectedSession, playbackPosition],
  ([$isRemote, $session, $localPosition]) => {
    if ($isRemote && $session?.playState) {
      return ticksToSeconds($session.playState.positionTicks ?? 0);
    }
    return $localPosition;
  }
);

/**
 * Merged duration - prefers remote session when in remote mode
 */
export const mergedDuration = derived(
  [isRemoteMode, selectedSession, playbackDuration],
  ([$isRemote, $session, $localDuration]) => {
    if ($isRemote && $session?.nowPlayingItem?.runTimeTicks) {
      return ticksToSeconds($session.nowPlayingItem.runTimeTicks);
    }
    return $localDuration;
  }
);

/**
 * Merged volume - prefers remote session when in remote mode
 * Both local and remote use 0-1 normalized range
 */
export const mergedVolume = derived(
  [isRemoteMode, selectedSession, volume],
  ([$isRemote, $session, $localVolume]) => {
    if ($isRemote && $session?.playState) {
      // Convert remote 0-100 to normalized 0-1
      return ($session.playState.volumeLevel ?? 100) / 100;
    }
    return $localVolume;
  }
);

/**
 * Whether an item is video content and therefore must NOT appear in the audio
 * mini player. Checks the Jellyfin item type (Movie/Episode/live TV) AND the
 * backend queue item's `mediaType` discriminator.
 *
 * The `mediaType` check is essential: a video started via `player_play_item`
 * (every Movie/Episode goes through VideoPlayer) is pushed onto the backend
 * queue as a PlayerMediaItem with NO `type` field but `mediaType: "video"`.
 * Without this, that queue item's absent `type` slips past the Movie/Episode
 * check and the video surfaces in the audio mini player after leaving /player.
 */
function isVideoItem(item: MediaItem | null): boolean {
  if (!item) return false;
  const type = item.type;
  if (type === "Movie" || type === "Episode" || type === "TvChannel") {
    return true;
  }
  // Backend PlayerMediaItem carries a lowercase mediaType discriminator that is
  // present even when the Jellyfin `type` is absent (video-only play requests).
  const mediaType = (item as { mediaType?: string }).mediaType;
  return mediaType === "video";
}

/**
 * Should show audio miniplayer - state machine gated
 * Only true when:
 * 1. In remote mode with an active session playing media, OR
 * 2. There is an audio item loaded/queued and we are not in a genuine
 *    stopped state. The bar stays visible through transient idle/loading/
 *    seeking blips so it never flickers while advancing between tracks.
 */
export const shouldShowAudioMiniPlayer = derived(
  [player, currentMedia, currentQueueItem, isRemoteMode, selectedSession],
  ([$player, $media, $queueItem, $isRemote, $session]) => {
    // In remote mode, show if the remote session has a now-playing item
    if ($isRemote && $session?.nowPlayingItem) {
      return true;
    }

    // Determine media type from the player state, falling back to the queue
    // item (the player store can momentarily lack media during transitions).
    const item = $media ?? $queueItem;
    if (isVideoItem(item)) {
      return false;
    }

    const state = $player.state;

    // A genuine stop clears the queue too, so when the player reports
    // idle/error we only hide if there is also no queue item to fall back to.
    // This keeps the bar visible through a transient idle/stopped blip emitted
    // mid-transition (e.g. sleep-timer churn or a stop-then-load track change),
    // while still hiding once playback has truly ended and the queue is empty.
    if (state.kind === "idle" || state.kind === "error") {
      return $queueItem != null;
    }

    // playing / paused / loading / seeking — audio is active, show the bar.
    return true;
  }
);
