import { ContestType, type ContestPrefix, type ContestLabelTranslations } from '$lib/types/contest';

export const regexForJag = /^JAG(Prelim|Regional|Summer|Winter|Spring)\d{4}(-day\d+)?[A-Z]?$/;
export const regexForAojUniversity = /^AOJ-[A-Z]+PC\d{4}/;

// See:
// https://github.com/kenkoooo/AtCoderProblems/blob/master/atcoder-problems-frontend/src/utils/ContestClassifier.ts
export const classifyContest = (contest_id: string) => {
  // AtCoder
  if (/^abc\d{3}$/.exec(contest_id)) {
    return ContestType.ABC;
  }

  if (/^arc\d{3}$/.exec(contest_id)) {
    return ContestType.ARC;
  }

  if (/^agc\d{3}$/.exec(contest_id)) {
    return ContestType.AGC;
  }

  if (/^awc\d{4}$/.exec(contest_id)) {
    return ContestType.AWC;
  }

  if (contest_id.startsWith('APG4b')) {
    return ContestType.APG4B;
  }

  if (contest_id === 'abs') {
    return ContestType.ABS;
  }

  if (contest_id === 'typical90') {
    return ContestType.TYPICAL90;
  }

  if (contest_id === 'dp') {
    return ContestType.EDPC;
  }

  if (contest_id === 'tdpc') {
    return ContestType.TDPC;
  }

  if (contest_id === 'ndpc') {
    return ContestType.NDPC;
  }

  if (contest_id.startsWith('past')) {
    return ContestType.PAST;
  }

  if (contest_id === 'practice2') {
    return ContestType.ACL_PRACTICE;
  }

  if (contest_id.startsWith('joi')) {
    return ContestType.JOI;
  }

  if (contest_id === 'tessoku-book') {
    return ContestType.TESSOKU_BOOK;
  }

  if (contest_id === 'math-and-algorithm') {
    return ContestType.MATH_AND_ALGORITHM;
  }

  if (contest_id === 'fps-24') {
    return ContestType.FPS_24;
  }

  if (isWorldTourFinals(contest_id)) {
    return ContestType.ATCODER_MAIN_OFFICIAL_ONSITE;
  }

  if (abcLikePrefixes.has(contest_id)) {
    return ContestType.ABC_LIKE;
  }

  if (arcLikePrefixes.has(contest_id)) {
    return ContestType.ARC_LIKE;
  }

  if (agcLikePrefixes.some((prefix) => contest_id.startsWith(prefix))) {
    return ContestType.AGC_LIKE;
  }

  if (atCoderUniversityPrefixes.some((prefix) => contest_id.startsWith(prefix))) {
    return ContestType.UNIVERSITY;
  }

  if (atCoderOthersPrefixes.some((prefix) => contest_id.startsWith(prefix))) {
    return ContestType.OTHERS;
  }

  // AIZU ONLINE JUDGE
  if (aojCoursePrefixes.has(contest_id)) {
    return ContestType.AOJ_COURSES;
  }

  if (/^PCK(Prelim|Final)\d*$/.exec(contest_id)) {
    return ContestType.AOJ_PCK;
  }

  if (/^ICPC(Prelim|Regional)\d*$/.exec(contest_id)) {
    return ContestType.AOJ_ICPC;
  }

  if (regexForJag.exec(contest_id)) {
    return ContestType.AOJ_JAG;
  }

  if (regexForAojUniversity.test(contest_id)) {
    return ContestType.AOJ_UNIVERSITY;
  }

  return null;
};

