export type AnyObject = Record<string, any>;

/*
|--------------------------------------------------------------------------
| Field definitions
|--------------------------------------------------------------------------
*/

export const PANCHANG_ELEMENTS: { key: string; aliases: string[]; label: string }[] = [
  { key: "tithi", aliases: ["tithi"], label: "Tithi" },
  { key: "nakshatra", aliases: ["nakshatra"], label: "Nakshatra" },
  { key: "yoga", aliases: ["yoga"], label: "Yoga" },
  { key: "karana", aliases: ["karana", "karan"], label: "Karana" },
  { key: "paksha", aliases: ["paksha"], label: "Paksha" },
  { key: "weekday", aliases: ["weekday", "day", "vaar", "day_of_week"], label: "Weekday" },
];

export const SAMVAT_ELEMENTS: { key: string; aliases: string[]; label: string }[] = [
  { key: "shaka_samvat", aliases: ["shaka_samvat", "shakasamvat", "shaka"], label: "Shaka Samvat" },
  { key: "vikram_samvat", aliases: ["vikram_samvat", "vikramsamvat", "vikram"], label: "Vikram Samvat" },
  { key: "moon_sign", aliases: ["moon_sign", "moonsign", "rashi", "chandra_rashi"], label: "Moon Sign" },
  { key: "sun_sign", aliases: ["sun_sign", "sunsign", "surya_rashi"], label: "Sun Sign" },
];

export const SUN_MOON_FIELDS: { key: string; aliases: string[]; label: string; icon: string; tone: string }[] = [
  { key: "sunrise", aliases: ["sunrise", "sun_rise"], label: "Sunrise", icon: "☀️", tone: "sunrise" },
  { key: "sunset", aliases: ["sunset", "sun_set"], label: "Sunset", icon: "🌇", tone: "sunset" },
  { key: "moonrise", aliases: ["moonrise", "moon_rise"], label: "Moonrise", icon: "🌙", tone: "moonrise" },
  { key: "moonset", aliases: ["moonset", "moon_set"], label: "Moonset", icon: "🌑", tone: "moonset" },
];

export const MUHURAT_ELEMENTS: { key: string; aliases: string[]; label: string }[] = [
  { key: "kantaka", aliases: ["kantaka_mrityu", "kantaka", "mrityu"], label: "Kantaka / Mrityu" },
  { key: "rahu_kaal", aliases: ["rahukaal", "rahu_kaal", "rahu_kalam", "rahu_kaalam"], label: "Rahu Kaal" },
  { key: "kaalvela", aliases: ["kaalvela_ardhayaam", "kaalvela", "ardhayaam"], label: "Kaalvela / Ardhayaam" },
  { key: "yamaghanta", aliases: ["yamaghanta"], label: "Yamaghanta" },
  { key: "yamakanta", aliases: ["yamakanta", "yamaganda", "yamagandam"], label: "Yamakanta" },
  { key: "kulika_kaal", aliases: ["kulika_kaal", "kulika"], label: "Kulika Kaal" },
  { key: "gulika_kaal", aliases: ["gulika", "gulika_kaal", "gulikai"], label: "Gulika Kaal" },
];

export const BALAM_ELEMENTS: { key: string; aliases: string[]; label: string }[] = [
  { key: "tarabalam", aliases: ["tarabalam", "tara_balam"], label: "Tarabalam" },
  { key: "chandrabalam", aliases: ["chandrabalam", "chandra_balam"], label: "Chandrabalam" },
];

/*
|--------------------------------------------------------------------------
| Field Extraction Helpers
|--------------------------------------------------------------------------
*/

export function extractField(obj: AnyObject, aliases: string[]): any {
  for (const key of aliases) {
    if (obj?.[key] !== undefined && obj?.[key] !== null) {
      return obj[key];
    }
  }

  return undefined;
}

export function formatPanchangElement(value: any): string {
  if (value === undefined || value === null) {
    return "-";
  }

  if (typeof value === "string" || typeof value === "number") {
    return String(value);
  }

  if (Array.isArray(value)) {
    return value.map((item) => formatPanchangElement(item)).join(", ");
  }

  if (typeof value === "object") {
    const name = value.name ?? value.title ?? value.value;

    const end = value.end_time ?? value.upto ?? value.end ?? value.ends_at;

    if (name && end) {
      return `${name} upto ${formatTimeShort(end)}`;
    }

    if (name) {
      return String(name);
    }
  }

  return "-";
}

export function formatTimeShort(value: any): string {
  if (typeof value !== "string") {
    return String(value);
  }

  /*
   * API timestamps look like "Fri Aug 28 2026 9:22:59 AM" or
   * plain "6:05:16 AM" — pull out just the HH:MM (+ AM/PM if
   * present), dropping the date and seconds.
   */

  const match = value.match(/(\d{1,2}:\d{2})(?::\d{2})?\s*([AaPp][Mm])?/);

  if (!match) {
    return value;
  }

  return match[2] ? `${match[1]} ${match[2].toUpperCase()}` : match[1];
}

