export type Locale = "en" | "bn";

export function cn(...classes: Array<string | false | null | undefined>): string {
  return classes.filter(Boolean).join(" ");
}

export function formatBDT(value: string | number | null | undefined, locale: Locale = "en"): string {
  const n = Number(value ?? 0);
  const formatted = new Intl.NumberFormat(locale === "bn" ? "bn-BD" : "en-US", {
    maximumFractionDigits: 0,
  }).format(n);
  return `৳${formatted}`;
}

export function formatDate(iso: string | Date | null | undefined, locale: Locale = "en"): string {
  if (!iso) return "—";
  const d = new Date(iso);
  return new Intl.DateTimeFormat(locale === "bn" ? "bn-BD" : "en-GB", {
    day: "numeric",
    month: "short",
    year: "numeric",
  }).format(d);
}

export function formatDateTime(iso: string | Date | null | undefined, locale: Locale = "en"): string {
  if (!iso) return "—";
  const d = new Date(iso);
  return new Intl.DateTimeFormat(locale === "bn" ? "bn-BD" : "en-GB", {
    day: "numeric",
    month: "short",
    year: "numeric",
    hour: "2-digit",
    minute: "2-digit",
  }).format(d);
}

export function formatNumber(n: number, locale: Locale = "en"): string {
  return new Intl.NumberFormat(locale === "bn" ? "bn-BD" : "en-US").format(n);
}

/** Format an "HH:MM" 24h time string for display (e.g. 14:30 → 2:30 PM). */
export function formatTime(time: string | null | undefined, locale: Locale = "en"): string {
  if (!time) return "—";
  let h: number, m: number;
  if (/^\d{2}:\d{2}$/.test(time)) {
    [h, m] = time.split(":").map(Number);
  } else {
    const d = new Date(time); // full ISO timestamp (device punches)
    if (isNaN(d.getTime())) return time;
    h = d.getHours();
    m = d.getMinutes();
  }
  const d = new Date(2000, 0, 1, h, m);
  return new Intl.DateTimeFormat(locale === "bn" ? "bn-BD" : "en-US", {
    hour: "numeric",
    minute: "2-digit",
    hour12: true,
  }).format(d);
}

export function addMonths(date: Date, months: number): Date {
  const d = new Date(date);
  d.setMonth(d.getMonth() + months);
  return d;
}

