/**
 * Tử Vi Số · Calendar engine
 *
 * Wraps lunar-typescript with Vietnamese translation. All public functions
 * accept a Date or "YYYY-M-D" string and return UI-friendly objects.
 */
import { Solar, Lunar } from "lunar-typescript";
import {
  translateCanChi,
  translateYiJi,
  translateZodiac,
  translateBranch,
  translateTruc,
  XIU_28,
  TIANSHEN,
} from "./dict";

export type DayInfo = {
  /** YYYY-MM-DD */
  solarDate: string;
  /** "2026-04-14" lunar */
  lunarDate: { year: number; month: number; day: number };
  /** "Mười Bốn / Tháng Tư / Bính Ngọ" formatted parts */
  lunarLabel: string;
  /** Year/Month/Day Can Chi (translated, e.g. "Bính Ngọ") */
  yearGanZhi: string;
  monthGanZhi: string;
  dayGanZhi: string;
  /** Day of week 0..6, locale Sunday=0 */
  dayOfWeek: number;
  /** Year zodiac (Ngọ, Mùi, ...) Vietnamese */
  yearZodiac: string;
  /** Day branch (e.g. "Thìn") */
  dayBranch: string;
  /** Trực (12 jianchu): Kiến / Trừ / ... */
  truc: string;
  /** 28 tú with luck */
  xiu: { vi: string; luck: "Cát" | "Hung" } | null;
  /** Day Tianshen (12 thiên thần) — Hoàng đạo / Hắc đạo + name */
  tianshen: { vi: string; tone: "Hoàng đạo" | "Hắc đạo" } | null;
  /** Things to do today (translated) */
  yi: string[];
  /** Things to avoid today (translated) */
  ji: string[];
  /** Auspicious deities */
  jiShen: string[];
  /** Inauspicious deities */
  xiongSha: string[];
  /** Festivals (kept in original form) */
  festivals: string[];
  /** Auspicious hours: array of {branch (Tý..Hợi), tianshen, isHoang} */
  hoangDaoHours: HourInfo[];
  /** All hours including hắc đạo, ordered Tý..Hợi */
  allHours: HourInfo[];
  /** Whether the day's tianshen is Hoàng đạo */
  isHoangDaoDay: boolean;
};

export type HourInfo = {
  branch: string;
  /** "23:00–00:59" etc. */
  range: string;
  ganzhi: string;
  tianshen: { vi: string; tone: "Hoàng đạo" | "Hắc đạo" } | null;
  isHoang: boolean;
};

const HOUR_RANGES: Record<string, string> = {
  Tý: "23:00–00:59",
  Sửu: "01:00–02:59",
  Dần: "03:00–04:59",
  Mão: "05:00–06:59",
  Thìn: "07:00–08:59",
  Tỵ: "09:00–10:59",
  Ngọ: "11:00–12:59",
  Mùi: "13:00–14:59",
  Thân: "15:00–16:59",
  Dậu: "17:00–18:59",
  Tuất: "19:00–20:59",
  Hợi: "21:00–22:59",
};

function toSolar(input: Date | string): Solar {
  if (typeof input === "string") {
    const m = input.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
    if (!m) throw new Error(`Date phải YYYY-M-D (nhận: "${input}")`);
    return Solar.fromYmd(parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10));
  }
  return Solar.fromYmd(input.getFullYear(), input.getMonth() + 1, input.getDate());
}

function pad2(n: number): string {
  return String(n).padStart(2, "0");
}

function buildHourInfo(t: ReturnType<Lunar["getTimes"]>[number]): HourInfo {
  // lunar-typescript LunarTime: getGanZhi(), getZhi(), getTianShen(), getTianShenLuck()
  const branchZh = t.getZhi();
  const branch = translateBranch(branchZh);
  const tianshenZh = t.getTianShen();
  const tianshen = TIANSHEN[tianshenZh] ?? null;
  return {
    branch,
    range: HOUR_RANGES[branch] ?? "",
    ganzhi: translateCanChi(t.getGanZhi()),
    tianshen,
    isHoang: tianshen?.tone === "Hoàng đạo",
  };
}

