import type { CoursePlanWithItems } from "../types"

/**
 * course-plans 日历视图工具（纯函数，便于单测）。
 *
 * 将周计划项映射到日历日期范围：
 * - 若 plan.startDate 存在：第 N 周对应 [startDate + (N-1)*7, startDate + N*7 - 1]
 * - 若 plan.startDate 缺失：返回 null（无法映射到日期）
 */

/** 日历事件（一个周计划项对应一个事件） */
export interface CalendarEvent {
  /** 事件唯一 ID（使用 item.id） */
  id: string
  /** 事件标题（使用 item.topic） */
  title: string
  /** 起始日期（ISO 字符串，YYYY-MM-DD） */
  startDate: string
  /** 结束日期（ISO 字符串，YYYY-MM-DD，含） */
  endDate: string
  /** 周次 */
  week: number
  /** 课时 */
  hours: number
  /** 是否已完成 */
  isCompleted: boolean
  /** 教材章节 */
  textbookChapter: string | null
}

/** 将日期对象格式化为 YYYY-MM-DD（不依赖 date-fns） */
export function formatDateISO(date: Date): string {
  const y = date.getFullYear()
  const m = String(date.getMonth() + 1).padStart(2, "0")
  const d = String(date.getDate()).padStart(2, "0")
  return `${y}-${m}-${d}`
}

/** 解析 YYYY-MM-DD 字符串为本地日期（避免 UTC 偏移） */
export function parseISODate(iso: string): Date {
  const [y, m, d] = iso.split("-").map(Number)
  return new Date(y, (m ?? 1) - 1, d ?? 1)
}

/** 计算某日所在周的周日（作为周起始；遵循 ISO 8601 周一为周首） */
export function startOfWeek(date: Date): Date {
  const d = new Date(date)
  const day = d.getDay() // 0=Sunday, 1=Monday, ...
  const diff = (day === 0 ? -6 : 1 - day) // 周一为周首
  d.setDate(d.getDate() + diff)
  d.setHours(0, 0, 0, 0)
  return d
}

/** 计算某日所在月的首日 */
export function startOfMonth(date: Date): Date {
  return new Date(date.getFullYear(), date.getMonth(), 1)
}

/** 计算某日所在月的末日 */
export function endOfMonth(date: Date): Date {
  return new Date(date.getFullYear(), date.getMonth() + 1, 0)
}

/** 在日期上加天数 */
export function addDays(date: Date, days: number): Date {
  const d = new Date(date)
  d.setDate(d.getDate() + days)
  return d
}

/** 在日期上加月数 */
export function addMonths(date: Date, months: number): Date {
  const d = new Date(date)
  d.setMonth(d.getMonth() + months)
  return d
}

/** 判断两日期是否同一天 */
export function isSameDay(a: Date, b: Date): boolean {
  return (
    a.getFullYear() === b.getFullYear() &&
    a.getMonth() === b.getMonth() &&
    a.getDate() === b.getDate()
  )
}

/** 判断日期 a 是否在 [start, end] 区间内（含端点） */
export function isWithinRange(date: Date, start: Date, end: Date): boolean {
  const t = date.getTime()
  return t >= start.getTime() && t <= end.getTime()
}

/**
 * 将课程计划及其周计划项转换为日历事件列表（纯函数）。
 *
 * - 需要 `plan.startDate` 才能计算每周的日期范围
 * - 第 N 周对应日期范围 [startDate + (N-1)*7, startDate + N*7 - 1]
 *
 * @returns 事件列表；若无 startDate 则返回空数组
 */
export function planToCalendarEvents(plan: CoursePlanWithItems): CalendarEvent[] {
  if (!plan.startDate) return []

  const start = parseISODate(plan.startDate)
  return plan.items.map((item) => {
    const weekStart = addDays(start, (item.week - 1) * 7)
    const weekEnd = addDays(weekStart, 6) // 周一至周日
    return {
      id: item.id,
      title: item.topic,
      startDate: formatDateISO(weekStart),
      endDate: formatDateISO(weekEnd),
      week: item.week,
      hours: item.hours,
      isCompleted: item.isCompleted,
      textbookChapter: item.textbookChapter,
    }
  })
}

/**
 * 生成日历网格：返回覆盖给定月份所需的 6 周 × 7 天 = 42 天的日期数组。
 *
 * 网格从月份首日所在周的周一开始，确保整月可见。
 *
 * @param monthDate 月份内任意一天
 * @returns 42 个 Date 对象（6 行 × 7 列）
 */
export function buildMonthGrid(monthDate: Date): Date[] {
  const monthStart = startOfMonth(monthDate)
  const gridStart = startOfWeek(monthStart)
  return Array.from({ length: 42 }, (_, i) => addDays(gridStart, i))
}

/**
 * 过滤在给定日期范围内有重叠的事件。
 *
 * @param events 事件列表
 * @param rangeStart 范围开始
 * @param rangeEnd 范围结束
 */
export function filterEventsInRange(
  events: readonly CalendarEvent[],
  rangeStart: Date,
  rangeEnd: Date,
): CalendarEvent[] {
  return events.filter((event) => {
    const eventStart = parseISODate(event.startDate)
    const eventEnd = parseISODate(event.endDate)
    // 区间相交判断：eventStart <= rangeEnd && eventEnd >= rangeStart
    return eventStart.getTime() <= rangeEnd.getTime() &&
      eventEnd.getTime() >= rangeStart.getTime()
  })
}

/**
 * 返回某一天的事件列表（事件日期范围包含该天）。
 */
export function eventsOnDay(
  events: readonly CalendarEvent[],
  day: Date,
): CalendarEvent[] {
  return events.filter((event) => {
    const eventStart = parseISODate(event.startDate)
    const eventEnd = parseISODate(event.endDate)
    return isWithinRange(day, eventStart, eventEnd)
  })
}