// HACK: As of December 2025, the following contests are applicable.
// Note: The classification logic may need to be revised when new contests are added.
const ABC_LIKE: ContestPrefix = {
  'tenka1-2017-beginner': 'Tenka1 Programmer Beginner Contest 2017',
  abl: 'ACL Beginner Contest',
  caddi2018b: 'CADDi 2018 for Beginners',
  'soundhound2018-summer-qual': 'SoundHound Inc. Programming Contest 2018 -Masters Tournament-',
  'tenka1-2018-beginner': 'Tenka1 Programmer Beginner Contest 2018',
  aising2019: 'エイシング プログラミング コンテスト 2019',
  sumitrust2019: '三井住友信託銀行プログラミングコンテスト2019',
  'tenka1-2019-beginner': 'Tenka1 Programmer Beginner Contest 2019',
  aising2020: 'エイシング プログラミング コンテスト 2020',
  hhkb2020: 'HHKB プログラミングコンテスト 2020',
  'm-solutions2020': 'M-SOLUTIONS プロコンオープン 2020',
  panasonic2020: 'パナソニックプログラミングコンテスト 2020',
  jsc2021: '第二回日本最強プログラマー学生選手権',
  zone2021: 'ZONeエナジー プログラミングコンテスト “HELLO SPACE”',
  'jsc2025advance-final': '日本最強プログラマー学生選手権～Advance～',
} as const;
const abcLikePrefixes = new Set(getContestPrefixes(ABC_LIKE));

const ARC_LIKE: ContestPrefix = {
  'tenka1-2017': 'Tenka1 Programmer Contest 2017',
  'tenka1-2018': 'Tenka1 Programmer Contest 2018',
  'tenka1-2019': 'Tenka1 Programmer Contest 2019',
  caddi2018: 'CADDi 2018',
  'dwacon5th-prelims': '第5回 ドワンゴからの挑戦状 予選',
  'dwacon6th-prelims': '第6回 ドワンゴからの挑戦状 予選',
  diverta2019: 'diverta 2019 Programming Contest',
  keyence2019: 'キーエンス プログラミング コンテスト 2019',
  keyence2020: 'キーエンス プログラミング コンテスト 2020',
  keyence2021: 'キーエンス プログラミング コンテスト 2021',
  'jsc2019-qual': '第一回日本最強プログラマー学生選手権-予選-',
  'nikkei2019-qual': '全国統一プログラミング王決定戦予選',
  acl1: 'ACL Contest 1',
} as const;
const arcLikePrefixes = new Set(getContestPrefixes(ARC_LIKE));

const AGC_LIKE: ContestPrefix = {
  'code-festival-2016-qual': 'CODE FESTIVAL 2016 qual',
  'code-festival-2017-qual': 'CODE FESTIVAL 2017 qual',
  'cf16-final': 'CODE FESTIVAL 2016 final',
  'cf17-final': 'CODE FESTIVAL 2017 final',
} as const;
const agcLikePrefixes = getContestPrefixes(AGC_LIKE);

// HACK: As of September 2025, KUPC, QUPC, UTPC, TTPC and TUPC are included.
// More university contests may be added in the future.
/**
 * Maps university contest ID prefixes to their display names.
 *
 * @example
 * {
 *   kupc: 'KUPC' // Kyoto University Programming Contest
 *   qupc: 'QUPC' // Kyushu University Programming Contest
 *   utpc: 'UTPC' // University of Tokyo Programming Contest
 *   ttpc: 'TTPC' // Tokyo Institute of Technology Programming Contest
 *   tupc: 'TUPC' // Tohoku University Programming Contest
 *   wupc: 'WUPC' // Waseda University Programming Contest
 * }
 *
 * @remarks
 * When adding new university contests:
 * 1. Use lowercase prefix as key
 * 2. Use official contest name as value
 * 3. Ensure prefix doesn't conflict with existing contest types
 */
const ATCODER_UNIVERSITIES: ContestPrefix = {
  kupc: 'KUPC',
  qupc: 'QUPC',
  utpc: 'UTPC',
  ttpc: 'TTPC',
  tupc: 'TUPC',
  wupc: 'WUPC',
} as const;

const atCoderUniversityPrefixes = getContestPrefixes(ATCODER_UNIVERSITIES);

// World Tour Finals (AtCoder official onsite finals), Algorithm division only.
//
// Seeded contest_id values carry a trailing "-open" (e.g. wtf19-open), except
// wtf22-day2 which AtCoder Problems records without it; strip it before matching
// so both forms work and it never leaks into the display label.
//
// From 2025 the id gains an "algo" infix (awtf2025algo-open) to disambiguate
// from the new Heuristic division (awtf2025heuristic), which stays out of scope.
const regexForWorldTourFinals = /^(wtf19|wtf22-day[12]|awtf2024|awtf20\d{2}algo)$/;