/**
 * Get full day info for a solar date.
 *
 * Hour list: lunar-typescript returns 13 entries (early Tý + 11 + late Tý);
 * we collapse to the canonical 12 by keeping the first Tý.
 */
export function getDayInfo(input: Date | string): DayInfo {
  const solar = toSolar(input);
  const lunar = solar.getLunar();

  const dayBranchZh = lunar.getDayZhi();
  const dayTianshenZh = lunar.getDayTianShen();
  const tianshen = TIANSHEN[dayTianshenZh] ?? null;
  const xiuZh = lunar.getXiu();
  const xiu = XIU_28[xiuZh] ?? null;

  const monthCn = [
    "Giêng",
    "Hai",
    "Ba",
    "Tư",
    "Năm",
    "Sáu",
    "Bảy",
    "Tám",
    "Chín",
    "Mười",
    "Mười một",
    "Chạp",
  ];
  const lunarMonth = lunar.getMonth();
  const monthLabel = monthCn[Math.abs(lunarMonth) - 1] ?? String(lunarMonth);

  // Hours: dedupe by branch, keep first occurrence
  const seen = new Set<string>();
  const allHours: HourInfo[] = [];
  for (const t of lunar.getTimes()) {
    const info = buildHourInfo(t);
    if (seen.has(info.branch)) continue;
    seen.add(info.branch);
    allHours.push(info);
  }
  // Reorder Tý first
  allHours.sort((a, b) => {
    const order = [
      "Tý",
      "Sửu",
      "Dần",
      "Mão",
      "Thìn",
      "Tỵ",
      "Ngọ",
      "Mùi",
      "Thân",
      "Dậu",
      "Tuất",
      "Hợi",
    ];
    return order.indexOf(a.branch) - order.indexOf(b.branch);
  });

  return {
    solarDate: `${solar.getYear()}-${pad2(solar.getMonth())}-${pad2(solar.getDay())}`,
    lunarDate: { year: lunar.getYear(), month: lunar.getMonth(), day: lunar.getDay() },
    lunarLabel: `Ngày ${lunar.getDay()} tháng ${monthLabel}${lunarMonth < 0 ? " (nhuận)" : ""} năm ${translateCanChi(lunar.getYearInGanZhi())}`,
    yearGanZhi: translateCanChi(lunar.getYearInGanZhi()),
    monthGanZhi: translateCanChi(lunar.getMonthInGanZhi()),
    dayGanZhi: translateCanChi(lunar.getDayInGanZhi()),
    dayOfWeek: solar.getWeek(),
    yearZodiac: translateZodiac(lunar.getYearShengXiao()),
    dayBranch: translateBranch(dayBranchZh),
    truc: translateTruc(lunar.getZhiXing()),
    xiu,
    tianshen,
    yi: lunar.getDayYi().map(translateYiJi),
    ji: lunar.getDayJi().map(translateYiJi),
    jiShen: lunar.getDayJiShen(),
    xiongSha: lunar.getDayXiongSha(),
    festivals: [...solar.getFestivals(), ...lunar.getFestivals()],
    hoangDaoHours: allHours.filter((h) => h.isHoang),
    allHours,
    isHoangDaoDay: tianshen?.tone === "Hoàng đạo",
  };
}

/** Solar → lunar conversion only (lighter than full DayInfo). */
export function solarToLunar(input: Date | string): {
  year: number;
  month: number;
  day: number;
  label: string;
} {
  const info = getDayInfo(input);
  return {
    year: info.lunarDate.year,
    month: info.lunarDate.month,
    day: info.lunarDate.day,
    label: info.lunarLabel,
  };
}