/*
|--------------------------------------------------------------------------
| Samvat Formatting ("1948 Parabhava")
|--------------------------------------------------------------------------
*/

export function formatSamvat(number: any, name: any): string | undefined {
  if (number === undefined || number === null) {
    return undefined;
  }

  return name ? `${number} ${name}` : String(number);
}

/*
|--------------------------------------------------------------------------
| Muhurat Range Formatting ("From X To Y")
|--------------------------------------------------------------------------
*/

export function formatMuhuratRange(value: any): string {
  if (value === undefined || value === null) {
    return "-";
  }

  if (typeof value === "object" && !Array.isArray(value)) {
    const start = value.start ?? value.start_time ?? value.from;

    const end = value.end ?? value.end_time ?? value.to;

    if (start && end) {
      return `From ${formatTimeShort(String(start))} To ${formatTimeShort(String(end))}`;
    }
  }

  /*
   * The API returns these as a single "HH:MM AM to HH:MM AM"
   * string — reformat to match the "From X To Y" style.
   */

  if (typeof value === "string") {
    const parts = value.split(/\s+to\s+/i);

    if (parts.length === 2) {
      return `From ${formatTimeShort(parts[0])} To ${formatTimeShort(parts[1])}`;
    }
  }

  return formatPanchangElement(value);
}

/*
|--------------------------------------------------------------------------
| Unwrap nested "data" envelopes until the shape matches
|--------------------------------------------------------------------------
*/

export function unwrapUntil(
  raw: any,
  predicate: (obj: AnyObject) => boolean,
): AnyObject {
  if (!raw || typeof raw !== "object") {
    return {};
  }

  let current: AnyObject = raw;

  let guard = 0;

  while (
    current &&
    typeof current === "object" &&
    !Array.isArray(current) &&
    !predicate(current) &&
    current.data &&
    typeof current.data === "object" &&
    guard < 5
  ) {
    current = current.data;
    guard++;
  }

  return current || {};
}

export function isTodayShape(obj: AnyObject): boolean {
  return ["tithi", "nakshatra", "yoga", "karana"].some((key) => key in obj);
}

export function isSunMoonShape(obj: AnyObject): boolean {
  return ["sunrise", "sun_rise", "sunset", "sun_set"].some((key) => key in obj);
}

export function isChoghadiyaShape(obj: AnyObject): boolean {
  return (
    "day" in obj ||
    "night" in obj ||
    "choghadiya" in obj ||
    Array.isArray(obj)
  );
}

export function isHoraShape(obj: AnyObject): boolean {
  return "hora" in obj || "hora_list" in obj || Array.isArray(obj);
}

/*
|--------------------------------------------------------------------------
| Choghadiya Rows
|--------------------------------------------------------------------------
*/

/*
| Confirmed real response: { day: [{ start, end, muhurat, type }], night: [...] }
| — "muhurat" (no second "t"), not "muhurta". The API's own "type" field
| only carries 3 coarse buckets (Auspicious/Inauspicious/Good), not the
| 4-way Good/Neutral/Bad/Evil split the reference site shows — that split
| is the fixed, well-known meaning of each of the 7 muhurat names, so it's
| looked up by name below rather than trusted from the API.
*/

const CHOGHADIYA_NATURE: Record<string, "good" | "neutral" | "bad" | "evil"> = {
  amrit: "good",
  shubh: "good",
  subh: "good",
  labh: "good",
  chal: "neutral",
  udveg: "bad",
  rog: "evil",
  kaal: "evil",
};

export function getChoghadiyaNature(name: string): "good" | "neutral" | "bad" | "evil" | undefined {
  return CHOGHADIYA_NATURE[name.trim().toLowerCase()];
}

export interface ChoghadiyaRow {
  time: string;
  name: string;
  nature: string;
  start?: string;
  end?: string;
}

export function getChoghadiyaRows(report: AnyObject) {
  const day = normalizeChoghadiyaList(
    report?.day ?? report?.choghadiya?.day ?? (Array.isArray(report) ? report : null),
  );

  const night = normalizeChoghadiyaList(report?.night ?? report?.choghadiya?.night);

  return { day, night };
}

