export interface ClassSchedule {
  id: number;
  class_room_id: number;
  subject_id?: number | null;
  day_of_week: number;
  week_type?: string | null;
}

/** Matches backend TeacherPanelController: Saturday=0 … Friday=6 */
export function gregorianDayOfWeek(iso: string): number {
  const jsDay = new Date(iso + "T12:00:00").getDay();
  return (jsDay + 1) % 7;
}

function isoWeekNumber(iso: string): number {
  const d = new Date(iso + "T12:00:00");
  const target = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
  const dayNum = target.getUTCDay() || 7;
  target.setUTCDate(target.getUTCDate() + 4 - dayNum);
  const yearStart = new Date(Date.UTC(target.getUTCFullYear(), 0, 1));
  return Math.ceil(((target.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
}

function matchesWeekType(iso: string, weekType?: string | null): boolean {
  const type = weekType ?? "fixed";
  if (type === "fixed") return true;
  const week = isoWeekNumber(iso);
  if (type === "odd") return week % 2 === 1;
  if (type === "even") return week % 2 === 0;
  return true;
}

function eachDayInRange(from: string, to: string): string[] {
  const days: string[] = [];
  const cursor = new Date(from + "T12:00:00");
  const end = new Date(to + "T12:00:00");
  while (cursor <= end) {
    days.push(cursor.toISOString().slice(0, 10));
    cursor.setDate(cursor.getDate() + 1);
  }
  return days;
}

export function monthRangeGregorian(ym: string): { from: string; to: string } {
  const [y, m] = ym.split("-").map(Number);
  const from = `${y}-${String(m).padStart(2, "0")}-01`;
  const last = new Date(y, m, 0).getDate();
  const to = `${y}-${String(m).padStart(2, "0")}-${String(last).padStart(2, "0")}`;
  return { from, to };
}

export function computeSessionDates(
  from: string,
  to: string,
  schedules: ClassSchedule[],
  opts?: { scheduleId?: number | null; subjectId?: number | null },
): string[] {
  let filtered = schedules;
  if (opts?.scheduleId) {
    filtered = schedules.filter((s) => s.id === opts.scheduleId);
  } else if (opts?.subjectId) {
    filtered = schedules.filter((s) => s.subject_id === opts.subjectId);
  }

  if (!filtered.length) return [];

  const result = new Set<string>();
  for (const day of eachDayInRange(from, to)) {
    const dow = gregorianDayOfWeek(day);
    for (const schedule of filtered) {
      if (schedule.day_of_week === dow && matchesWeekType(day, schedule.week_type)) {
        result.add(day);
      }
    }
  }

  return [...result].sort();
}

/** Session dates on or before `maxDate` (default today), sorted ascending. */
export function sessionDatesUpTo(
  from: string,
  to: string,
  schedules: ClassSchedule[],
  opts?: { scheduleId?: number | null; subjectId?: number | null },
  maxDate?: string,
): string[] {
  const cap = maxDate ?? to;
  return computeSessionDates(from, to, schedules, opts).filter((d) => d <= cap);
}

export function previousSessionDate(sessionDates: string[], current: string): string | null {
  const sorted = sessionDates.filter((d) => d < current);
  return sorted.length ? sorted[sorted.length - 1] : null;
}

export function nextSessionDate(
  sessionDates: string[],
  current: string,
  maxDate?: string,
): string | null {
  const cap = maxDate ?? current;
  const sorted = sessionDates.filter((d) => d > current && d <= cap);
  return sorted.length ? sorted[0] : null;
}

/** Prefer same day; else latest past session; else earliest future session up to maxDate. */
export function nearestSessionDate(
  sessionDates: string[],
  current: string,
  maxDate?: string,
): string | null {
  if (sessionDates.includes(current)) return current;
  const prev = previousSessionDate(sessionDates, current);
  if (prev) return prev;
  return nextSessionDate(sessionDates, current, maxDate);
}

export function gregorianMonthFromDate(iso: string): string {
  return iso.slice(0, 7);
}
