/**
 * @file 祝日データと関連ユーティリティのテンプレート
 * @module holidays-jp-template
 */

/**
 * 祝日データを格納するオブジェクト。
 * キーは 'YYYY-MM-DD' 形式の日付文字列、値は祝日名。
 * @type { [key: string]: string }
 */
export const holidays: { [key: string]: string } = {};

/**
 * Dateオブジェクトまたは日付文字列を 'YYYY-MM-DD' 形式の文字列に変換します。
 * @param {string | Date} date - 変換する日付。
 * @returns {string | null} 'YYYY-MM-DD' 形式の日付文字列。無効な日付の場合は `null`。
 */
const toDateString = (date: string | Date): string | null => {
  const dateObj = typeof date === "string" ? new Date(date) : date;

  // Invalid Dateオブジェクトの場合はnullを返す
  if (Number.isNaN(dateObj.getTime())) {
    return null;
  }

  const yyyy = dateObj.getFullYear();
  const mm = (dateObj.getMonth() + 1).toString().padStart(2, "0");
  const dd = dateObj.getDate().toString().padStart(2, "0");
  return [yyyy, mm, dd].join("-");
};

/**
 * 指定された日付が日本の祝日であるかどうかを判定します。
 * @param {string | Date} date - 判定する日付。文字列の場合は'YYYY-MM-DD'形式。
 * @returns {boolean} 祝日であれば `true`、そうでなければ `false`。
 */
export const isHoliday = (date: string | Date): boolean => {
  const key = toDateString(date);
  if (key === null) {
    return false;
  }
  return key in holidays;
};

/**
 * 指定された日付の祝日名を取得します。
 * @param {string | Date} date - 祝日名を取得する日付。文字列の場合は'YYYY-MM-DD'形式。
 * @returns {string | null} 祝日名。祝日でない場合は `null`。
 */
export const getHolidayName = (date: string | Date): string | null => {
  const key = toDateString(date);
  if (key === null) {
    return null;
  }
  return holidays[key] || null;
};

/**
 * 指定された日付が営業日（土日祝以外）であるかどうかを判定します。
 * @param {string | Date} date - 判定する日付。文字列の場合は'YYYY-MM-DD'形式。
 * @returns {boolean} 営業日であれば `true`、そうでなければ `false`。
 */
export const isBusinessDay = (date: string | Date): boolean => {
  const dateObj = typeof date === "string" ? new Date(date) : date;

  // 無効な日付の場合はfalseを返す
  if (Number.isNaN(dateObj.getTime())) {
    return false;
  }

  const dayOfWeek = dateObj.getDay();

  // 土日の場合はfalse
  if (dayOfWeek === 0 || dayOfWeek === 6) {
    return false;
  }

  // 祝日の場合はfalse（isHolidayで例外処理済み）
  return !isHoliday(date);
};

/**
 * 指定された日付からN営業日後（前）の日付を計算します。
 * @param {string | Date} date - 基準となる日付。文字列の場合は'YYYY-MM-DD'形式。
 * @param {number} days - 加算する営業日数（正なら未来方向、負なら過去方向）。
 * @param {"next" | "previous" | "none"} adjustment - 基準日の調整方法。
 *   省略時は days >= 0 なら "next"、days < 0 なら "previous"。
 *   - "next": 基準日が営業日でない場合、次の営業日に調整してから計算
 *   - "previous": 基準日が営業日でない場合、前の営業日に調整してから計算
 *   - "none": 基準日をそのまま使用（営業日でなくても調整しない）
 * @returns {string | null} 計算結果の日付（'YYYY-MM-DD'形式）。無効な日付の場合は `null`。
 */
export const offsetInBusinessDays = (
  date: string | Date,
  days: number,
  adjustment?: "next" | "previous" | "none"
): string | null => {
  const startDateObj = typeof date === "string" ? new Date(date) : date;

  // 無効な日付の場合はnullを返す
  if (Number.isNaN(startDateObj.getTime())) {
    return null;
  }

  // スマートデフォルトの決定
  const actualAdjustment = adjustment ?? (days >= 0 ? "next" : "previous");

  // 時間を00:00:00に統一したDateオブジェクト作成
  const currentDate = new Date(
    startDateObj.getFullYear(),
    startDateObj.getMonth(),
    startDateObj.getDate()
  );

  // 基準日を調整
  if (actualAdjustment !== "none") {
    const step = actualAdjustment === "next" ? 1 : -1;
    while (!isBusinessDay(currentDate)) {
      currentDate.setDate(currentDate.getDate() + step);
    }
  }

  // 営業日を加算/減算
  const direction = days > 0 ? 1 : -1;
  let remainingDays = Math.abs(days);

  while (remainingDays > 0) {
    currentDate.setDate(currentDate.getDate() + direction);

    if (isBusinessDay(currentDate)) {
      remainingDays--;
    }
  }

  return toDateString(currentDate);
};
