// Sanctoral occurrence resolution: given a date, which saint(s) are
// assigned via data/calendar/sanctoral-calendar.yml's day -> saint-id
// mapping, and their rank from data/calendar/saints/<id>.yml. See
// calendar/temporal.ts for the separate temporal-cycle resolution, and
// calendar/commemorations.ts for how a candidate returned here actually
// gets weighed against the day's temporal standing.
//
// Doesn't resolve clashes *among* multiple saints sharing a date — real
// practice has its own precedence/commemoration rules for that too, not
// modeled here. If a day has more than one candidate, calendar/index.ts
// just picks the highest-ranked as the day's sole sanctoral contender.
import type { FeastClass, OctaveConfig } from './types';
import sanctoralCalendarData from '../data/calendar/sanctoral-calendar.yml';

export interface SaintRecord {
  id: string;
  name: string;
  /** Latin form of `name`, when authored — see calendar/types.ts's
   * `SanctoralIdentity.nameLa` doc comment for the display fallback. */
  nameLa?: string;
  rank: FeastClass;
  common: string;
  propers: string | null;
  /** Fallback id for Prime/Terce/Sext/None's antiphon/chapter when this
   * saint has no proper file of their own for a given hour (see
   * hours/resolve-common.ts's resolveMinorHourAntiphon/Chapter) — a
   * *different* id than `common` above, deliberately: live-verified that
   * the minor hours don't always draw from the same Common category as
   * the saint's overall classification. E.g. both Chair of St. Peter
   * feasts are `common: common-of-an-apostle` (Peter's own rank/collect
   * classification), but their Prime/Terce/Sext/None content is drawn
   * from the Common of a Confessor Bishop instead (Peter considered as
   * bishop, for that specific commemoration) — `minorHoursCommon:
   * common-of-a-confessor-bishop` captures that without corrupting the
   * saint's real `common` field. Absent for saints with no shared-Common
   * minor-hour content (either fully proper, or not yet authored).
   */
  minorHoursCommon?: string;
  /**
   * Fallback id for the day collect (`getDayCollect`) when this saint has
   * no proper collect of their own (`propers` may still be non-null —
   * e.g. St. Agatha and St. Lucy both have a proper antiphon but a
   * Common-sourced collect) — the *full* id of a `data/propers/common/
   * collect-c*.yml` file (e.g. `collect-c2-1`, not `c2-1` — no suffix
   * gets appended), a *template* with a literal `{N}` standing in for
   * the saint's own name, substituted via `collectName` below.
   * Deliberately more granular than `common` above: the reference
   * source's own Commune files split e.g. "Common of a Martyr Bishop"
   * into several differently-worded collect templates (its own `C2`,
   * `C2-1`, `C2a-1`, `C2b`, `C2b-1` — no clean semantic label recovered
   * for *why* each saint gets a particular one, so these ids trace
   * straight back to the source's own naming, e.g. `collect-c2-1`,
   * rather than inventing a name that might be wrong.
   */
  collectCommon?: string;
  /** The `{N}` substitution values for `collectCommon`'s template, in
   * whatever grammatical case that specific template's own placeholder
   * position needs — taken directly from the reference source's own
   * per-saint `[Name]` section (Latin/English/Sancti or SanctiM
   * `MM-DD.txt`), not re-declined by hand. Arrays, filled in
   * left-to-right against the template's own `{N}` occurrences in
   * document order — most saints need exactly one name, but several
   * Common templates (e.g. `collect-c3`, two co-named Martyr-Bishops)
   * have two or more `{N}` placeholders for a group commemorated
   * together, one array entry per saint in the group. */
  collectName?: { la: string[]; en: string[] };
  /**
   * Fallback id for Lauds' Benedictus antiphon (`getBenedictusAntiphon`)
   * when this saint has no proper `${propers}-antiphon` of their own —
   * `data/propers/common/benedictus-antiphon-${id}.yml`. Unlike
   * `collectCommon`, no name-substitution template: live-verified every
   * Common category's Benedictus antiphon in current use is name-free
   * text (the source's own `Commune/C4a.txt`, Doctor, is the one
   * exception — has a `{N}`-shaped "beáte N." — but no saint on this
   * calendar draws from it yet, so no substitution mechanism was built
   * for this field; revisit if that changes). Often, but not always, the
   * same category as `minorHoursCommon` below (both were live-verified
   * independently per saint, not assumed identical).
   */
  benedictusCommon?: string;
  /**
   * The `{N}` substitution value(s) for the Matins invitatory antiphon's
   * own Common template (`hours/matins.ts`'s `resolveMatinsInvitatoryText`,
   * via `hours/resolve-common.ts`'s `substituteName` — the same helper
   * `getDayCollect` already uses for `collectCommon`/`collectName`, just a
   * separate field here since the invitatory's own `{N}` position needs
   * its own grammatical case, not necessarily the same one `collectName`
   * was declined for). Currently only needed for Common-of-a-Holy-Woman
   * (`Commune/C7.txt`'s own `[Invit]`, "Laudémus Deum nostrum * In
   * confessióne beátæ N.." — genitive singular, e.g. "Marthæ") — every
   * other Common category in use has name-free invitatory text. Absent
   * for saints whose Common invitatory antiphon needs no substitution. */
  invitatoryName?: { la: string[]; en: string[] };
  /** See calendar/types.ts's OctaveConfig doc comment. Absent for the vast
   * majority of saints — only ones with a real octave declare it. */
  octave?: OctaveConfig;
  /** For a `rank: vigil` record only — the real id of the feast this is
   * the eve of (e.g. `st-andrew`, not derivable from this record's own id
   * by stripping a `vigil-of-` prefix: several vigils' ids don't match
   * their feast's id exactly — `vigil-of-the-assumption` vs `assumption`,
   * `vigil-of-st-james` vs `st-james-the-greater`). Lets
   * calendar/vespers.ts's evening-anticipation merge recognize "today's
   * Vigil is being displaced by the very feast it's the eve of" precisely
   * (rather than guessing from date adjacency alone) and skip
   * commemorating the Vigil redundantly alongside its own feast. */
  vigilOf?: string;
  /** This feast, when it keeps its own Second Vespers (see
   * calendar/vespers.ts's keepsOwnSecondVespers), refuses to commemorate
   * whatever wins the following day there — the feast-side mirror of a
   * privileged Sunday/major-feria refusing a weak commemoration *back*
   * (calendar/commemorations.ts), which vu had no equivalent of at all
   * until this field. Modeled as an explicit per-feast flag rather than
   * porting the reference engine's fractional-rank privilege matrix
   * (crank/rank thresholds, a "Festum Domini" distinction, an outright
   * hardcoded pair exception for Most Precious Blood vs. the Sacred
   * Heart on the rare late-Easter date they collide) — that matches this
   * repo's own "explicit per-feast fact, verified against the live
   * engine one date at a time" convention (same as `vigilOf` above)
   * rather than a general formula applied blanket and hoped correct.
   * Absent (the default) preserves today's status quo: any sanctoral
   * successor gets commemorated regardless of rank. Not yet set on any
   * saint — add only after checking a specific date against the live
   * reference engine, not speculatively for every `duplex-1-classis`
   * feast. NOT a fit for the Precious Blood/Sacred Heart case itself
   * (that exclusion is specific to one neighbor, not "never commemorate
   * anyone" — see this field's own doc comment above) — see TODO.md. */
  forbidsSuccessorCommemoration?: true;
}

