import type { BaseItemDto } from '../api/types'

/**
 * Helpers that crunch the user's played-items list into the
 * personalisation stats surfaced on the /profile page (streaks, genre
 * breakdown, year-in-review).
 *
 * All inputs assume Jellyfin items with `UserData.LastPlayedDate` and
 * `RunTimeTicks` fields populated.
 */

const TICKS_PER_HOUR = 36_000_000_000

export interface GenreShare {
  genre: string
  count: number
  /** Fractional share of the total (0..1). */
  share: number
}

/**
 * Tally each genre's share of the played list. Items with multiple
 * genres count once per genre; the resulting shares can sum past 1.
 */
export function genreBreakdown(items: BaseItemDto[]): GenreShare[] {
  const counts = new Map<string, number>()
  let total = 0
  for (const it of items) {
    for (const g of it.Genres || []) {
      counts.set(g, (counts.get(g) || 0) + 1)
      total++
    }
  }
  if (total === 0) return []
  return [...counts.entries()]
    .map(([genre, count]) => ({ genre, count, share: count / total }))
    .sort((a, b) => b.count - a.count)
}

/**
 * Detect the user's current consecutive-days watch streak. A streak
 * counts every calendar day on which at least one item carries a
 * LastPlayedDate inside it. Today and yesterday both keep the streak
 * alive (so a user who hasn't yet watched today still sees a streak).
 */
export function watchStreak(items: BaseItemDto[]): {
  current: number
  longest: number
  lastWatchDate: string | null
} {
  const dayKeys = new Set<string>()
  let lastWatchDate: string | null = null
  for (const it of items) {
    const raw = it.UserData?.LastPlayedDate
    if (!raw) continue
    const d = new Date(raw)
    if (isNaN(d.getTime())) continue
    if (!lastWatchDate || raw > lastWatchDate) lastWatchDate = raw
    dayKeys.add(dayKey(d))
  }
  if (dayKeys.size === 0) {
    return { current: 0, longest: 0, lastWatchDate: null }
  }
  // Compute longest run by walking sorted unique days.
  const sorted = [...dayKeys].sort()
  let longest = 1
  let run = 1
  for (let i = 1; i < sorted.length; i++) {
    if (consecutiveDay(sorted[i - 1], sorted[i])) {
      run++
      if (run > longest) longest = run
    } else {
      run = 1
    }
  }
  // Compute current run starting from the most recent watched day,
  // counting back. If the last watch was more than 1 day ago we treat
  // the streak as broken.
  const now = new Date()
  const todayKey = dayKey(now)
  const yesterday = new Date(now)
  yesterday.setDate(now.getDate() - 1)
  const yesterdayKey = dayKey(yesterday)
  let current = 0
  if (dayKeys.has(todayKey) || dayKeys.has(yesterdayKey)) {
    let cursor = dayKeys.has(todayKey) ? todayKey : yesterdayKey
    while (dayKeys.has(cursor)) {
      current++
      const prev = stepBack(cursor)
      cursor = prev
    }
  }
  return { current, longest, lastWatchDate }
}

/**
 * Sum total hours watched across the input list. Uses RunTimeTicks
 * (full duration) per played item; this overstates a little when the
 * user only made it 60% through but is the only signal we have without
 * per-session playback history.
 */
export function totalHoursWatched(items: BaseItemDto[]): number {
  let ticks = 0
  for (const it of items) {
    ticks += Number(it.RunTimeTicks ?? 0)
  }
  return ticks / TICKS_PER_HOUR
}

/**
 * Longest binge - the maximum number of items with LastPlayedDate
 * timestamps falling within a single calendar day.
 */
export function longestBinge(items: BaseItemDto[]): { count: number; day: string | null } {
  const counts = new Map<string, number>()
  for (const it of items) {
    const raw = it.UserData?.LastPlayedDate
    if (!raw) continue
    const d = new Date(raw)
    if (isNaN(d.getTime())) continue
    const k = dayKey(d)
    counts.set(k, (counts.get(k) || 0) + 1)
  }
  let max = 0
  let day: string | null = null
  for (const [k, n] of counts) {
    if (n > max) {
      max = n
      day = k
    }
  }
  return { count: max, day }
}

export function dayKey(d: Date): string {
  const yyyy = d.getFullYear()
  const mm = String(d.getMonth() + 1).padStart(2, '0')
  const dd = String(d.getDate()).padStart(2, '0')
  return `${yyyy}-${mm}-${dd}`
}

function consecutiveDay(prev: string, next: string): boolean {
  const pd = new Date(prev + 'T00:00:00')
  const nd = new Date(next + 'T00:00:00')
  const diff = nd.getTime() - pd.getTime()
  return diff === 86_400_000
}

function stepBack(key: string): string {
  const d = new Date(key + 'T00:00:00')
  d.setDate(d.getDate() - 1)
  return dayKey(d)
}