export const isWorldTourFinals = (contestId: string): boolean =>
  regexForWorldTourFinals.test(stripOpenSuffix(contestId));

export const getWorldTourFinalsLabel = (contestId: string): string => {
  const base = 'World Tour Finals';
  const id = stripOpenSuffix(contestId);

  if (id === 'wtf19') {
    return `${base} 2019`;
  }

  const dayMatch = /^wtf22-day([12])$/.exec(id);

  if (dayMatch) {
    return `${base} 2022 Day${dayMatch[1]}`;
  }

  if (id === 'awtf2024') {
    return `${base} 2024`;
  }

  const algoMatch = /^awtf(20\d{2})algo$/.exec(id);

  if (algoMatch) {
    // "Algorithm" distinguishes from the Heuristic division, introduced in 2025.
    return `${base} ${algoMatch[1]} Algorithm`;
  }

  return contestId.toUpperCase();
};

const stripOpenSuffix = (contestId: string): string =>
  contestId.endsWith('-open') ? contestId.slice(0, -'-open'.length) : contestId;

/**
 * Maps other AtCoder contest ID prefixes to their display names.
 * Includes special, corporate, and promotional contests that don't fit other categories.
 *
 * @example
 * {
 *   'mujin-pc-2018': 'Mujin Programming Challenge 2018',
 *   'discovery2016': 'DISCO presents ディスカバリーチャンネル プログラミングコンテスト2016'
 * }
 *
 * @remarks
 * When adding new contests:
 * 1. Use kebab-case for contest ID prefix as key
 * 2. Use official contest name in English or Japanese as value
 * 3. Ensure the contest doesn't belong to other specific categories
 */
const ATCODER_OTHERS: ContestPrefix = {
  chokudai_S: 'Chokudai SpeedRun',
  atc001: 'AtCoder Typical Contest 001',
  geocon2013: '幾何コンテスト2013',
  's8pc-3': 'square869120Contest #3',
  's8pc-4': 'square869120Contest #4',
  'maximum-cup-2013': 'Maximum-Cup 2013',
  'maximum-cup-2018': 'Maximum-Cup 2018',
  'code-festival-2014-quala': 'Code Festival 2014 予選 A',
  'code-festival-2014-qualb': 'Code Festival 2014 予選 B',
  'code-festival-2014-final': 'Code Festival 2014 決勝',
  'code-festival-2014-china-open': 'Code Festival 2014 上海',
  'code-festival-2015-qualb': 'Code Festival 2015 予選 B',
  'code-festival-2015-morning-middle': 'CODE FESTIVAL 2015 あさぷろ Middle',
  'code-festival-2015-exhibition': 'CODE FESTIVAL 2015 エキシビション',
  'code-thanks-festival': 'CODE THANKS FESTIVAL',
  donuts: 'Donutsプロコンチャレンジ',
  indeednow: 'Indeedなう',
  'dwango2016-prelims': '第2回 ドワンゴからの挑戦状 予選',
  'dwacon2017-prelims': '第3回 ドワンゴからの挑戦状 予選',
  'mujin-pc-2016': 'Mujin Programming Challenge 2016',
  'mujin-pc-2018': 'Mujin Programming Challenge 2018',
  soundhound2018: 'SoundHound Inc. Programming Contest 2018 (春)',
  'pakencamp-2018-day3': 'パ研合宿コンペティション 3日目',
  'pakencamp-2024-day1': 'パ研合宿2024 第1日「SpeedRun」',
  'tenka1-2012-qualB': '天下一プログラマーコンテスト2012予選B',
  'tenka1-2015-quala': '天下一プログラマーコンテスト2015予選A',
  'tenka1-2015-qualb': '天下一プログラマーコンテスト2015予選B',
  'tenka1-2016-final': '天下一プログラマーコンテスト2016本戦',
  // Discovery Channel contest featuring algorithm problems
  discovery2016: 'DISCO presents ディスカバリーチャンネル プログラミングコンテスト2016',
  colopl: 'COLOCON',
  gigacode: 'GigaCode',
  cpsco2019: 'CPSCO 2019',
  'iroha2019-day4': 'いろはちゃんコンテスト Day4',
  'nikkei2019-final': '全国統一プログラミング王決定戦本戦',
  'jsc2019-final': '第一回日本最強プログラマー学生選手権決勝',
  'jsc2025-final': '第六回日本最強プログラマー学生選手権 -決勝-',
  DEGwer2023: 'DEGwer さんの D 論応援コンテスト',
  xmascon19: 'Xmas Contest 2019',
} as const;
const atCoderOthersPrefixes = getContestPrefixes(ATCODER_OTHERS);

