import type {
  BaseItemDto,
  BaseItemDtoQueryResult,
  PlaybackInfoResponse,
  UserDto,
} from '@jellyfin/sdk/lib/generated-client/models'

export type { BaseItemDto, BaseItemDtoQueryResult, PlaybackInfoResponse, UserDto }

export interface AuthState {
  serverUrl: string
  token: string
  userId: string
  userName?: string
}

export interface HomeShowSettings {
  continueWatching: boolean
  nextUp: boolean
  recentlyAdded: boolean
  trendingWeek: boolean
  topRated: boolean
  watchedRecently: boolean
  surpriseMe: boolean
  watchlist: boolean
  becauseYouWatched: boolean
  personSpotlights: boolean
  trendingToday: boolean
  criticallyAcclaimed: boolean
  genreDeepDive: boolean
  cultClassics: boolean
  yearEndBestOf: boolean
  foreignCinema: boolean
  documentaryPicks: boolean
  awardWinnersMissing: boolean
  discoverCanon: boolean
  letterboxdLists: boolean
  comingSoon: boolean
  smartShelves: boolean
  moodPicker: boolean
  timeOfDay: boolean
  hiddenGems: boolean
  untouched: boolean
  decadeRows: boolean
  featuredGenres: boolean
  studios: boolean
  networks: boolean
}

export interface DetailShowSettings {
  awards: boolean
  filmingLocations: boolean
  trivia: boolean
  videos: boolean
  personal: boolean
  diary: boolean
  episodeExtras: boolean
  collectionMeter: boolean
}

export interface EpisodeShowSettings {
  fillerChips: boolean
  ratingChips: boolean
  sparklines: boolean
  spoilerBlur: boolean
}

export interface EpisodeBehaviorSettings {
  swipeActions: boolean
  sortMode: 'order' | 'rating-desc' | 'rating-asc'
  orderPreference: 'aired' | 'production' | 'dvd' | 'story' | 'absolute' | 'tv'
}

export interface EpisodeRecapSettings {
  card: boolean
  gapDays: number
}

export interface EpisodeSettings {
  show: EpisodeShowSettings
  behavior: EpisodeBehaviorSettings
  recap: EpisodeRecapSettings
}

export interface EndVideoShowSettings {
  moreLikeThis: boolean
  antiRec: boolean
}

export interface AppSettings {
  // Playback
  autoplayNext: boolean
  skipIntros: boolean
  skipCredits: boolean
  resumeThresholdSec: number
  sleepTimerMinutes: number
  areYouStillWatching: boolean
  // 1.0 default. Vidstack's playbackRate setter applies this; persisted so
  // a user who likes 1.5x doesn't have to set it on every episode.
  defaultPlaybackRate: number
  preserveAudioPitch: boolean
  // Pause when window loses focus, resume when it regains. Off by default.
  pauseOnBlur: boolean
  // Show "Resume from M:SS / Start over" prompt instead of silently
  // resuming when there's a saved position.
  showResumePrompt: boolean
  // Show end-of-video card (Replay / Episodes / Back) when the video ends
  // and there's no auto-advance target.
  endOfVideoCard: boolean
  // Play a trailer before starting a movie, when one is available.
  preRollTrailers: boolean
  // Per-shortcut-id override map. Empty by default; defaults come from the
  // shortcut registry.
  keyboardShortcuts: Record<string, string[]>

  // Audio + subtitles
  subtitleLanguage: string
  audioLanguage: string
  subtitleMode: 'default' | 'always' | 'none'
  // Persistent player volume (0-1). Restored across episodes / sessions so
  // users don't have to re-set it every time they open the player.
  playerVolume: number
  // Volume boost beyond 100%, via Web Audio gain. 1.0..3.0.
  volumeBoost: number
  // Dynamic range compression for "night mode" - lifts quiet dialogue,
  // tames loud action.
  nightMode: boolean
  // Request server-side audio passthrough for codecs like TrueHD/DTS-HD.
  // Browser players rarely support this, but the flag tells the server
  // to prefer direct-stream over transcode for these tracks.
  audioPassthrough: boolean
  // Persistent default offset (ms). Per-session offsets live in the
  // player runtime store.
  audioDelayMs: number
  subtitleDelayMs: number
  subtitleFontSize: number
  subtitleFontFamily: 'sans' | 'serif' | 'mono'
  subtitleBackground: 'none' | 'subtle' | 'solid'
  subtitleEdge: 'none' | 'shadow' | 'outline'
  subtitlePosition: 'bottom' | 'top'
  subtitleColor: 'white' | 'yellow' | 'cyan'

