/**
 * The Certificate of Registration snapshot (SPEC.md §16, §30).
 *
 * This shape is the *document format*, and it lives here — in the domain layer,
 * framework-free — because two places write it: the application when the
 * Registrar issues a certificate, and the seed when it fabricates demonstration
 * records. Two hand-written copies of a document format drift, and the drift
 * only surfaces when someone opens an old certificate and the page breaks.
 * One builder, one shape, both callers.
 *
 * A snapshot is deliberately self-contained: everything needed to reprint the
 * certificate exactly as issued, with no lookups. A certificate is a statement
 * about a moment, and reprinting it must not consult records that have since
 * moved on.
 */

export const COR_WATERMARK = 'UNOFFICIAL — PROTOTYPE';

export interface CorSubject {
  readonly code: string;
  readonly title: string;
  readonly section: string;
  readonly units: number;
  /** Pre-rendered, e.g. `M 07:30-09:00 NUR-201, W 07:30-09:00 NUR-201`. */
  readonly schedule: string;
}

export interface CorSnapshot {
  readonly version: 1;
  readonly student: {
    readonly studentNumber: string;
    readonly fullName: string;
    readonly program: string;
    readonly yearLevel: number;
  };
  readonly term: { readonly name: string; readonly academicYear: string };
  readonly subjects: readonly CorSubject[];
  readonly totals: { readonly units: number; readonly subjects: number };
  /** Decimal string in the institution's currency, or null if unassessed. */
  readonly assessedNet: string | null;
  readonly currency: string;
  readonly issuedAt: string;
  readonly watermark: string | null;
}

export interface CorSnapshotInput {
  readonly studentNumber: string;
  readonly fullName: string;
  readonly program: string;
  readonly yearLevel: number;
  readonly termName: string;
  readonly academicYear: string;
  readonly subjects: readonly CorSubject[];
  readonly assessedNet: string | null;
  readonly currency: string;
  readonly issuedAt: Date;
  /** False for the prototype; the watermark is omitted only once true. */
  readonly isOfficial: boolean;
}

/** Builds the snapshot. Pure: same input, same document, every time. */
export function buildCorSnapshot(input: CorSnapshotInput): CorSnapshot {
  return {
    version: 1,
    student: {
      studentNumber: input.studentNumber,
      fullName: input.fullName,
      program: input.program,
      yearLevel: input.yearLevel,
    },
    term: { name: input.termName, academicYear: input.academicYear },
    subjects: input.subjects,
    totals: {
      units: input.subjects.reduce((sum, subject) => sum + subject.units, 0),
      subjects: input.subjects.length,
    },
    assessedNet: input.assessedNet,
    currency: input.currency,
    issuedAt: input.issuedAt.toISOString(),
    watermark: input.isOfficial ? null : COR_WATERMARK,
  };
}

const DAY_ABBREVIATION: Record<string, string> = {
  MONDAY: 'M',
  TUESDAY: 'T',
  WEDNESDAY: 'W',
  THURSDAY: 'Th',
  FRIDAY: 'F',
  SATURDAY: 'S',
  SUNDAY: 'Su',
};

export interface CorMeeting {
  readonly dayOfWeek: string;
  readonly startMinutes: number;
  readonly endMinutes: number;
  readonly room?: string | null;
}

/**
 * Renders meeting times into the single line the certificate prints.
 *
 * A subject with no scheduled meetings reads "To be announced" rather than
 * showing an empty cell, which on a printed certificate is indistinguishable
 * from a printing fault.
 */
export function formatCorSchedule(meetings: readonly CorMeeting[]): string {
  if (meetings.length === 0) return 'To be announced';

  return meetings
    .map((meeting) => {
      const day = DAY_ABBREVIATION[meeting.dayOfWeek] ?? meeting.dayOfWeek;
      const start = formatClock(meeting.startMinutes);
      const end = formatClock(meeting.endMinutes);
      return `${day} ${start}-${end}${meeting.room ? ` ${meeting.room}` : ''}`;
    })
    .join(', ');
}

function formatClock(minutes: number): string {
  const hours = Math.floor(minutes / 60);
  const rest = minutes % 60;
  return `${String(hours).padStart(2, '0')}:${String(rest).padStart(2, '0')}`;
}

/**
 * Reads a stored snapshot, accepting older records.
 *
 * Certificates issued before the format was versioned carry a flat shape. They
 * are real documents that people may still open, so they are migrated on read
 * rather than being allowed to crash the page — losing a record because its
 * format is old is not an acceptable outcome for an archival document.
 */
export function readCorSnapshot(stored: unknown): CorSnapshot | null {
  if (typeof stored !== 'object' || stored === null) return null;
  const value = stored as Record<string, unknown>;

  if (value.version === 1) return value as unknown as CorSnapshot;

  // The pre-versioning flat shape.
  if (typeof value.studentNumber === 'string') {
    const subjects = Array.isArray(value.subjects)
      ? (value.subjects as Record<string, unknown>[]).map((subject) => ({
          code: String(subject.code ?? ''),
          title: String(subject.title ?? ''),
          section: String(subject.section ?? ''),
          units: Number(subject.units ?? 0),
          schedule: typeof subject.schedule === 'string' ? subject.schedule : 'To be announced',
        }))
      : [];

    return {
      version: 1,
      student: {
        studentNumber: String(value.studentNumber),
        fullName: String(value.studentName ?? ''),
        program: String(value.program ?? ''),
        yearLevel: Number(value.yearLevel ?? 0),
      },
      term: {
        name: String(value.term ?? ''),
        academicYear: String(value.academicYear ?? ''),
      },
      subjects,
      totals: {
        units: Number(value.totalUnits ?? subjects.reduce((sum, s) => sum + s.units, 0)),
        subjects: subjects.length,
      },
      assessedNet: typeof value.assessedNet === 'string' ? value.assessedNet : null,
      currency: typeof value.currency === 'string' ? value.currency : 'PHP',
      issuedAt: typeof value.issuedAt === 'string' ? value.issuedAt : new Date(0).toISOString(),
      watermark: typeof value.watermark === 'string' ? value.watermark : COR_WATERMARK,
    };
  }

  return null;
}