const sanctoralCalendar = sanctoralCalendarData as { days: Record<string, string[]> };

const saintModules = import.meta.glob<{ default: SaintRecord }>('../data/calendar/saints/*.yml', {
  eager: true,
});

const saintsById = new Map<string, SaintRecord>();
for (const mod of Object.values(saintModules)) {
  saintsById.set(mod.default.id, mod.default);
}

export interface SanctoralCandidate {
  id: string;
  name: string;
  nameLa?: string;
  rank: FeastClass;
}

/** Which saint(s) (if any) are assigned to this date. `isoDate`'s year is ignored — the sanctoral cycle repeats every civil year. */
/** The saint's full record — used when something more than id/name/rank is
 * needed, e.g. hours/resolve-common.ts's getDayCollect looking up whether
 * a real collect has been authored for a winning saint. */
export function getSaintRecord(id: string): SaintRecord | undefined {
  return saintsById.get(id);
}

export function getSanctoralCandidatesFor(isoDate: string): SanctoralCandidate[] {
  const monthDay = isoDate.slice(5);
  const ids = sanctoralCalendar.days[monthDay] ?? [];
  return ids.map((id) => {
    const saint = saintsById.get(id);
    if (!saint) {
      throw new Error(`sanctoral-calendar.yml references unknown saint id '${id}'`);
    }
    return { id: saint.id, name: saint.name, nameLa: saint.nameLa, rank: saint.rank };
  });
}