  // Picture filters (applied via CSS `filter` on the <video> element)
  videoBrightness: number
  videoContrast: number
  videoSaturation: number

  // Display
  theme: 'dark'
  density: 'comfortable' | 'compact'
  // Interface zoom factor. Multiplied via CSS `zoom` on <html> so every
  // pixel-defined size scales together. 1.0 = native pixels.
  uiZoom: number
  reduceMotion: boolean
  accentColor: string
  showTechBadges: boolean
  showTmdbRatings: boolean
  heroAutoAdvance: boolean
  heroAutoAdvanceMs: number
  // Auto-play TMDB trailer in the poster card after a short hover.
  hoverTrailers: boolean
  // Pre-fire Jellyfin PlaybackInfo + first stream bytes on poster hover
  // so the click → playing transition feels instant.
  prebufferOnHover: boolean

  // Discovery
  region: string
  tmdbApiKey: string
  // Optional personal key from https://fanart.tv/get-an-api-key/.
  fanartApiKey: string
  hideAdult: boolean
  defaultLanding: 'home' | 'movies' | 'shows'

  // Section toggles grouped by surface
  home: { show: HomeShowSettings }
  detail: { show: DetailShowSettings }
  episode: EpisodeSettings
  endVideo: { show: EndVideoShowSettings }

  // Quick-look right-click / long-press peek
  quickLookEnabled: boolean

  // Privacy
  pushNotifications: boolean
  diagnosticLogging: boolean
}

export const defaultHomeShow: HomeShowSettings = {
  continueWatching: true,
  nextUp: true,
  recentlyAdded: true,
  trendingWeek: true,
  topRated: true,
  watchedRecently: true,
  surpriseMe: true,
  watchlist: true,
  becauseYouWatched: true,
  personSpotlights: true,
  trendingToday: true,
  criticallyAcclaimed: true,
  genreDeepDive: true,
  cultClassics: true,
  yearEndBestOf: true,
  foreignCinema: true,
  documentaryPicks: true,
  awardWinnersMissing: true,
  discoverCanon: true,
  letterboxdLists: true,
  comingSoon: true,
  smartShelves: true,
  moodPicker: true,
  timeOfDay: true,
  hiddenGems: true,
  untouched: true,
  decadeRows: true,
  featuredGenres: true,
  studios: true,
  networks: true,
}

export const defaultDetailShow: DetailShowSettings = {
  awards: true,
  filmingLocations: true,
  trivia: true,
  videos: true,
  personal: true,
  diary: true,
  episodeExtras: true,
  collectionMeter: true,
}

export const defaultEpisode: EpisodeSettings = {
  show: {
    fillerChips: true,
    ratingChips: true,
    sparklines: true,
    spoilerBlur: true,
  },
  behavior: {
    swipeActions: true,
    sortMode: 'order',
    orderPreference: 'aired',
  },
  recap: {
    card: true,
    gapDays: 14,
  },
}

export const defaultEndVideoShow: EndVideoShowSettings = {
  moreLikeThis: true,
  antiRec: true,
}

export const defaultSettings: AppSettings = {
  autoplayNext: true,
  skipIntros: false,
  skipCredits: false,
  resumeThresholdSec: 5,
  sleepTimerMinutes: 0,
  areYouStillWatching: false,
  defaultPlaybackRate: 1,
  preserveAudioPitch: true,
  pauseOnBlur: false,
  showResumePrompt: true,
  endOfVideoCard: true,
  preRollTrailers: false,
  keyboardShortcuts: {},

  subtitleLanguage: 'eng',
  audioLanguage: 'eng',
  subtitleMode: 'default',
  playerVolume: 1,
  volumeBoost: 1,
  nightMode: false,
  audioPassthrough: false,
  audioDelayMs: 0,
  subtitleDelayMs: 0,
  subtitleFontSize: 22,
  subtitleFontFamily: 'sans',
  subtitleBackground: 'subtle',
  subtitleEdge: 'shadow',
  subtitlePosition: 'bottom',
  subtitleColor: 'white',

  videoBrightness: 1,
  videoContrast: 1,
  videoSaturation: 1,

  theme: 'dark',
  density: 'comfortable',
  uiZoom: 1,
  reduceMotion: false,
  accentColor: '#F5B642',
  showTechBadges: true,
  showTmdbRatings: true,
  heroAutoAdvance: true,
  heroAutoAdvanceMs: 9000,
  hoverTrailers: true,
  prebufferOnHover: true,

  region: '',
  tmdbApiKey: '',
  fanartApiKey: '',
  hideAdult: true,
  defaultLanding: 'home',

  home: { show: defaultHomeShow },
  detail: { show: defaultDetailShow },
  episode: defaultEpisode,
  endVideo: { show: defaultEndVideoShow },

  quickLookEnabled: true,

  pushNotifications: false,
  diagnosticLogging: false,
}