/** Lunar → solar conversion. */
export function lunarToSolar(
  year: number,
  month: number,
  day: number,
  isLeapMonth = false
): { year: number; month: number; day: number; iso: string } {
  const lunar = Lunar.fromYmd(year, isLeapMonth ? -month : month, day);
  const solar = lunar.getSolar();
  return {
    year: solar.getYear(),
    month: solar.getMonth(),
    day: solar.getDay(),
    iso: `${solar.getYear()}-${pad2(solar.getMonth())}-${pad2(solar.getDay())}`,
  };
}

export type CalendarCell = {
  /** YYYY-MM-DD */
  iso: string;
  solarDay: number;
  lunarDay: number;
  /** "Mùng 1" if lunarDay === 1, else "" */
  monthMarker: string;
  isToday: boolean;
  isCurrentMonth: boolean;
  isHoangDao: boolean;
};

/**
 * Build a 6-row × 7-col calendar grid for a given solar month.
 * Days from prev/next month fill leading/trailing slots.
 */
export function buildMonthGrid(year: number, month: number, todayISO?: string): CalendarCell[] {
  const first = Solar.fromYmd(year, month, 1);
  const firstWeek = first.getWeek(); // 0..6 Sunday..Saturday
  const cells: CalendarCell[] = [];

  // Prev-month padding
  for (let i = firstWeek - 1; i >= 0; i--) {
    const prev = Solar.fromYmd(year, month, 1).next(-i - 1);
    const lun = prev.getLunar();
    const iso = `${prev.getYear()}-${pad2(prev.getMonth())}-${pad2(prev.getDay())}`;
    cells.push({
      iso,
      solarDay: prev.getDay(),
      lunarDay: lun.getDay(),
      monthMarker:
        lun.getDay() === 1 ? `Th${lun.getMonth() < 0 ? "(N)" : ""}${Math.abs(lun.getMonth())}` : "",
      isToday: iso === todayISO,
      isCurrentMonth: false,
      isHoangDao: TIANSHEN[lun.getDayTianShen()]?.tone === "Hoàng đạo",
    });
  }

  // Current month
  const lastDay = Solar.fromYmd(year, month, 1).next(0).next(31);
  // We don't actually know last day from API directly; iterate until month flips.
  for (let d = 1; d <= 31; d++) {
    const cur = Solar.fromYmd(year, month, 1).next(d - 1);
    if (cur.getMonth() !== month) break;
    const lun = cur.getLunar();
    const iso = `${cur.getYear()}-${pad2(cur.getMonth())}-${pad2(cur.getDay())}`;
    cells.push({
      iso,
      solarDay: cur.getDay(),
      lunarDay: lun.getDay(),
      monthMarker:
        lun.getDay() === 1 ? `Th${lun.getMonth() < 0 ? "(N)" : ""}${Math.abs(lun.getMonth())}` : "",
      isToday: iso === todayISO,
      isCurrentMonth: true,
      isHoangDao: TIANSHEN[lun.getDayTianShen()]?.tone === "Hoàng đạo",
    });
  }

  // Next-month padding to fill 6 rows × 7 cols = 42
  let extra = 1;
  while (cells.length < 42) {
    const cur = Solar.fromYmd(year, month, 1).next(
      cells.filter((c) => c.isCurrentMonth).length + extra - 1
    );
    const lun = cur.getLunar();
    const iso = `${cur.getYear()}-${pad2(cur.getMonth())}-${pad2(cur.getDay())}`;
    cells.push({
      iso,
      solarDay: cur.getDay(),
      lunarDay: lun.getDay(),
      monthMarker:
        lun.getDay() === 1 ? `Th${lun.getMonth() < 0 ? "(N)" : ""}${Math.abs(lun.getMonth())}` : "",
      isToday: iso === todayISO,
      isCurrentMonth: false,
      isHoangDao: TIANSHEN[lun.getDayTianShen()]?.tone === "Hoàng đạo",
    });
    extra++;
    // Safety break
    if (extra > 100) break;
  }
  // Avoid unused-var lint for `lastDay`; keep for potential future heuristic.
  void lastDay;

  return cells;
}

