// Season plans are whole-garden copies (POST /gardens/:id/copy): a separate // garden you rearrange freely while this year's stays put. The API keeps no link // between a copy and its source, so the relationship rides on the name the copy // is given by default — "Home Garden — 2027" — which is what the editor's season // control and the gardens list read back. Rename the copy and it is simply a // garden again; nothing breaks, it just stops being offered as a plan. const PLAN_RE = /^(.+?)\s+[—–-]\s+(\d{4})$/ export interface PlanName { base: string year: number } /** "Home Garden — 2027" → { base: "Home Garden", year: 2027 }, else null. */ export function parsePlanName(name: string): PlanName | null { const m = name.trim().match(PLAN_RE) if (!m) return null return { base: m[1].trim(), year: Number(m[2]) } } /** The default name for a plan copy of `base` for `year`. */ export function planNameFor(base: string, year: number): string { return `${base} — ${year}` } /** Whether a garden reads as a forward-looking plan (a year-suffixed copy for * this year or later). Past years are archives, not plans, and get no tag. */ export function planYearOf(name: string, now = new Date()): number | null { const p = parsePlanName(name) return p && p.year >= now.getFullYear() ? p.year : null } /** The plan copies of `base` among `gardens`, ascending by year. */ export function planGardensOf(base: string, gardens: readonly G[]): (G & { year: number })[] { const out: (G & { year: number })[] = [] for (const g of gardens) { const p = parsePlanName(g.name) if (p && p.base === base.trim()) out.push({ ...g, year: p.year }) } return out.sort((a, b) => a.year - b.year) } /** The first year from `from` on that `base` has no plan copy for among * `names`, so a new copy never proposes a name that is already taken — two * "Back Yard — 2027"s would both be offered as the 2027 plan. */ export function nextPlanYear(base: string, names: readonly string[], from: number): number { const taken = new Set() for (const n of names) { const p = parsePlanName(n) if (p && p.base === base.trim()) taken.add(p.year) } let year = from while (taken.has(year)) year += 1 return year }