/**
 * Typed accessors for fields the Jellyfin SDK doesn't fully model and for
 * synthetic fields the app stamps on `BaseItemDto` to bridge TMDB results
 * into the `PosterCard` pipeline. Centralizes type-normalization for items entering PosterCard
 * so call sites can read fields directly with confidence.
 */
import type { BaseItemDto } from '../api/types'
import type { TmdbMovie, TmdbTvShow } from '../api/tmdb'

/* ─── TMDB union narrowing ──────────────────────────────────────── */

/**
 * Merged shape for TMDB movie + TV detail data. The two endpoints return
 * different shapes (`title`/`release_date` vs `name`/`first_air_date`)
 * but the app frequently reads the same logical fields from either.
 * `Partial<A> & Partial<B>` exposes the union surface without forcing
 * call sites to rediscriminate at every read.
 */
export type TmdbDetailUnion = Partial<TmdbMovie> & Partial<TmdbTvShow>

/** Cast a movie | tv | null to the merged union type. */
export function asTmdbDetail(data: TmdbMovie | TmdbTvShow | null | undefined): TmdbDetailUnion | undefined {
  return data as TmdbDetailUnion | undefined
}

/* ─── Synthetic TMDB fields ──────────────────────────────────────── */

/**
 * Extra fields the app stamps on a `BaseItemDto` when it's actually a
 * TMDB result mapped through `mapTmdbToJf`. Library hits route via the
 * Jellyfin id; non-library hits route via the synthetic `tmdb-<kind>-<id>`
 * route id.
 */
export interface TmdbSyntheticFields {
  _tmdbPoster?: string
  _tmdbBackdrop?: string
  _inLibrary?: boolean
  _tmdbId?: string
}

export type TmdbDecoratedItem = BaseItemDto & TmdbSyntheticFields

/** Read the TMDB-decoration fields from an item without per-call casts. */
export function tmdbDecoration(item: BaseItemDto | null | undefined): TmdbSyntheticFields {
  if (!item) return {}
  return item as TmdbDecoratedItem
}

/** TMDB id from the ProviderIds map, falling back to a synthetic _tmdbId
 *  set by mapTmdbToJf for non-library items. */
export function getTmdbId(item: BaseItemDto | null | undefined): string | undefined {
  if (!item) return undefined
  return item.ProviderIds?.Tmdb || tmdbDecoration(item)._tmdbId
}

/** True if the item is either a real Jellyfin item OR a TMDB-synthetic
 *  item the app has flagged as already in the library. */
export function isInLibrary(item: BaseItemDto | null | undefined): boolean {
  if (!item) return false
  return tmdbDecoration(item)._inLibrary === true
}

/* ─── Series-specific extensions ─────────────────────────────────── */

interface SeriesAirInfoFields {
  AirDays?: string[] | null
  AirTime?: string | null
}

/** Returns the AirDays + AirTime that the Jellyfin SDK doesn't expose on
 *  the base BaseItemDto. Both are null when the item isn't a Series or
 *  the server didn't populate the fields. */
export function getSeriesAirInfo(item: BaseItemDto | null | undefined): { airDays: string[] | null; airTime: string | null } {
  if (!item) return { airDays: null, airTime: null }
  const augmented = item as BaseItemDto & SeriesAirInfoFields
  return {
    airDays: augmented.AirDays ?? null,
    airTime: augmented.AirTime ?? null,
  }
}

/* ─── Episode-specific extensions ────────────────────────────────── */

interface EpisodeExtraFields {
  LocationType?: string
  Trickplay?: unknown
}

/** Jellyfin's canonical signal for "file not on disk": LocationType
 *  === 'Virtual'. Returns true for missing items so callers can render
 *  a missing-state without their own cast. */
export function isMissingEpisode(item: BaseItemDto | null | undefined): boolean {
  if (!item) return false
  return (item as BaseItemDto & EpisodeExtraFields).LocationType === 'Virtual'
}

/** True when the server has trickplay sprite sheets generated for this
 *  item. Just a typed accessor for the otherwise-unmodeled field. */
export function hasTrickplay(item: BaseItemDto | null | undefined): boolean {
  if (!item) return false
  return !!(item as BaseItemDto & EpisodeExtraFields).Trickplay
}

/* ─── Playlist-specific extensions ───────────────────────────────── */

interface PlaylistEntryFields {
  PlaylistItemId?: string
}

/** PlaylistItemId is the per-row identifier the Jellyfin Playlists
 *  endpoint returns. The SDK's BaseItemDto doesn't include it, but the
 *  playlist-items response always has it. */
export function getPlaylistItemId(item: BaseItemDto | null | undefined): string | undefined {
  if (!item) return undefined
  return (item as BaseItemDto & PlaylistEntryFields).PlaylistItemId
}

/* ─── Music-specific extensions ──────────────────────────────────── */

interface MusicTrackFields {
  AlbumArtist?: string
  Album?: string
  Artists?: string[]
}

/** Best-effort artist label for an audio item. Falls back through
 *  AlbumArtist -> first Artist -> empty string. */
export function getTrackArtist(item: BaseItemDto | null | undefined): string {
  if (!item) return ''
  const m = item as BaseItemDto & MusicTrackFields
  if (m.AlbumArtist) return m.AlbumArtist
  if (Array.isArray(m.Artists) && m.Artists.length > 0) return m.Artists[0]
  return ''
}

/** Joined artist list for an audio item (first N, default 2). */
export function getTrackArtists(item: BaseItemDto | null | undefined, limit = 2): string {
  if (!item) return ''
  const m = item as BaseItemDto & MusicTrackFields
  if (m.AlbumArtist) return m.AlbumArtist
  if (Array.isArray(m.Artists)) return m.Artists.slice(0, limit).join(', ')
  return ''
}

/** Album field accessor. */
export function getAlbum(item: BaseItemDto | null | undefined): string {
  if (!item) return ''
  return (item as BaseItemDto & MusicTrackFields).Album ?? ''
}
