/**
 * Suggested names for a new programme, derived from its college.
 *
 * Picking "College of Nursing" fills in "Bachelor of Science in Nursing",
 * "BS Nursing" and "BSN" — because most of a programme's name is already
 * implied by the college it sits in, and typing it out again is work the form
 * can do.
 *
 * Everything here is derived from what the institution itself entered when it
 * created the college. There is no built-in catalogue of degree programmes:
 * inventing one would be putting an institution's academic offering into the
 * source code, which is the thing this system is built not to do (SPEC.md §0.7).
 * The consequence is that the suggestions are sometimes wrong — a College of
 * Medicine awards a Doctor of Medicine, not a Bachelor of Science in Medicine,
 * until the degree level is set to Professional. That is the right trade:
 * a wrong suggestion is one keystroke to correct, whereas a hard-coded
 * catalogue is wrong for every institution that is not the one it was written
 * for.
 */

/** Prefixes a college name carries that say nothing about its discipline. */
const COLLEGE_PREFIXES = [
  'college of the',
  'college of',
  'school of',
  'institute of',
  'faculty of',
  'department of',
  'academy of',
  'college',
  'school',
];

/** Words that are not initialised when a code is built from a name. */
const MINOR_WORDS = new Set(['of', 'and', 'the', 'in', 'for', '&']);

export type DegreeLevel = 'UNDERGRADUATE' | 'GRADUATE' | 'DOCTORAL' | 'PROFESSIONAL';

interface Naming {
  readonly full: (discipline: string) => string;
  readonly short: (discipline: string) => string;
  readonly codePrefix: string;
}

const NAMING: Record<DegreeLevel, Naming> = {
  UNDERGRADUATE: {
    full: (discipline) => `Bachelor of Science in ${discipline}`,
    short: (discipline) => `BS ${discipline}`,
    codePrefix: 'BS',
  },
  GRADUATE: {
    full: (discipline) => `Master of Science in ${discipline}`,
    short: (discipline) => `MS ${discipline}`,
    codePrefix: 'MS',
  },
  DOCTORAL: {
    full: (discipline) => `Doctor of Philosophy in ${discipline}`,
    short: (discipline) => `PhD ${discipline}`,
    codePrefix: 'PHD',
  },
  PROFESSIONAL: {
    full: (discipline) => `Doctor of ${discipline}`,
    short: (discipline) => `Doctor of ${discipline}`,
    codePrefix: 'D',
  },
};

/**
 * The subject a college teaches, with the institutional wrapper taken off.
 *
 * "College of Allied Health Sciences" is about Allied Health Sciences; the word
 * "College" is about the institution's own filing.
 */
export function disciplineOf(collegeName: string): string {
  let name = collegeName.trim().replace(/\s+/g, ' ');
  const lower = name.toLowerCase();

  for (const prefix of COLLEGE_PREFIXES) {
    if (lower.startsWith(`${prefix} `)) {
      name = name.slice(prefix.length + 1);
      break;
    }
  }

  // "Nursing College" as well as "College of Nursing".
  name = name.replace(/\s+(college|school|institute|faculty|department|academy)$/i, '');
  name = name.trim();

  /*
   * What is left may still be nothing but the wrapper — a college recorded as
   * "College", or "School". Treated as no discipline at all, because the
   * alternative is suggesting "Bachelor of Science in College".
   */
  if (/^(college|school|institute|faculty|department|academy)$/i.test(name)) {
    return '';
  }

  return name;
}

/**
 * An abbreviation built from the initials of the significant words.
 *
 * "Medical Laboratory Science" becomes MLS, so an undergraduate programme in it
 * is suggested as BSMLS — which is what the abbreviation would have been if a
 * person had written it.
 */
export function initialsOf(discipline: string): string {
  return discipline
    .split(/[\s-]+/)
    .filter((word) => word.length > 0 && !MINOR_WORDS.has(word.toLowerCase()))
    .map((word) => word[0]?.toUpperCase() ?? '')
    .join('');
}

export interface SuggestedProgram {
  code: string;
  shortName: string;
  name: string;
}

/**
 * What to put in the three name fields for a new programme in this college.
 *
 * Returns empty strings for a college with no usable name, so the caller can
 * leave the fields alone rather than filling them with nonsense.
 */
export function suggestProgram(
  collegeName: string,
  degreeLevel: DegreeLevel = 'UNDERGRADUATE',
): SuggestedProgram {
  const discipline = disciplineOf(collegeName);

  if (discipline.length === 0) {
    return { code: '', shortName: '', name: '' };
  }

  const naming = NAMING[degreeLevel];
  const initials = initialsOf(discipline);

  return {
    // The column allows 16 characters; a very long discipline is truncated
    // rather than rejected on submit.
    code: `${naming.codePrefix}${initials}`.slice(0, 16),
    shortName: naming.short(discipline),
    name: naming.full(discipline),
  };
}