// AIZU ONLINE JUDGE AOJ Courses
export const AOJ_COURSES: ContestPrefix = {
  ITP1: 'プログラミング入門',
  ALDS1: 'アルゴリズムとデータ構造入門',
  ITP2: 'プログラミング応用',
  DPL: '組み合わせ最適化',
  GRL: 'グラフ',
  DSL: 'データ構造',
  CGL: '計算幾何学',
  NTL: '整数論',
} as const;

export function getPrefixForAojCourses() {
  return getContestPrefixes(AOJ_COURSES);
}

const aojCoursePrefixes = new Set(getPrefixForAojCourses()); // For O(1) lookups

/**
 * Extracts contest prefixes (keys) from a contest prefix object.
 * @param contestPrefixes - Object mapping contest IDs to their display names
 * @returns Array of contest prefix strings
 */
export function getContestPrefixes(contestPrefixes: Record<string, string>) {
  return Object.keys(contestPrefixes);
}

/**
 * Contest type priorities (0 = Highest, 26 = Lowest)
 *
 * Priority assignment rationale:
 * - Educational contests (0-11, 17): ABS, ABC, APG4B and AWC etc.
 * - Contests for genius (12-16): ARC, AGC, and their variants
 * - Special contests (18-21): UNIVERSITY, FPS_24, ATCODER_MAIN_OFFICIAL_ONSITE, OTHERS
 * - External platforms (22-26): AOJ_COURSES, AOJ_PCK, AOJ_ICPC, AOJ_JAG, AOJ_UNIVERSITY
 *
 * @remarks
 * HACK: The priorities for ARC, AGC, UNIVERSITY, AOJ_COURSES, and AOJ_PCK are temporary
 * and may be adjusted based on future requirements.
 *
 * See:
 * https://jsprimer.net/basic/map-and-set/
 */
export const contestTypePriorities: Map<ContestType, number> = new Map([
  [ContestType.ABS, 0],
  [ContestType.ABC, 1],
  [ContestType.APG4B, 2],
  [ContestType.TYPICAL90, 3],
  [ContestType.EDPC, 4],
  [ContestType.TDPC, 5],
  [ContestType.NDPC, 6],
  [ContestType.PAST, 7],
  [ContestType.ACL_PRACTICE, 8],
  [ContestType.JOI, 9],
  [ContestType.TESSOKU_BOOK, 10],
  [ContestType.MATH_AND_ALGORITHM, 11],
  [ContestType.ARC, 12],
  [ContestType.AGC, 13],
  [ContestType.ABC_LIKE, 14],
  [ContestType.ARC_LIKE, 15],
  [ContestType.AGC_LIKE, 16],
  [ContestType.AWC, 17],
  [ContestType.UNIVERSITY, 18],
  [ContestType.FPS_24, 19],
  [ContestType.ATCODER_MAIN_OFFICIAL_ONSITE, 20],
  [ContestType.OTHERS, 21], // AtCoder (その他)
  [ContestType.AOJ_COURSES, 22],
  [ContestType.AOJ_PCK, 23],
  [ContestType.AOJ_ICPC, 24],
  [ContestType.AOJ_JAG, 25],
  [ContestType.AOJ_UNIVERSITY, 26],
]);

export function getContestPriority(contestId: string): number {
  const contestType = classifyContest(contestId);
  const INF: number = 10 ** 3;

  if (contestType === null || contestType === undefined) {
    return INF;
  } else {
    return contestTypePriorities.get(contestType) as number;
  }
}

