import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

const SEO_PATTERNS: RegExp[] = [
  /\bFeatured In Group[^.!?]*[.!?]?/gi,
  /\busers? who liked this recipe also liked[^.!?]*[.!?]?/gi,
  /\bSimilar recipes (include|are)[^.!?]*[.!?]?/gi,
  /\b\d+ people (found this recipe|made this recipe|have made this recipe)[^.!?]*[.!?]?/gi,
  /\bOverall,? this recipe earns[^.!?]*[.!?]?/gi,
  /\b\d+ people have made this recipe and would make it again\.?/gi,
  /\b\d+ person has tried and liked this recipe\.?/gi,
  /\bFor \$[\d.]+ per serving,? this recipe covers[^.!?]*[.!?]?/gi,
  /\bThis recipe serves \d+\.?\s?/gi,
  /\bIt is brought to you by [^.!?]+[.!?]?/gi,
  /\bFrom preparation to the plate,? this recipe takes[^.!?]*[.!?]?/gi,
  /\bIt works well as [^.!?]+[.!?]?/gi,
  /\bIf you have [^,.]+(,\s*[^,.]+){0,5},? you can make it\.?/gi,
  /\bOne serving contains [^.!?]+[.!?]?/gi,
  /\bFor \d+ cents per serving,? this recipe covers[^.!?]*[.!?]?/gi,
];

export function cleanDescription(input: string | undefined | null, maxLen = 280): string {
  if (!input) return '';
  let s = input;
  for (const re of SEO_PATTERNS) s = s.replace(re, '');
  s = s.replace(/\s{2,}/g, ' ').replace(/\.\s*\./g, '.').trim();
  if (s.length > maxLen) {
    const cut = s.slice(0, maxLen);
    const lastDot = cut.lastIndexOf('.');
    s = (lastDot > 80 ? cut.slice(0, lastDot + 1) : cut.trimEnd() + '…');
  }
  return s;
}

/* ------------------------------------------------------------------ */
/*  Week helpers (used by F5 URL week selector + Sprint 7 WeekRangeNav) */
/* ------------------------------------------------------------------ */

/**
 * Return the ISO date (YYYY-MM-DD) of the *upcoming* Monday for the given date.
 * - If `d` is a Monday → returns `d` itself.
 * - Otherwise → returns the next Monday (1..6 days ahead).
 *
 * Pairs with `runner._current_week_start()` on the backend (see
 * `backend/app/services/orchestrator/runner.py`). "This week" means the
 * upcoming Mon-Sun week the Friday email advertises.
 */
export function upcomingMonday(d: Date = new Date()): string {
  const day = d.getUTCDay() // 0=Sun, 1=Mon, ..., 6=Sat
  if (day === 0) {
    // Sunday: upcoming Monday is tomorrow (1 day ahead).
    const next = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + 1))
    return next.toISOString().slice(0, 10)
  }
  if (day === 1) {
    // Monday: today.
    return d.toISOString().slice(0, 10)
  }
  // Tue..Sat: next Monday is (7 - day) days ahead.
  const offset = 7 - day + 1
  const next = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + offset))
  return next.toISOString().slice(0, 10)
}

/**
 * Legacy alias kept for any external code that might still reference the
 * calendar-week Monday. The frontend codebase has been updated to use
 * `upcomingMonday`; this alias is intentionally a no-op redirect to the
 * new function with a deprecation note.
 *
 * @deprecated Use `upcomingMonday()` — see Sprint 7 root-cause in
 * `Review/handoff-ui-audit.md`. The "current calendar week" semantic
 * mismatched the orchestrator's "upcoming week" semantic, causing the
 * webui to show an empty state on Fridays.
 */
export function isoMonday(d: Date = new Date()): string {
  return upcomingMonday(d)
}

/** Parse a YYYY-MM-DD string into a Date (UTC midnight). Returns null if invalid. */
export function parseIsoDate(s: string): Date | null {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return null
  const d = new Date(`${s}T00:00:00Z`)
  return Number.isNaN(d.getTime()) ? null : d
}

/** Shift an ISO date string by `days` calendar days. */
export function shiftIsoDate(s: string, days: number): string {
  const d = parseIsoDate(s)
  if (!d) return s
  d.setUTCDate(d.getUTCDate() + days)
  return d.toISOString().slice(0, 10)
}

/**
 * Format a YYYY-MM-DD string for display: "Jun 1, 2026".
 * Always renders in UTC so the displayed date matches the ISO date
 * stored in the DB regardless of the viewer's local timezone.
 */
export function formatIsoDate(s: string): string {
  const d = parseIsoDate(s)
  if (!d) return s
  return d.toLocaleDateString(undefined, {
    month: 'short',
    day: 'numeric',
    year: 'numeric',
    timeZone: 'UTC',
  })
}

/**
 * Format a Monday's ISO date as a Mon–Sun range label: "Jun 8 — Jun 14".
 * Used by `WeekRangeNav` (Sprint 7) for the visible week range.
 *
 * Always renders in UTC so the date the user sees matches the ISO date
 * stored in the DB regardless of the viewer's local timezone.
 */
export function formatWeekRange(mondayIso: string): string {
  const monday = parseIsoDate(mondayIso)
  if (!monday) return mondayIso
  const sunday = new Date(monday)
  sunday.setUTCDate(sunday.getUTCDate() + 6)
  const fmt = (d: Date) =>
    d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', timeZone: 'UTC' })
  return `${fmt(monday)} — ${fmt(sunday)}`
}