/**
 * Translate a v1 flat-keyed preferences blob into the v2 nested shape.
 * Anything missing or malformed in the persisted state falls back to the
 * matching default, so partial / corrupt user data still loads.
 */
export function migrateV1Preferences(raw: Record<string, unknown>): AppSettings {
  const pick = <T,>(key: string, fallback: T): T =>
    (raw[key] as T | undefined) ?? fallback

  const boolPair = <K extends string>(prefix: string, key: K, def: boolean): boolean =>
    pick(prefix + capitalize(key), def)

  return {
    ...defaultSettings,
    ...raw,

    home: {
      show: {
        ...defaultHomeShow,
        ...Object.fromEntries(
          (Object.keys(defaultHomeShow) as (keyof HomeShowSettings)[]).map(k => [
            k,
            boolPair('homeShow', k, defaultHomeShow[k]),
          ]),
        ),
      } as HomeShowSettings,
    },

    detail: {
      show: {
        ...defaultDetailShow,
        ...Object.fromEntries(
          (Object.keys(defaultDetailShow) as (keyof DetailShowSettings)[]).map(k => [
            k,
            boolPair('detailShow', k, defaultDetailShow[k]),
          ]),
        ),
      } as DetailShowSettings,
    },

    episode: {
      show: {
        fillerChips: pick('episodeShowFillerChips', defaultEpisode.show.fillerChips),
        ratingChips: pick('episodeShowRatingChips', defaultEpisode.show.ratingChips),
        sparklines: pick('episodeShowSparklines', defaultEpisode.show.sparklines),
        spoilerBlur: pick('spoilerBlur', defaultEpisode.show.spoilerBlur),
      },
      behavior: {
        swipeActions: pick('episodeSwipeActions', defaultEpisode.behavior.swipeActions),
        sortMode: pick('episodeSortMode', defaultEpisode.behavior.sortMode),
        orderPreference: pick('episodeOrderPreference', defaultEpisode.behavior.orderPreference),
      },
      recap: {
        card: pick('showRecapCard', defaultEpisode.recap.card),
        gapDays: pick('recapGapDays', defaultEpisode.recap.gapDays),
      },
    },

    endVideo: {
      show: {
        moreLikeThis: pick('endVideoShowMoreLikeThis', defaultEndVideoShow.moreLikeThis),
        antiRec: pick('endVideoShowAntiRec', defaultEndVideoShow.antiRec),
      },
    },
  }
}

function capitalize<S extends string>(s: S): Capitalize<S> {
  return (s.charAt(0).toUpperCase() + s.slice(1)) as Capitalize<S>
}

/**
 * Translate a v2 preferences blob (nested but with the old flat episode
 * fields) into the v3 shape (episode regrouped into show/behavior/recap).
 * Anything missing or malformed falls back to defaults.
 */
export function migrateV2Preferences(raw: Record<string, unknown>): AppSettings {
  const episode = (raw.episode as Record<string, unknown> | undefined) ?? {}
  const episodeShow = (episode.show as Record<string, unknown> | undefined) ?? {}

  return {
    ...defaultSettings,
    ...raw,
    episode: {
      show: {
        fillerChips: (episodeShow.fillerChips as boolean | undefined) ?? defaultEpisode.show.fillerChips,
        ratingChips: (episodeShow.ratingChips as boolean | undefined) ?? defaultEpisode.show.ratingChips,
        sparklines: (episodeShow.sparklines as boolean | undefined) ?? defaultEpisode.show.sparklines,
        spoilerBlur: (episode.spoilerBlur as boolean | undefined) ?? defaultEpisode.show.spoilerBlur,
      },
      behavior: {
        swipeActions: (episode.swipeActions as boolean | undefined) ?? defaultEpisode.behavior.swipeActions,
        sortMode: (episode.sortMode as EpisodeBehaviorSettings['sortMode'] | undefined) ?? defaultEpisode.behavior.sortMode,
        orderPreference: (episode.orderPreference as EpisodeBehaviorSettings['orderPreference'] | undefined) ?? defaultEpisode.behavior.orderPreference,
      },
      recap: {
        card: (episode.recapCard as boolean | undefined) ?? defaultEpisode.recap.card,
        gapDays: (episode.recapGapDays as number | undefined) ?? defaultEpisode.recap.gapDays,
      },
    },
  } as AppSettings
}