/**
 * Regular expression to match contest codes.
 *
 * This regex matches strings that start with one of the following prefixes:
 * - "abc"
 * - "arc"
 * - "agc"
 * - "awc"
 *
 * followed by exactly three or four digits. The matching is case-insensitive.
 *
 * Example matches:
 * - "abc376"
 * - "ARC128"
 * - "agc045"
 * - "atc001"
 * - "awc0001"
 *
 * Example non-matches:
 * - "xyz123"
 * - "abc12"
 * - "abc1234"
 * - "atc1234"
 * - "awc12345"
 */
const regexForAxc = /^(abc|arc|agc|atc)(\d{3})$/i;
const regexForAwc = /^(awc)(\d{4})$/i;

/**
 * Regular expression to match AtCoder University contest identifiers.
 *
 * The pattern matches strings that:
 * - Start with either "ku", "qu", "ut", "tt","tu", or "wu"
 * - Followed by "pc"
 * - End with exactly year (four digits)
 *
 * Example matches:
 * - "kupc2024"
 * - "qupc2018"
 * - "utpc2014"
 * - "ttpc2022"
 * - "tupc2023"
 * - "wupc2019"
 */
const regexForAtCoderUniversity = /^(ku|qu|ut|tt|tu|wu)(pc)(\d{4})$/i;

export const getContestNameLabel = (contestId: string) => {
  // AtCoder
  if (regexForAxc.exec(contestId)) {
    return contestId.replace(
      regexForAxc,
      (_, contestType, contestNumber) => `${contestType.toUpperCase()} ${contestNumber}`,
    );
  }

  if (regexForAwc.exec(contestId)) {
    return contestId.replace(
      regexForAwc,
      (_, contestType, contestNumber) => `${contestType.toUpperCase()} ${contestNumber}`,
    );
  }

  if (contestId === 'APG4b' || contestId === 'APG4bPython') {
    return contestId;
  }

  if (contestId === 'typical90') {
    return '競プロ典型 90 問';
  }

  if (contestId === 'dp') {
    return 'EDPC';
  }

  if (contestId === 'tdpc') {
    return 'TDPC';
  }

  if (contestId === 'ndpc') {
    return 'NDPC';
  }

  if (contestId.startsWith('past')) {
    return getPastContestLabel(PAST_TRANSLATIONS, contestId);
  }

  if (contestId === 'practice2') {
    return 'ACL Practice';
  }

  if (contestId.startsWith('joi')) {
    return getJoiContestLabel(contestId);
  }

  if (contestId === 'tessoku-book') {
    return '競技プログラミングの鉄則';
  }

  if (contestId === 'math-and-algorithm') {
    return 'アルゴリズムと数学';
  }

  if (contestId === 'fps-24') {
    return 'FPS 24 題';
  }

  if (isWorldTourFinals(contestId)) {
    return getWorldTourFinalsLabel(contestId);
  }

  if (regexForAtCoderUniversity.exec(contestId)) {
    return getAtCoderUniversityContestLabel(contestId);
  }

  const othersLabel = ATCODER_OTHERS[contestId as keyof typeof ATCODER_OTHERS];

  if (othersLabel) {
    return othersLabel;
  }

  if (contestId.startsWith('chokudai_S')) {
    return contestId.replace('chokudai_S', 'Chokudai SpeedRun ');
  }

  // AIZU ONLINE JUDGE
  if (aojCoursePrefixes.has(contestId)) {
    return getAojContestLabel(AOJ_COURSES, contestId);
  }

  if (contestId.startsWith('PCK')) {
    return getAojContestLabel(PCK_TRANSLATIONS, contestId);
  }

  if (contestId.startsWith('ICPC')) {
    return getAojContestLabel(ICPC_TRANSLATIONS, contestId);
  }

  if (regexForJag.exec(contestId)) {
    return getAojContestLabel(JAG_TRANSLATIONS, contestId);
  }

  if (regexForAojUniversity.test(contestId)) {
    return getAojUniversityContestLabel(contestId);
  }

  return contestId.toUpperCase();
};

/**
 * A mapping of contest dates to their respective Japanese translations.
 * Each key represents a date in the format 'YYYYMM', and the corresponding value
 * is the Japanese translation indicating the contest number.
 *
 * Note:
 * After the 15th contest, the URL includes the number of times the contest has been held
 *
 * See:
 * https://atcoder.jp/contests/archive?ratedType=0&category=50
 *
 * Example:
 * - '201912': ' 第 1 回' (The 1st contest in December 2019)
 * - '202303': ' 第 14 回' (The 14th contest in March 2023)
 */
