// Precedence/occurrence resolution: given a day's temporal-cycle standing
// (calendar/types.ts's TemporalCategory) and a candidate sanctoral feast (if
// any), which one is actually kept, what (if anything) is commemorated
// alongside it, and whether the candidate needs to be transferred to an
// adjacent day instead (see calendar/transfer.ts for what actually happens
// with that signal).
//
// Modeled as explicit rules per category, not a numeric-weight comparison
// like the reference engine's occurrence()/concurrence() — see
// data/calendar/temporal-categories.yml's header for why. Verified in part
// against real dates: the Sunday-vs-Duplex threshold was corrected after
// finding St. Anthony Abbot, plain Duplex, outright winning against an
// ordinary Sunday in the real Monastic 1617 engine, and privileged-feria
// was later split into two real tiers after finding St. Gregory the Great,
// also plain Duplex, outright winning against a Lenten Ember Saturday
// (which the original single-tier model wrongly forbade) while St. Mark,
// Duplex II. classis, only ever gets commemorated — never wins outright —
// within the Easter Octave. The rest is the result of a design discussion,
// not yet independently verified against a primary source.

import type { Commemoration, DayWinner, FeastClass, SanctoralIdentity, TemporalCategory } from './types';

const FEAST_CLASS_ORDER: FeastClass[] = [
  'simplex',
  'vigil',
  'semiduplex',
  'duplex',
  'duplex-majus',
  'duplex-2-classis',
  'duplex-1-classis',
];

export function compareFeastClass(a: FeastClass, b: FeastClass): number {
  return FEAST_CLASS_ORDER.indexOf(a) - FEAST_CLASS_ORDER.indexOf(b);
}

export function isAtLeast(rank: FeastClass, threshold: FeastClass): boolean {
  return compareFeastClass(rank, threshold) >= 0;
}

/** Vigils belong to the day *before* their feast, so an impeded vigil is
 * shifted backward rather than forward like everything else — see
 * calendar/transfer.ts. */
function transferDirectionOf(rank: FeastClass): 'forward' | 'backward' {
  return rank === 'vigil' ? 'backward' : 'forward';
}

export interface OccurrenceResult {
  /** This day's own resolution, with the candidate absent if it's being transferred. */
  winner: DayWinner;
  commemorations: Commemoration[];
  /** Set when `sanctoral` doesn't win or get commemorated here at all — it
   * needs to be resolved against an adjacent day instead. */
  transfer?: { candidate: SanctoralIdentity; direction: 'forward' | 'backward' };
}

function sanctoralCommemoration(candidate: SanctoralIdentity): Commemoration {
  return { kind: 'sanctoral', id: candidate.id, name: candidate.name, rank: candidate.rank };
}

function sanctoralWinner(candidate: SanctoralIdentity): DayWinner {
  return { kind: 'sanctoral', id: candidate.id, name: candidate.name, rank: candidate.rank };
}

/**
 * `temporalId` is this day's own temporal-propers id (see
 * calendar/temporal-id.ts) — needed so a temporal winner or commemoration
 * can actually be looked up later (`propers.getTemporalProper`), not just
 * named "temporal" in the abstract.
 */
export function decideOccurrence(
  temporal: TemporalCategory,
  temporalId: string,
  sanctoral: SanctoralIdentity | null,
): OccurrenceResult {
  const temporalWinner: DayWinner = { kind: 'temporal', id: temporalId };
  if (!sanctoral) {
    return { winner: temporalWinner, commemorations: [] };
  }

  switch (temporal) {
    case 'ordinary-feria':
      // An ordinary feria has no standing of its own to defend — any real
      // feast, however low-ranked, is kept in its place.
      return { winner: sanctoralWinner(sanctoral), commemorations: [] };

    case 'privileged-feria-minor':
      // Advent's own ordinary ferias (verified: St. Bibiana, Simplex,
      // merely commemorated under an Advent feria; St. Nicholas,
      // Semiduplex, wins outright with the feria commemorated in return).
      // Weaker than `privileged-feria`'s Duplex+ threshold — Semiduplex is
      // already enough here — and unlike every other feria tier, nothing
      // ever transfers: whichever side loses still gets a commemoration.
      if (isAtLeast(sanctoral.rank, 'semiduplex')) {
        return { winner: sanctoralWinner(sanctoral), commemorations: [{ kind: 'temporal', id: temporalId }] };
      }
      return { winner: temporalWinner, commemorations: [sanctoralCommemoration(sanctoral)] };

    case 'privileged-feria':
      // The lesser of the two privileged-feria tiers (Ember days outside
      // Holy Week/the Easter Octave, etc.) — behaves exactly like an
      // ordinary Sunday: real standing of its own, but not enough to
      // resist a sufficiently ranked feast the way privileged-feria-major
      // does.
      if (isAtLeast(sanctoral.rank, 'duplex')) {
        return { winner: sanctoralWinner(sanctoral), commemorations: [{ kind: 'temporal', id: temporalId }] };
      }
      if (sanctoral.rank === 'simplex') {
        return { winner: temporalWinner, commemorations: [sanctoralCommemoration(sanctoral)] };
      }
      return {
        winner: temporalWinner,
        commemorations: [],
        transfer: { candidate: sanctoral, direction: transferDirectionOf(sanctoral.rank) },
      };

    case 'privileged-feria-major':
      // Ash Wednesday, Holy Week, the Easter Octave, the Vigil of
      // Christmas — never displaced, same as privileged-sunday.
      if (isAtLeast(sanctoral.rank, 'duplex-majus')) {
        return { winner: temporalWinner, commemorations: [sanctoralCommemoration(sanctoral)] };
      }
      return {
        winner: temporalWinner,
        commemorations: [],
        transfer: { candidate: sanctoral, direction: transferDirectionOf(sanctoral.rank) },
      };

    case 'ordinary-sunday':
      if (isAtLeast(sanctoral.rank, 'duplex')) {
        // Duplex+ wins outright; the Sunday itself is commemorated in return.
        return { winner: sanctoralWinner(sanctoral), commemorations: [{ kind: 'temporal', id: temporalId }] };
      }
      if (sanctoral.rank === 'simplex') {
        // Too minor to warrant its own day, but a plain commemoration
        // doesn't cheapen it the way it would a Semiduplex.
        return { winner: temporalWinner, commemorations: [sanctoralCommemoration(sanctoral)] };
      }
      // Semiduplex or Vigil: no room here at all, in either direction —
      // better to preserve the feast whole on another day than downgrade
      // it to a bare commemoration.
      return {
        winner: temporalWinner,
        commemorations: [],
        transfer: { candidate: sanctoral, direction: transferDirectionOf(sanctoral.rank) },
      };

    case 'privileged-sunday':
      if (isAtLeast(sanctoral.rank, 'duplex-majus')) {
        // Never displaced, but a sufficiently high feast still gets a nod.
        return { winner: temporalWinner, commemorations: [sanctoralCommemoration(sanctoral)] };
      }
      // Below duplex-majus, a privileged Sunday wants nothing at all —
      // not even the bare commemoration an ordinary Sunday would allow a
      // Simplex — so everything here transfers.
      return {
        winner: temporalWinner,
        commemorations: [],
        transfer: { candidate: sanctoral, direction: transferDirectionOf(sanctoral.rank) },
      };
  }
}
