import { planScheduleSlots, parseProjectTimezone } from "./plan-times";
import { formatTimezoneShort } from "./resolve-schedule";

export const SLOT_WINDOW_MINUTES = 30;

export interface LocalDateParts {
  year: number;
  month: number;
  day: number;
  weekday: string;
  hour: number;
  minute: number;
}

export function getLocalDateParts(date: Date, timeZone: string): LocalDateParts {
  const parts = new Intl.DateTimeFormat("en-US", {
    timeZone,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    weekday: "short",
    hour: "numeric",
    minute: "numeric",
    hourCycle: "h23",
  }).formatToParts(date);

  return {
    year: Number(parts.find((p) => p.type === "year")?.value ?? 0),
    month: Number(parts.find((p) => p.type === "month")?.value ?? 0),
    day: Number(parts.find((p) => p.type === "day")?.value ?? 0),
    weekday: parts.find((p) => p.type === "weekday")?.value ?? "",
    hour: Number(parts.find((p) => p.type === "hour")?.value ?? 0),
    minute: Number(parts.find((p) => p.type === "minute")?.value ?? 0),
  };
}

/** Convert a wall-clock time in `timeZone` to a UTC Date (iterative DST-safe correction). */
export function zonedDateTimeToUtc(
  year: number,
  month: number,
  day: number,
  hour: number,
  minute: number,
  timeZone: string,
): Date {
  let utc = new Date(Date.UTC(year, month - 1, day, hour, minute, 0, 0));
  for (let i = 0; i < 4; i++) {
    const loc = getLocalDateParts(utc, timeZone);
    const delta =
      Date.UTC(year, month - 1, day, hour, minute) -
      Date.UTC(loc.year, loc.month - 1, loc.day, loc.hour, loc.minute);
    if (delta === 0) break;
    utc = new Date(utc.getTime() + delta);
  }
  return utc;
}

function sameLocalDay(a: LocalDateParts, b: LocalDateParts) {
  return a.year === b.year && a.month === b.month && a.day === b.day;
}

function dayLabel(nowParts: LocalDateParts, slotParts: LocalDateParts): string {
  if (sameLocalDay(nowParts, slotParts)) return "Today";
  const tomorrow = new Date(Date.UTC(nowParts.year, nowParts.month - 1, nowParts.day + 1));
  const tomorrowParts = {
    year: tomorrow.getUTCFullYear(),
    month: tomorrow.getUTCMonth() + 1,
    day: tomorrow.getUTCDate(),
  };
  if (
    slotParts.year === tomorrowParts.year &&
    slotParts.month === tomorrowParts.month &&
    slotParts.day === tomorrowParts.day
  ) {
    return "Tomorrow";
  }
  return slotParts.weekday;
}

export interface NextScheduleSlotInfo {
  label: string;
  display: string;
  slotTimeLocal: string;
  dayLabel: string;
  slotAt: string;
  windowOpensAt: string;
  windowClosesAt: string;
  isWindowActive: boolean;
  msUntilWindowOpens: number;
  msUntilWindowCloses: number;
}

export function formatLocalDateTime(date: Date, timeZone: string, tzLabel?: string): string {
  const label = tzLabel ?? formatTimezoneShort(timeZone, date);
  const formatted = new Intl.DateTimeFormat("en-US", {
    timeZone,
    hour: "numeric",
    minute: "2-digit",
    hour12: true,
  }).format(date);
  return `${formatted} ${label}`;
}

export function computeNextScheduleSlot(input: {
  timezone: string;
  postsPerDay: 1 | 2;
  scheduleSeed: number;
  now?: Date;
}): {
  timezone: string;
  timezoneLabel: string;
  currentTimeLocal: string;
  currentDateLocal: string;
  activeSlotsNow: string[];
  scheduleStatus: "active_window" | "waiting";
  nextSlot: NextScheduleSlotInfo | null;
  allSlotsToday: string[];
} {
  const timeZone = parseProjectTimezone(input.timezone);
  const now = input.now ?? new Date();
  const tzLabel = formatTimezoneShort(timeZone, now);
  const nowParts = getLocalDateParts(now, timeZone);
  const slots = planScheduleSlots(input.postsPerDay, input.scheduleSeed);
  const windowMs = SLOT_WINDOW_MINUTES * 60 * 1000;

  const candidates: NextScheduleSlotInfo[] = [];

  for (let dayOffset = 0; dayOffset <= 1; dayOffset++) {
    const base = new Date(Date.UTC(nowParts.year, nowParts.month - 1, nowParts.day + dayOffset));
    const y = base.getUTCFullYear();
    const m = base.getUTCMonth() + 1;
    const d = base.getUTCDate();

    for (const slot of slots) {
      const slotAt = zonedDateTimeToUtc(y, m, d, slot.hour, slot.minute, timeZone);
      const windowOpensAt = new Date(slotAt.getTime() - windowMs);
      const windowClosesAt = new Date(slotAt.getTime() + windowMs);
      const slotParts = getLocalDateParts(slotAt, timeZone);
      const h12 = slot.hour % 12 || 12;
      const ampm = slot.hour < 12 ? "AM" : "PM";
      const slotTimeLocal = `${h12}:${slot.minute.toString().padStart(2, "0")} ${ampm}`;

      candidates.push({
        label: slot.label,
        display: slot.display,
        slotTimeLocal,
        dayLabel: dayLabel(nowParts, slotParts),
        slotAt: slotAt.toISOString(),
        windowOpensAt: windowOpensAt.toISOString(),
        windowClosesAt: windowClosesAt.toISOString(),
        isWindowActive: now >= windowOpensAt && now <= windowClosesAt,
        msUntilWindowOpens: Math.max(0, windowOpensAt.getTime() - now.getTime()),
        msUntilWindowCloses: Math.max(0, windowClosesAt.getTime() - now.getTime()),
      });
    }
  }

  const active = candidates.filter((c) => c.isWindowActive);
  const future = candidates
    .filter((c) => new Date(c.windowOpensAt) > now)
    .sort((a, b) => new Date(a.windowOpensAt).getTime() - new Date(b.windowOpensAt).getTime());

  const nextSlot = active[0] ?? future[0] ?? null;

  const currentDateLocal = new Intl.DateTimeFormat("en-US", {
    timeZone,
    weekday: "long",
    month: "short",
    day: "numeric",
  }).format(now);

  return {
    timezone: timeZone,
    timezoneLabel: tzLabel,
    currentTimeLocal: formatLocalDateTime(now, timeZone, tzLabel),
    currentDateLocal,
    activeSlotsNow: active.map((s) => `${s.dayLabel} · ${s.display}`),
    scheduleStatus: active.length > 0 ? "active_window" : "waiting",
    nextSlot,
    allSlotsToday: slots.map((s) => `${s.display} ${tzLabel}`),
  };
}

export function formatDuration(ms: number): string {
  if (ms <= 0) return "now";
  const totalMinutes = Math.ceil(ms / 60_000);
  const hours = Math.floor(totalMinutes / 60);
  const minutes = totalMinutes % 60;
  if (hours > 0) return `${hours}h ${minutes}m`;
  return `${minutes}m`;
}