export const PAST_TRANSLATIONS = {
  '201912': ' 第 1 回',
  '202004': ' 第 2 回',
  '202005': ' 第 3 回',
  '202010': ' 第 4 回',
  '202012': ' 第 5 回',
  '202104': ' 第 6 回',
  '202107': ' 第 7 回',
  '202109': ' 第 8 回',
  '202112': ' 第 9 回',
  '202203': ' 第 10 回',
  '202206': ' 第 11 回',
  '202209': ' 第 12 回',
  '202212': ' 第 13 回',
  '202303': ' 第 14 回',
};

/**
 * A regular expression to match strings that representing the 15th or later PAST contests.
 * The string should start with "past" followed by exactly two digits and end with "-open".
 * The matching is case-insensitive.
 *
 * Examples:
 * - "past15-open" (matches)
 * - "past16-open" (matches)
 * - "past99-open" (matches)
 */
const regexForPast = /^past(\d+)-open$/i;

export function getPastContestLabel(
  translations: Readonly<ContestLabelTranslations>,
  contestId: string,
): string {
  let label = contestId;

  Object.entries(translations).forEach(([abbrEnglish, japanese]) => {
    label = label.replace(abbrEnglish, japanese);
  });

  if (label == contestId) {
    label = label.replace(regexForPast, (_, round) => {
      return `PAST 第 ${round} 回`;
    });
  }

  // Remove suffix
  return label.replace('-open', '').toUpperCase();
}

/**
 * Regular expression to match specific patterns in contest identifiers.
 *
 * The pattern matches strings that follow these rules:
 * - Starts with "joi" (case insensitive).
 * - Optionally followed by "g" or "open".
 * - Optionally represents year (4-digit number).
 * - Optionally followed by "yo", "ho", "sc", or "sp" (Qual, Final and Spring camp).
 * - Optionally represents year (4-digit number).
 * - Optionally followed by "1" or "2" (Qual 1st, 2nd).
 * - Optionally followed by "a", "b", or "c" (Round 1, 2 and 3).
 *
 * Flags:
 * - `i`: Case insensitive matching.
 *
 * Examples:
 * - "joi2024yo1a" (matches)
 * - "joi2023ho" (matches)
 * - "joisc2022" (matches)
 * - "joisp2021" (matches)
 * - "joig2024-open" (matches)
 * - "joisc2024" (matches)
 * - "joisp2022" (matches)
 * - "joi24yo3d" (does not match)
 * - "joi2026sf" (matches)
 */
const regexForJoi = /^(joi)(g|open)*(\d{4})*(yo|ho|sc|sp|sf)*(\d{4})*(1|2)*(a|b|c)*/i;

/**
 * Transforms a contest ID into a formatted contest label.
 *
 * This function processes the given contest ID by removing specific suffixes
 * and applying various transformations to generate a human-readable contest label.
 *
 * @param contestId - The ID of the contest to be transformed.
 * @returns The formatted contest label.
 */
export function getJoiContestLabel(contestId: string): string {
  let label = contestId;
  // Remove suffix
  label = label.replace('-open', '');

  label = label.replace(
    regexForJoi,
    (_, base, subType, yearPrefix, division, yearSuffix, qual, qualRound) => {
      const SPACE = ' ';

      let newLabel = base.toUpperCase();
      newLabel += addJoiSubTypeIfNeeds(subType);

      if (division !== undefined) {
        newLabel += SPACE;
        newLabel += addJoiDivisionNameIfNeeds(division, qual);
      }

      newLabel += SPACE;
      newLabel += addJoiYear(yearSuffix, yearPrefix);

      if (qualRound !== undefined) {
        newLabel += SPACE;
        newLabel += addJoiQualRoundNameIfNeeds(qualRound);
      }

      return newLabel;
    },
  );

  return label;
}

function addJoiSubTypeIfNeeds(subType: string): string {
  if (subType === 'g') {
    return subType.toUpperCase();
  } else if (subType === 'open') {
    return ' Open';
  }

  return '';
}