function normalizeChoghadiyaList(list: any): ChoghadiyaRow[] {
  if (!Array.isArray(list)) {
    return [];
  }

  return list
    .map((item) => {
      const name = item?.muhurat ?? item?.name ?? item?.muhurta ?? item?.title;

      if (!name) {
        return null;
      }

      const start = item?.start ?? item?.start_time ?? item?.from;

      const end = item?.end ?? item?.end_time ?? item?.to;

      const time =
        start && end
          ? `${formatTimeShort(start)} - ${formatTimeShort(end)}`
          : formatTimeShort(start ?? end ?? "");

      const nature =
        getChoghadiyaNature(String(name)) ??
        String(item?.nature ?? item?.type ?? "").toLowerCase();

      return {
        time,
        name: String(name),
        nature,
        start: start ? String(start) : undefined,
        end: end ? String(end) : undefined,
      };
    })
    .filter(Boolean) as ChoghadiyaRow[];
}

/*
|--------------------------------------------------------------------------
| Hora Rows
|--------------------------------------------------------------------------
*/

export interface HoraRow {
  time: string;
  lord: string;
  start?: string;
  end?: string;
  benefits?: string;
  luckyGem?: string;
}

/*
| Confirmed real response: { horas: [{ start, end, hora, benefits,
| lucky_gem }], day_of_week }. The list is "horas" (plural) — not
| "hora"/"hora_list" — and each item's planet name is under "hora"
| (singular), not "lord"/"planet"/"name".
*/

export function getHoraRows(report: AnyObject): HoraRow[] {
  const list = Array.isArray(report)
    ? report
    : Array.isArray(report?.horas)
      ? report.horas
      : Array.isArray(report?.hora)
        ? report.hora
        : Array.isArray(report?.hora_list)
          ? report.hora_list
          : [];

  return list
    .map((item: AnyObject) => {
      const lord = item?.hora ?? item?.lord ?? item?.planet ?? item?.name;

      if (!lord) {
        return null;
      }

      const start = item?.start ?? item?.start_time ?? item?.from;

      const end = item?.end ?? item?.end_time ?? item?.to;

      const time =
        start && end
          ? `${formatTimeShort(start)} - ${formatTimeShort(end)}`
          : formatTimeShort(start ?? end ?? "");

      return {
        time,
        lord: String(lord),
        start: start ? String(start) : undefined,
        end: end ? String(end) : undefined,
        benefits: item?.benefits ? String(item.benefits) : undefined,
        luckyGem: item?.lucky_gem ? String(item.lucky_gem) : undefined,
      };
    })
    .filter(Boolean) as HoraRow[];
}

/*
|--------------------------------------------------------------------------
| Planetary Position Rows
|--------------------------------------------------------------------------
|
| The "today" panchang endpoint hasn't been confirmed to return this —
| checked defensively across a few likely shapes; renders the "not
| available" placeholder if none match.
|
*/

export function getPlanetRows(
  report: AnyObject,
): { name: string; rashi: string; longitude: string; nakshatra: string; pada: string }[] {
  const list =
    (Array.isArray(report?.planets) && report.planets) ||
    (Array.isArray(report?.planetary_positions) && report.planetary_positions) ||
    (Array.isArray(report?.planet_details) && report.planet_details) ||
    null;

  if (!list) {
    return [];
  }

  return list
    .map((item: AnyObject) => {
      const name = item?.name ?? item?.planet;

      if (!name) {
        return null;
      }

      return {
        name: String(name),
        rashi: String(item?.rashi ?? item?.sign ?? item?.zodiac ?? "-"),
        longitude: String(item?.longitude ?? item?.degree ?? "-"),
        nakshatra: String(item?.nakshatra ?? "-"),
        pada: String(item?.pada ?? "-"),
      };
    })
    .filter(Boolean) as { name: string; rashi: string; longitude: string; nakshatra: string; pada: string }[];
}

/*
|--------------------------------------------------------------------------
| Festival Rows
|--------------------------------------------------------------------------
*/

export function getFestivalRows(raw: any): { name: string; description: string }[] {
  const report = unwrapUntil(raw, (obj) =>
    "festivals" in obj || "list" in obj || Array.isArray(obj),
  );

  const list = Array.isArray(report)
    ? report
    : Array.isArray(report?.festivals)
      ? report.festivals
      : Array.isArray(report?.list)
        ? report.list
        : [];

  return list
    .map((item: AnyObject) => {
      const name = item?.name ?? item?.title ?? item?.festival;

      if (!name) {
        return null;
      }

      const description = item?.description ?? item?.significance ?? item?.remark ?? "";

      return { name: String(name), description: String(description) };
    })
    .filter(Boolean) as { name: string; description: string }[];
}

/*
|--------------------------------------------------------------------------
| Date parsing (avoids timezone-shift bugs from `new Date("YYYY-MM-DD")`)
|--------------------------------------------------------------------------
*/

export function parseDateOnly(date: string): Date | null {
  const parts = date.split("-").map(Number);

  if (parts.length !== 3 || parts.some((part) => Number.isNaN(part))) {
    return null;
  }

  const [year, month, day] = parts;

  return new Date(year, month - 1, day);
}