/** Categories for "Tra ngày tốt" tool. */
export type GoodDayPurpose =
  | "wedding"
  | "move"
  | "open-business"
  | "ground-breaking"
  | "burial"
  | "travel"
  | "haircut";

const PURPOSE_META: Record<GoodDayPurpose, { label: string; yiKeys: string[] }> = {
  wedding: { label: "Cưới hỏi", yiKeys: ["Cưới hỏi", "Đính hôn", "Hỏi vợ"] },
  move: { label: "Chuyển nhà", yiKeys: ["Chuyển nhà", "Nhập trạch", "Dọn nhà"] },
  "open-business": {
    label: "Khai trương",
    yiKeys: ["Khai trương", "Lập khế ước", "Giao dịch", "Nhận tài"],
  },
  "ground-breaking": {
    label: "Động thổ",
    yiKeys: ["Động thổ", "Khởi nền", "Phá thổ", "Cất nóc"],
  },
  burial: { label: "An táng", yiKeys: ["An táng", "Hạ huyệt", "Nhập liệm"] },
  travel: { label: "Đi xa", yiKeys: ["Đi xa", "Đi thuyền"] },
  haircut: { label: "Cắt tóc", yiKeys: ["Cắt tóc"] },
};

export type GoodDayHit = {
  iso: string;
  solar: string;
  lunar: string;
  dayOfWeek: number;
  yiHits: string[];
  jiBlockers: string[];
  isHoangDaoDay: boolean;
  truc: string;
  /** Higher = more auspicious for the purpose */
  score: number;
};

/**
 * Search for good days matching a purpose within a date range (inclusive).
 * Score: +2 per yi hit, +3 if Hoàng đạo day, -5 per ji blocker.
 */
export function findGoodDays(
  purpose: GoodDayPurpose,
  fromISO: string,
  toISO: string,
  limit = 20
): GoodDayHit[] {
  const meta = PURPOSE_META[purpose];
  const start = toSolar(fromISO);
  const end = toSolar(toISO);
  const startTs = new Date(start.getYear(), start.getMonth() - 1, start.getDay()).getTime();
  const endTs = new Date(end.getYear(), end.getMonth() - 1, end.getDay()).getTime();
  if (endTs < startTs) return [];

  const hits: GoodDayHit[] = [];
  let cur = start;
  // Hard cap to avoid infinite loop on malformed input
  for (let safety = 0; safety < 400; safety++) {
    const curTs = new Date(cur.getYear(), cur.getMonth() - 1, cur.getDay()).getTime();
    if (curTs > endTs) break;
    const info = getDayInfo(`${cur.getYear()}-${cur.getMonth()}-${cur.getDay()}`);
    const yiHits = info.yi.filter((y) => meta.yiKeys.includes(y));
    const jiBlockers = info.ji.filter((j) => meta.yiKeys.includes(j));
    if (yiHits.length > 0) {
      const score = yiHits.length * 2 + (info.isHoangDaoDay ? 3 : 0) - jiBlockers.length * 5;
      hits.push({
        iso: info.solarDate,
        solar: info.solarDate,
        lunar: info.lunarLabel,
        dayOfWeek: info.dayOfWeek,
        yiHits,
        jiBlockers,
        isHoangDaoDay: info.isHoangDaoDay,
        truc: info.truc,
        score,
      });
    }
    cur = cur.next(1);
  }

  hits.sort((a, b) => b.score - a.score || a.iso.localeCompare(b.iso));
  return hits.slice(0, limit);
}

export const PURPOSE_LABELS: Record<GoodDayPurpose, string> = Object.fromEntries(
  (Object.keys(PURPOSE_META) as GoodDayPurpose[]).map((k) => [k, PURPOSE_META[k].label])
) as Record<GoodDayPurpose, string>;

export const ALL_PURPOSES: GoodDayPurpose[] = Object.keys(PURPOSE_META) as GoodDayPurpose[];
