// Task 390: deterministic relative-date parser.
//
// Parses a free-text date phrase against a reference timestamp and returns
// the resolved Date plus the form that matched. Used by:
//   - timeline-extractor.ts (sentence-scoped parse during memory-write)
//   - datetime/SKILL.md (operator-facing arithmetic via `node -e`)
//
// Both consumers need identical semantics; this is the single source of
// truth. The parser is deterministic — no LLM, no chrono-node. Forms not
// listed here return null; the caller decides what to do with that.
//
// Recognised forms (priority order — first match wins):
//   1. Literal day words           — yesterday / today / tomorrow
//   2. Last/next/this <weekday>    — "last Tuesday", "next Fri"
//   3. N units ago                 — "two weeks ago", "3 days ago"
//   4. Holiday anchors             — "this Christmas", "this New Year"
//   5. Fiscal quarter, year known  — "Q3 2024", "Q1 25"
//   6. Fiscal quarter, year word   — "Q3 last year", "Q1 this year"
//
// Recurring forms ("every other Friday") return null intentionally —
// they belong on :Routine, not :TimelineEvent.
const WEEKDAYS = {
    sun: 0, sunday: 0,
    mon: 1, monday: 1,
    tue: 2, tues: 2, tuesday: 2,
    wed: 3, weds: 3, wednesday: 3,
    thu: 4, thur: 4, thurs: 4, thursday: 4,
    fri: 5, friday: 5,
    sat: 6, saturday: 6,
};
const WORD_NUMBERS = {
    one: 1, two: 2, three: 3, four: 4, five: 5,
    six: 6, seven: 7, eight: 8, nine: 9, ten: 10,
    eleven: 11, twelve: 12,
};
function startOfDay(d) {
    const out = new Date(d.getTime());
    out.setUTCHours(0, 0, 0, 0);
    return out;
}
function addDays(d, n) {
    const out = new Date(d.getTime());
    out.setUTCDate(out.getUTCDate() + n);
    return out;
}
function addMonths(d, n) {
    const out = new Date(d.getTime());
    out.setUTCMonth(out.getUTCMonth() + n);
    return out;
}
function addYears(d, n) {
    const out = new Date(d.getTime());
    out.setUTCFullYear(out.getUTCFullYear() + n);
    return out;
}
/** Returns null for "every other X" / "every X" — recurring is not a TimelineEvent. */
function isRecurring(text) {
    return /\bevery(\s+other)?\s+/i.test(text);
}
function parseLiteralDay(text, ref) {
    const m = text.match(/\b(yesterday|today|tomorrow)\b/i);
    if (!m)
        return null;
    const word = m[1].toLowerCase();
    const base = startOfDay(ref);
    if (word === "yesterday")
        return { date: addDays(base, -1), form: "literal-day", matched: m[0] };
    if (word === "tomorrow")
        return { date: addDays(base, 1), form: "literal-day", matched: m[0] };
    return { date: base, form: "literal-day", matched: m[0] };
}
function parseWeekday(text, ref) {
    // "last Tuesday", "next Fri", "this Monday"
    const m = text.match(/\b(last|next|this)\s+([A-Za-z]+)\b/i);
    if (!m)
        return null;
    const qualifier = m[1].toLowerCase();
    const target = WEEKDAYS[m[2].toLowerCase()];
    if (target === undefined)
        return null;
    const base = startOfDay(ref);
    const refDow = base.getUTCDay();
    let delta;
    if (qualifier === "last") {
        delta = target - refDow;
        if (delta >= 0)
            delta -= 7;
    }
    else if (qualifier === "next") {
        delta = target - refDow;
        if (delta <= 0)
            delta += 7;
    }
    else {
        // "this <weekday>" — the matching day in the current ISO week
        // (Mon-Sun). For ref = Wed, "this Monday" is two days ago;
        // "this Friday" is two days ahead. Always within +/-6 days of ref.
        const isoDow = refDow === 0 ? 7 : refDow; // Mon=1..Sun=7
        const isoTarget = target === 0 ? 7 : target;
        delta = isoTarget - isoDow;
    }
    return { date: addDays(base, delta), form: "weekday", matched: m[0] };
}
function parseUnitsAgo(text, ref) {
    // "two weeks ago", "3 days ago", "a month ago"
    const m = text.match(/\b(a|an|\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+(day|week|month|year)s?\s+ago\b/i);
    if (!m)
        return null;
    const numWord = m[1].toLowerCase();
    let n;
    if (numWord === "a" || numWord === "an")
        n = 1;
    else if (WORD_NUMBERS[numWord] !== undefined)
        n = WORD_NUMBERS[numWord];
    else
        n = Number(numWord);
    if (!Number.isFinite(n) || n <= 0)
        return null;
    const unit = m[2].toLowerCase();
    const base = startOfDay(ref);
    let date;
    if (unit === "day")
        date = addDays(base, -n);
    else if (unit === "week")
        date = addDays(base, -7 * n);
    else if (unit === "month")
        date = addMonths(base, -n);
    else
        date = addYears(base, -n);
    return { date, form: "ago", matched: m[0] };
}
function parseHoliday(text, ref) {
    // "this Christmas" -> 25 Dec of reference year.
    // "this New Year" -> 1 Jan of reference year.
    const xmas = text.match(/\b(?:this\s+)?Christmas\b/i);
    if (xmas) {
        const d = new Date(Date.UTC(ref.getUTCFullYear(), 11, 25));
        return { date: d, form: "holiday", matched: xmas[0] };
    }
    const ny = text.match(/\b(?:this\s+)?New\s+Year('s)?(\s+Day)?\b/i);
    if (ny) {
        const d = new Date(Date.UTC(ref.getUTCFullYear(), 0, 1));
        return { date: d, form: "holiday", matched: ny[0] };
    }
    return null;
}
function quarterStart(year, q) {
    // Q1 -> Jan 1, Q2 -> Apr 1, Q3 -> Jul 1, Q4 -> Oct 1.
    return new Date(Date.UTC(year, (q - 1) * 3, 1));
}
function parseQuarter(text, ref) {
    // "Q3 2024", "Q1 25", "Q4 last year", "Q2 this year", "Q1 next year"
    const explicit = text.match(/\bQ([1-4])\s+(\d{2}|\d{4})\b/i);
    if (explicit) {
        const q = Number(explicit[1]);
        let y = Number(explicit[2]);
        if (y < 100)
            y = 2000 + y;
        if (y < 1900 || y > 2099)
            return null;
        return { date: quarterStart(y, q), form: "quarter", matched: explicit[0] };
    }
    const relative = text.match(/\bQ([1-4])\s+(last|this|next)\s+year\b/i);
    if (relative) {
        const q = Number(relative[1]);
        const word = relative[2].toLowerCase();
        const refYear = ref.getUTCFullYear();
        const y = word === "last" ? refYear - 1 : word === "next" ? refYear + 1 : refYear;
        return { date: quarterStart(y, q), form: "quarter", matched: relative[0] };
    }
    return null;
}
/**
 * Parse `text` against `reference`. Returns the first matching form or null.
 * Recurring phrases ("every other Friday") return null by design.
 */
export function parseRelativeDate(text, reference) {
    if (!text || typeof text !== "string")
        return null;
    if (!(reference instanceof Date) || Number.isNaN(reference.getTime()))
        return null;
    if (isRecurring(text))
        return null;
    return (parseLiteralDay(text, reference) ??
        parseWeekday(text, reference) ??
        parseUnitsAgo(text, reference) ??
        parseHoliday(text, reference) ??
        parseQuarter(text, reference) ??
        null);
}
//# sourceMappingURL=relative-date.js.map