/** Download rows as a CSV file (BOM prefix so Excel renders Bangla correctly). */
export function downloadCsv(filename: string, rows: (string | number)[][]): void {
  const escape = (v: string | number) => {
    const s = String(v ?? "");
    return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
  };
  const csv = rows.map((r) => r.map(escape).join(",")).join("\r\n");
  const blob = new Blob(["\uFEFF" + csv], { type: "text/csv;charset=utf-8;" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename.endsWith(".csv") ? filename : `${filename}.csv`;
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(url);
}

/* ------------------------------------------------------------------ */
/*  Smooth section scrolling                                           */
/* ------------------------------------------------------------------ */
let smoothScrollRaf: number | null = null;
let smoothScrollTarget: HTMLElement | null = null;
let smoothScrollCleanup: (() => void) | null = null;

/** Glide the page to a section element (id/class anchored scrolling).
 *
 *  Used instead of scrollIntoView({ behavior: "smooth" }): the theme sets
 *  html{scroll-behavior:smooth}, so every scroll call becomes an async native
 *  animation — and repeated calls (re-renders while a sheet loads) plus layout
 *  shifts (QR images, sheet swaps) cancel those animations mid-flight,
 *  leaving the page unscrolled or at a random offset. This JS tween instead:
 *    - re-targets the element's live position every frame, so content that
 *      resizes above it is tracked, never fought;
 *    - ignores repeat calls for the element it is already gliding to;
 *    - hands over instantly when the user wheels / drags / presses a key;
 *    - stops if the element unmounts (the next call glides to the new node);
 *    - falls back to an instant jump when rAF never runs (hidden/occluded
 *      page) or the user prefers reduced motion — the section always reveals.
 */
export function smoothScrollToSection(el: HTMLElement, offset = 0, duration = 500): void {
  if (typeof window === "undefined") return;

  // already gliding to this exact element — the per-frame re-target keeps it accurate
  if (smoothScrollRaf !== null && smoothScrollTarget === el && el.isConnected) return;

  const liveTarget = () => Math.max(0, el.getBoundingClientRect().top + window.scrollY - offset);
  const targetNow = liveTarget();
  if (Math.abs(targetNow - window.scrollY) < 2) return; // already there

  const jumpThere = () =>
    window.scrollTo({ top: liveTarget(), behavior: "instant" });

  const prefersReducedMotion =
    typeof matchMedia !== "undefined" &&
    matchMedia("(prefers-reduced-motion: reduce)").matches;
  if (prefersReducedMotion) { jumpThere(); return; }

  // cancel any in-flight glide first
  if (smoothScrollRaf !== null) cancelAnimationFrame(smoothScrollRaf);
  smoothScrollCleanup?.();
  smoothScrollCleanup = null;

  const startY = window.scrollY;
  const t0 = performance.now();
  let userTookOver = false;
  let ranFrame = false;
  const stop = () => { userTookOver = true; };
  const events = ["wheel", "touchstart", "mousedown", "keydown"] as const;
  events.forEach((e) => window.addEventListener(e, stop, { passive: true }));

  let watchdog = 0;
  const finish = () => {
    window.clearTimeout(watchdog);
    events.forEach((e) => window.removeEventListener(e, stop));
    smoothScrollRaf = null;
    smoothScrollTarget = null;
    smoothScrollCleanup = null;
  };
  smoothScrollCleanup = finish;

  const easeInOutCubic = (t: number) =>
    t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;

  const step = (now: number) => {
    ranFrame = true;
    if (userTookOver || !el.isConnected) { finish(); return; }
    // live target — the section may move while content above it loads
    const target = liveTarget();
    const t = Math.min(1, (now - t0) / duration);
    // "instant" overrides html{scroll-behavior:smooth}: the rAF loop is the animation
    window.scrollTo({ top: startY + (target - startY) * easeInOutCubic(t), behavior: "instant" });
    if (t < 1) smoothScrollRaf = requestAnimationFrame(step);
    else finish();
  };
  smoothScrollTarget = el;
  smoothScrollRaf = requestAnimationFrame(step);

  // rAF is suspended while the page isn't rendered (hidden tab / occluded
  // webview) — if not a single frame ran, jump so the section still reveals
  watchdog = window.setTimeout(() => {
    if (!ranFrame && smoothScrollTarget === el) {
      if (smoothScrollRaf !== null) cancelAnimationFrame(smoothScrollRaf);
      finish();
      if (el.isConnected) jumpThere();
    }
  }, 250);
}

/** Read an image file and downscale it (max 256px, JPEG q0.85) into a compact
    data URL — small enough for profile photo columns. */
export function fileToResizedDataUrl(file: File, max = 256): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = () => reject(new Error("read"));
    reader.onload = () => {
      const url = String(reader.result);
      const img = new Image();
      img.onload = () => {
        const scale = Math.min(1, max / Math.max(img.width, img.height));
        const w = Math.max(1, Math.round(img.width * scale));
        const h = Math.max(1, Math.round(img.height * scale));
        const canvas = document.createElement("canvas");
        canvas.width = w;
        canvas.height = h;
        const ctx = canvas.getContext("2d");
        if (!ctx) return resolve(url);
        // flatten transparency (PNG) onto white before JPEG encoding
        ctx.fillStyle = "#ffffff";
        ctx.fillRect(0, 0, w, h);
        ctx.drawImage(img, 0, 0, w, h);
        const resized = canvas.toDataURL("image/jpeg", 0.85);
        resolve(resized.length < url.length ? resized : url);
      };
      img.onerror = () => reject(new Error("decode"));
      img.src = url;
    };
    reader.readAsDataURL(file);
  });
}