function addJoiDivisionNameIfNeeds(division: string, qual: string): string {
  if (division === 'yo') {
    if (qual === undefined) {
      return '予選';
    } else if (qual === '1') {
      return '一次予選';
    } else if (qual === '2') {
      return '二次予選';
    }
  } else if (division === 'ho') {
    return '本選';
  } else if (division === 'sf') {
    return 'セミファイナルステージ';
  } else if (division === 'sc' || division === 'sp') {
    return '春合宿';
  }

  return '';
}

function addJoiYear(yearSuffix: string, yearPrefix: string): string {
  if (yearPrefix !== undefined) {
    return yearPrefix;
  } else if (yearSuffix !== undefined) {
    return yearSuffix;
  }

  return '';
}

function addJoiQualRoundNameIfNeeds(qualRound: string): string {
  if (qualRound === 'a') {
    return '第 1 回';
  } else if (qualRound === 'b') {
    return '第 2 回';
  } else if (qualRound === 'c') {
    return '第 3 回';
  }

  return '';
}

/**
 * Generates a formatted contest label for AtCoder University contests.
 *
 * This function takes a contest ID string and replaces parts of it using a regular expression
 * to generate a formatted label. The label is constructed by converting the contest type and
 * common part to uppercase and appending the contest year.
 *
 * @param contestId - The ID of the contest to format (ex: utpc2023).
 * @returns The formatted contest label (ex: UTPC 2023).
 */
export function getAtCoderUniversityContestLabel(contestId: string): string {
  if (!regexForAtCoderUniversity.test(contestId)) {
    throw new Error(`Invalid university contest ID format: ${contestId}`);
  }

  return contestId.replace(
    regexForAtCoderUniversity,
    (_, contestType, common, contestYear) =>
      `${(contestType + common).toUpperCase()} ${contestYear}`,
  );
}

/**
 * Maps PCK contest type abbreviations to their Japanese translations.
 *
 * @example
 * {
 *   PCK: 'パソコン甲子園',
 *   Prelim: '予選',
 *   Final: '本選'
 * }
 */
const PCK_TRANSLATIONS = {
  PCK: 'パソコン甲子園',
  Prelim: ' 予選 ',
  Final: ' 本選 ',
};

function getAojUniversityContestLabel(contestId: string): string {
  const label = contestId
    .replace(/^AOJ-/, '')
    .replace(/UAPC/g, 'ACPC')
    .replace(/([A-Z]{2,})(\d{4})/g, '$1 $2')
    .replace(/-in-/, ' in ')
    .replace(/-day(\d+)/, ' Day$1')
    .replace(/-summer/, ' Summer');
  return '（' + label + '）';
}

/**
 * Maps JAG contest type abbreviations to their Japanese translations.
 *
 * @example
 * {
 *   Prelim: '模擬国内',
 *   Regional: '模擬地区'
 * }
 */
const JAG_TRANSLATIONS = {
  Prelim: ' 模擬国内 ',
  Regional: ' 模擬地区 ',
  Summer: ' 夏合宿 ',
  Winter: ' 冬合宿 ',
  Spring: ' 春合宿 ',
  '-day': ' Day',
};

const ICPC_TRANSLATIONS = {
  Prelim: ' 国内予選 ',
  Regional: ' アジア地区 ',
};

export function getAojContestLabel(
  translations: Readonly<ContestLabelTranslations>,
  contestId: string,
): string {
  let label = contestId;

  Object.entries(translations).forEach(([abbrEnglish, japanese]) => {
    label = label.replace(abbrEnglish, japanese);
  });

  return '（' + label + '）';
}

export const addContestNameToTaskIndex = (contestId: string, taskTableIndex: string): string => {
  const contestName = getContestNameLabel(contestId);

  if (isAojContest(contestId)) {
    return `AOJ ${taskTableIndex}${contestName}`;
  }

  return `${contestName} - ${taskTableIndex}`;
};

function isAojContest(contestId: string): boolean {
  return (
    aojCoursePrefixes.has(contestId) ||
    contestId.startsWith('PCK') ||
    regexForJag.test(contestId) ||
    contestId.startsWith('ICPC') ||
    regexForAojUniversity.test(contestId)
  );
}
