import type { FormatPrefs } from './date';

type ParseInput = string | null | undefined;

const RE_REL = /^\s*([+-])\s*(\d+)\s*([dwm])\s*$/i;
const RE_NUMERIC = /^(\d{1,4})[/\-.](\d{1,4})(?:[/\-.](\d{1,4}))?$/;

function startOfDay(d: Date): Date {
  const out = new Date(d.getFullYear(), d.getMonth(), d.getDate());
  return out;
}

function addDays(d: Date, n: number): Date {
  const out = new Date(d);
  out.setDate(out.getDate() + n);
  return out;
}

function addMonths(d: Date, n: number): Date {
  const out = new Date(d);
  out.setMonth(out.getMonth() + n);
  return out;
}

function pivotYear(twoDigit: number): number {
  const now = new Date().getFullYear();
  const century = Math.floor(now / 100) * 100;
  const offset = now % 100;
  return twoDigit > (offset + 50) ? century - 100 + twoDigit : century + twoDigit;
}

function parseNumeric(
  input: string,
  prefs: FormatPrefs,
  base: Date,
): Date | null {
  const m = input.match(RE_NUMERIC);
  if (!m) return null;
  let day: number, month: number, year: number;
  const a = parseInt(m[1], 10);
  const b = parseInt(m[2], 10);
  const c = m[3] != null ? parseInt(m[3], 10) : NaN;

  if (prefs.dateFormat === 'yyyy-MM-dd') {
    if (m[3] == null) return null;
    year = a; month = b; day = c;
  } else if (prefs.dateFormat === 'MM/dd/yyyy') {
    month = a; day = b;
    if (m[3] == null) year = base.getFullYear();
    else year = c < 100 ? pivotYear(c) : c;
  } else {
    // default: 'dd/MM/yyyy'
    day = a; month = b;
    if (m[3] == null) year = base.getFullYear();
    else year = c < 100 ? pivotYear(c) : c;
  }

  if (month < 1 || month > 12) return null;
  const result = new Date(year, month - 1, day);
  if (result.getFullYear() !== year || result.getMonth() !== month - 1 || result.getDate() !== day) {
    return null;
  }
  if (m[3] == null && result < startOfDay(base)) {
    result.setFullYear(year + 1);
    if (result.getDate() !== day) return null;
  }
  return result;
}

function parseKeyword(
  word: string,
  keywords: { today: string[]; tomorrow: string[]; yesterday: string[]; weekdays: string[][] },
  base: Date,
): Date | null {
  const w = word.trim().toLowerCase();
  if (keywords.today.some((k) => k.toLowerCase() === w)) return startOfDay(base);
  if (keywords.tomorrow.some((k) => k.toLowerCase() === w)) return addDays(startOfDay(base), 1);
  if (keywords.yesterday.some((k) => k.toLowerCase() === w)) return addDays(startOfDay(base), -1);
  for (let i = 0; i < keywords.weekdays.length; i++) {
    if (keywords.weekdays[i].some((k) => k.toLowerCase() === w)) {
      const today = startOfDay(base);
      const diff = (i - today.getDay() + 7) % 7 || 7;
      return addDays(today, diff);
    }
  }
  return null;
}

export type DateKeywords = {
  today: string[];
  tomorrow: string[];
  yesterday: string[];
  /** Index 0=Sunday, 6=Saturday. Each entry: aliases (short + long). */
  weekdays: string[][];
};

export function parseDate(
  input: ParseInput,
  prefs: FormatPrefs,
  keywords: DateKeywords,
  baseDate: Date = new Date(),
): Date | null {
  if (!input) return null;
  const trimmed = input.trim();
  if (!trimmed) return null;

  const rel = trimmed.match(RE_REL);
  if (rel) {
    const sign = rel[1] === '-' ? -1 : 1;
    const n = sign * parseInt(rel[2], 10);
    const unit = rel[3].toLowerCase();
    const base = startOfDay(baseDate);
    if (unit === 'd') return addDays(base, n);
    if (unit === 'w') return addDays(base, n * 7);
    if (unit === 'm') return addMonths(base, n);
  }

  const kw = parseKeyword(trimmed, keywords, baseDate);
  if (kw) return kw;

  const num = parseNumeric(trimmed, prefs, baseDate);
  if (num) return num;

  return null;
}

export function parseDateRange(
  input: ParseInput,
  prefs: FormatPrefs,
  keywords: DateKeywords,
  baseDate: Date = new Date(),
): { from: Date; to?: Date } | null {
  if (!input) return null;
  const parts = input.split(/\s+(?:-{1,2}|–|to)\s+/i);
  if (parts.length === 1) {
    const single = parseDate(parts[0], prefs, keywords, baseDate);
    return single ? { from: single } : null;
  }
  if (parts.length === 2) {
    const from = parseDate(parts[0], prefs, keywords, baseDate);
    if (!from) return null;
    const to = parseDate(parts[1], prefs, keywords, from);
    if (!to) return null;
    return { from, to };
  }
  return null;
}
