/* ===================== Duewise (iOS-friendly UI) ===================== */
const { useState, useEffect, useRef } = React;

/* ---- Month helpers (local time) ---- */
function ymFormat(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  return `${y}-${m}`;
}
function ymParse(ym) {
  const [y, m] = ym.split("-").map(Number);
  return new Date(y, m - 1, 1);
}

// returns 28..31 for a given "YYYY-MM"
function daysInMonth(ym) {
  const [y, m] = ym.split("-").map(Number);
  return new Date(y, m, 0).getDate();
}

// Fri, Sat, ...
function weekdayShort(Y, M, d) {
  return new Date(Y, M - 1, d).toLocaleString(undefined, { weekday: "short" });
}

// Parse "YYYY-MM-DD" *without* Date() (avoids timezone shifts)
function dayFromDateStr(s) {
  if (!s) return null;
  const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(s).slice(0, 10));
  return m ? Number(m[3]) : null;
}

// Most-used categories (+ "Other")
const CATEGORIES = [
  "Rent/Mortgage",
  "Maintenance",
  "Electricity",
  "Water",
  "Gas",
  "Trash/Sewer",
  "Internet",
  "Mobile Phone",
  "TV/Cable",
  "Streaming",
  "Credit Card",
  "Loan",
  "Insurance",
  "House Help",
  "Tution Fees",
  "Saving",
  "Investment",
  "Self Transfer",
  "Transfer",
  "App Subscription",
  "Software Subscription",
  "Groceries",
  "Dining & Food",
  "Shopping",
  "Fuel",
  "Transport",
  "Parking/Tolls",
  "Healthcare",
  "Pharmacy",
  "Education",           // new, while keeping "Tution Fees"
  "Recharge/Top-up",     // prepaid mobile/DTH/gift cards/FASTag top-ups
  "Fees/Charges",
  "Cash Withdrawal",
  "Income/Salary",
  "Other",
  
];

/* Display helpers */
const fmtAmt = (v) =>
  v === null || v === undefined || v === "" ? (
    <i className="bi bi-question-lg"></i>
  ) : (
    `${Number(v).toFixed(2)}`
  );
const getDisplayAmount = (b) => b.amount ?? b.default_amount ?? null;
const isVariableBill = (b) =>
  b.default_amount === null || b.default_amount === undefined;

/* ---- API helpers ---- */
const API = "/api";
let CSRF = null;
async function getCsrf() {
  const r = await fetch(`${API}/csrf`, { credentials: "include" });
  const j = await r.json();
  CSRF = j.csrf;
  return CSRF;
}
const opt = (m, b) => ({
  method: m,
  credentials: "include",
  headers: {
    "Content-Type": "application/json",
    ...(CSRF ? { "X-CSRF": CSRF } : {}),
  },
  body: b ? JSON.stringify(b) : undefined,
});

const auth = {
  async login(email, password) {
    await getCsrf();
    const r = await fetch(`${API}/login`, opt("POST", { email, password }));
    return r.json();
  },
  async register(name, email, password) {
    await getCsrf();
    const r = await fetch(
      `${API}/register`,
      opt("POST", { name, email, password })
    );
    return r.json();
  },
   async logout() {
    await getCsrf();                              // <— ensure token is loaded
    const r = await fetch(`${API}/logout`, opt('POST'));
    const j = await safeJson(r);
    if (!r.ok || j?.ok === false) throw new Error(j?.error || 'Logout failed');
    return j || { ok: true };
  },
  async logout2() {
    const r = await fetch(`${API}/logout`, opt("POST"));
    return r.json();
  },
  async me() {
    const r = await fetch(`${API}/me`, { credentials: "include" });
    return r.json();
  },
};

async function safeJson(res) {
  const text = await res.text(); // works for empty/204 too
  try {
    return text ? JSON.parse(text) : null;
  } catch {
    return null;
  }
}

const api = {
  dashboard: async (month) => {
    const r = await fetch(`${API}/dashboard?month=${month}`, {
      credentials: "include",
    });
    return r.json();
  },
  complete: async (bill_id, month, amount) => {
    await getCsrf();
    const body = { bill_id, month };
    if (amount !== undefined && amount !== null && String(amount) !== "") {
      body.amount = Number(amount);
    }
    const res = await fetch(`${API}/complete`, opt("POST", body));
    const json = await safeJson(res);
    if (!res.ok || json?.ok === false) {
      throw new Error(json?.error || `Complete failed (${res.status})`);
    }
    return json || { ok: true };
  },
  uncomplete: async (bill_id, month) => {
    await getCsrf();
    const res = await fetch(
      `${API}/uncomplete`,
      opt("POST", { bill_id, month })
    );
    const json = await safeJson(res);
    if (!res.ok || json?.ok === false) {
      throw new Error(json?.error || `Uncomplete failed (${res.status})`);
    }
    return json || { ok: true };
  },
  billsList: async () => {
    const r = await fetch(`${API}/bills`, { credentials: "include" });
    return r.json();
  },
  billCreate: async (d) => {
    await getCsrf();
    const r = await fetch(`${API}/bills`, opt("POST", d));
    return r.json();
  },
  billUpdate: async (id, d) => {
    await getCsrf();
    const r = await fetch(`${API}/bills/${id}`, opt("PUT", d));
    return r.json();
  },
  billDelete: async (id) => {
    await getCsrf();
    const r = await fetch(`${API}/bills/${id}`, opt("DELETE"));
    return r.json();
  },
  stats: async (bill_id, from, to) => {
    const params = new URLSearchParams({ bill_id, from, to });
    const r = await fetch(`${API}/stats?${params}`, { credentials: "include" });
    return r.json();
  },
  overview: async (from, to, exclude = {}) => {
    const p = new URLSearchParams({ from, to });
    if (exclude.billIds?.length)
      p.set("exclude_bills", exclude.billIds.join(","));
    if (exclude.categories?.length)
      p.set("exclude_categories", exclude.categories.join(","));
    const r = await fetch(`${API}/overview?${p.toString()}`, {
      credentials: "include",
    });
    return r.json();
  },
};

function FloatingAddButton({ onClick, title = "Add" }) {
  return (
    <div className="fab">
      <button
        className="btn btn-primary"
        title={title}
        aria-label={title}
        onClick={onClick}
      >
        <i className="bi bi-plus-lg"></i>
      </button>
    </div>
  );
}

async function refreshApp() {
  try {
    if ("serviceWorker" in navigator) {
      const reg = await navigator.serviceWorker.getRegistration();
      if (reg) {
        reg.update();
        const timeout = setTimeout(() => location.reload(), 600);
        navigator.serviceWorker.addEventListener(
          "controllerchange",
          () => {
            clearTimeout(timeout);
            location.reload();
          },
          { once: true }
        );
        return;
      }
    }
  } catch (e) {}
  location.reload();
}

/* ---- Utilities ---- */
function cleanupBackdrops() {
  document.querySelectorAll(".modal-backdrop").forEach((el) => el.remove());
  document.body.classList.remove("modal-open");
  document.body.style.removeProperty("padding-right");
}
function ensureBootstrapModal(el) {
  // eslint-disable-next-line no-undef
  return bootstrap.Modal.getOrCreateInstance(el, {
    backdrop: true,
    keyboard: true,
  });
}
function loadChartJS() {
  return new Promise((resolve, reject) => {
    if (window.Chart) return resolve();
    const s = document.createElement("script");
    s.src = "https://cdn.jsdelivr.net/npm/chart.js";
    s.onload = () => resolve();
    s.onerror = reject;
    document.head.appendChild(s);
  });
}

/* ---------------- UI ---------------- */

function TopBar({
  view,
  setView,
  onLogout,
  onRefresh,
  onToday,
  showToday,
  onOpenGlobal,
  month,
  setMonth,
}) {
  return (
    <div className="app-topbar">
      <div className="container dw-container app-shell">
        {/* Row 1: brand left, controls right */}
        <div className="d-flex align-items-center justify-content-between px-2 py-2 border-bottom ">
          <div className="brand fs-4">
            <svg
              viewBox="0 0 280 316"
              xmlns="http://www.w3.org/2000/svg"
              className="w-20 h-20 text-primary"
              width="30.92"
              height="30.53"
            >
              <path d="M111.94.28c32.86-2.55,73.97,12.56,95.08,38.3,45.2,55.12,41.03,148.62-4.04,202.64-39.24,47.03-92.14,57.57-146.6,79.2-5.4.85-10.29-3.02-14.84-5.61-11.1-6.31-26.04-14.72-36.21-22.03-2.14-1.54-4.37-3.28-4.97-6.01-.17-51.39.11-102.79,0-154.18-.06-26.55-.77-53.1,0-79.68.2-6.82-2.54-17.72,5.45-20.32C35.51,24.45,64.81,11.82,94.37,3.72c5.66-1.55,11.69-2.98,17.57-3.44ZM59.53,308.99l1.82-.33c50.83-20.55,102.88-31.41,137.77-77.53,49.44-65.35,41.02-191.67-48.79-217.02-23.11-6.53-36.98-5.56-59.66,1.17-24.04,7.13-47.65,15.99-71.72,23.05-.26,1.14.21.77.62,1.06,10.73,7.46,25.1,12.75,36.93,18.38,23.26-5.41,48.2-18.78,72.48-16.1,38.99,4.31,62.5,36.62,66.6,73.75,6.09,55.21-13.16,102.37-62.52,129.37-22.68,12.41-49.02,19.45-73.53,27.44v36.76ZM49.98,65.54L9.88,45.96v237.49l2.91,2.35,37.19,22.24V65.54ZM59.53,261.74c.47.47,10.35-2.73,11.82-3.21,48.13-15.61,90.96-31.08,109.08-82.82,10.21-29.18,10.04-77.32-11.12-101.8-18.22-21.08-39.84-27.01-66.92-20.17-14.29,3.61-28.13,8.99-42.36,12.77l-.5,195.22Z" />
            </svg>
            <span
              className="fw-lighter align-text-bottom"
              style={{ fontSize: "12px", marginTop: "14px" }}
            >
              v1.0
            </span>
          </div>

          <div className="d-flex align-items-center gap-2 ">
            {showToday && (
              <button
                className="btn btn-light icon-btn"
                aria-label="Today"
                title="Today"
                onClick={onToday}
              >
                <i className="bi bi-calendar-event"></i>
              </button>
            )}
            <button
              className="btn btn-light icon-btn"
              aria-label="Refresh"
              title="Refresh"
              onClick={onRefresh}
            >
              <i className="bi bi-arrow-clockwise"></i>
            </button>

            {/* NEW: Global Insights button */}
            <button
              className="btn btn-transparent icon-btn"
              aria-label="Insights"
              title="Insights"
              onClick={onOpenGlobal}
            >
              <i class="bi bi-file-earmark-bar-graph"></i>
            </button>

            <button
              className="btn btn-light icon-btn"
              aria-label="Logout"
              title="Logout"
              onClick={onLogout}
            >
              <i className="bi bi-power"></i>
            </button>
          </div>
        </div>

        {/* Row 2: segmented control centered */}
        <div className="d-flex justify-content-center pt-2 pb-2 ">
          <div className="seg-wrap d-inline-flex">
            <button
              className={`btn ${view === "dashboard" ? "active" : ""}`}
              onClick={() => setView("dashboard")}
            >
              <i className="bi bi-grid-3x3-gap-fill me-1"></i>
              <span className=" d-sm-inline">Dashboard</span>
            </button>
            <button
              className={`btn ${view === "master" ? "active" : ""}`}
              onClick={() => setView("master")}
            >
              <i className="bi bi-list-ul me-1"></i>
              <span className="d-sm-inline">Master List</span>
            </button>
          </div>
        </div>
      </div>
      {view === "dashboard" ? (
        <MonthNav month={month} setMonth={setMonth} />
      ) : undefined}
    </div>
  );
}

function MonthNav({ month, setMonth }) {
  const dt = ymParse(month);
  const fmt = dt.toLocaleString(undefined, { month: "short", year: "numeric" });
  const shift = (n) => {
    const d = ymParse(month);
    d.setMonth(d.getMonth() + n);
    setMonth(ymFormat(d));
  };
  const goToday = () => setMonth(ymFormat(new Date()));
  return (
    <div className="d-grid month-row mb-2 border-top border-bottom pt-2 pb-2">
      <div className="d-flex align-items-center gap-2">
        <button
          className="btn btn-light icon-btn"
          onClick={() => shift(-1)}
          aria-label="Previous month"
        >
          <i className="bi bi-arrow-left"></i>
        </button>
      </div>
      <h3 className="text-center m-0">
        {fmt} <br />{" "}
        <button
          className="d-none btn btn-link p-0 text-secondary"
          onClick={goToday}
        >
          Today
        </button>
      </h3>
      <div className="text-end">
        <button
          className="btn btn-light icon-btn"
          onClick={() => shift(1)}
          aria-label="Next month"
        >
          <i className="bi bi-arrow-right"></i>
        </button>
      </div>
    </div>
  );
}

/* ---------------- Bill Insights Modal (per bill, readable) ---------------- */
function InsightsModal({ bill, open, onClose }) {
  const modalRef = useRef(null);
  const canvasRef = useRef(null);
  const chartRef = useRef(null);

  const [range, setRange] = useState("ytd"); // '6m' | '12m' | 'ytd'
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);

  // Pretty ₹ formatter + month labels like "Sep ’25"
  const INR = new Intl.NumberFormat("en-IN", {
    style: "currency",
    currency: "INR",
    maximumFractionDigits: 2,
  });
  const rupee = (n) => INR.format(Number(n || 0));
  const fmtYMpretty = (ym) => {
    if (!ym) return "-";
    const [y, m] = ym.split("-").map(Number);
    const d = new Date(y, m - 1, 1);
    return d.toLocaleString(undefined, { month: "short", year: "2-digit" });
  };

  function computeRange() {
    const now = new Date();
    const to = ymFormat(now);
    if (range === "6m") {
      const d = new Date(now);
      d.setMonth(d.getMonth() - 5);
      return { from: ymFormat(d), to };
    }
    if (range === "ytd") {
      const d = new Date(now.getFullYear(), 0, 1);
      return { from: ymFormat(d), to };
    }
    const d = new Date(now);
    d.setMonth(d.getMonth() - 11);
    return { from: ymFormat(d), to };
  }

  /* ---- modal open/close & cleanup ---- */
  useEffect(() => {
    const el = modalRef.current;
    if (!el) return;
    const m = ensureBootstrapModal(el);
    const onHidden = () => {
      if (chartRef.current) {
        chartRef.current.destroy();
        chartRef.current = null;
      }
      cleanupBackdrops();
      onClose && onClose();
    };
    el.addEventListener("hidden.bs.modal", onHidden);
    if (open) {
      cleanupBackdrops();
      m.show();
    } else {
      m.hide();
    }
    return () => {
      el.removeEventListener("hidden.bs.modal", onHidden);
      if (chartRef.current) {
        chartRef.current.destroy();
        chartRef.current = null;
      }
    };
  }, [open, onClose]);

  /* ---- fetch data when opened / range changes ---- */
  useEffect(() => {
    if (!open || !bill) return;
    const { from, to } = computeRange();
    setLoading(true);
    api.stats(bill.id, from, to).then((res) => {
      setData(res);
      setLoading(false);
    });
  }, [open, bill, range]);

  /* ---- build chart once we have data ---- */
  useEffect(() => {
    (async () => {
      if (!open || !data || !canvasRef.current) return;
      await loadChartJS();
      if (chartRef.current) {
        chartRef.current.destroy();
        chartRef.current = null;
      }

      const labels = (data.months || []).map((m) => fmtYMpretty(m.ym));
      const amounts = (data.months || []).map((m) => Number(m.amount || 0));

      chartRef.current = new Chart(canvasRef.current.getContext("2d"), {
        type: "bar",
        data: {
          labels,
          datasets: [
            {
              label: "Amount paid",
              data: amounts,
              backgroundColor: "rgba(59, 130, 246, .28)",
              borderColor: "rgba(59, 130, 246, .9)",
              borderWidth: 1.2,
              borderRadius: 6,
              maxBarThickness: 32,
            },
          ],
        },
        options: {
          responsive: true,
          plugins: {
            legend: { display: false },
            tooltip: { callbacks: { label: (ctx) => rupee(ctx.parsed.y) } },
          },
          scales: {
            y: {
              beginAtZero: true,
              ticks: { callback: (v) => rupee(v) },
            },
            x: { ticks: { maxRotation: 0, autoSkip: true } },
          },
        },
      });
    })();
  }, [open, data]);

  // Derive min/max months from the series (clearer than just values)
  const series = data?.months || [];
  const paidOnly = series.filter((m) => Number(m.amount) > 0);
  const minRow = paidOnly.length
    ? paidOnly.reduce((a, b) => (b.amount < a.amount ? b : a))
    : null;
  const maxRow = paidOnly.length
    ? paidOnly.reduce((a, b) => (b.amount > a.amount ? b : a))
    : null;

  const total = Number(data?.summary?.total || 0);
  const monthsPaid = Number(data?.summary?.months_paid || 0);
  const monthsTotal = Number(data?.summary?.months_total || series.length || 0);
  const avgPaid = Number(data?.summary?.avg_paid || 0);
  const completion = monthsTotal ? (monthsPaid * 100) / monthsTotal : 0;

  return (
    <div className="modal fade" id="insightsModal" tabIndex="-1" ref={modalRef}>
      <div className="modal-dialog modal-dialog-scrollable">
        <div className="modal-content">
          <div className="modal-header">
            <h5 className="modal-title">
              Insights {bill ? `— ${bill.title}` : ""}
            </h5>
            <button
              type="button"
              className="btn-close"
              data-bs-dismiss="modal"
              aria-label="Close"
            ></button>
          </div>

          <div className="modal-body">
            {/* Controls */}
            <div className="d-flex justify-content-end mb-2">
              <select
                className="form-select form-select-sm w-auto"
                value={range}
                onChange={(e) => setRange(e.target.value)}
              >
                <option value="6m">Last 6 months</option>
                <option value="12m">Last 12 months</option>
                <option value="ytd">Year to date</option>
              </select>
            </div>

            {loading && <p className="mb-0">Loading…</p>}

            {!loading && data && (
              <>
                {/* Stat tiles */}
                <div className="insights-grid mb-2">
                  <div className="stat">
                    <small>Total paid</small>
                    <div className="k">{rupee(total)}</div>
                  </div>
                  <div className="stat">
                    <small>Months paid</small>
                    <div className="k">
                      {monthsPaid} / {monthsTotal}
                    </div>
                  </div>
                  <div className="stat">
                    <small>Average (paid months)</small>
                    <div className="k">{rupee(avgPaid)}</div>
                  </div>
                  <div className="stat">
                    <small>Best month</small>
                    <div className="k">
                      {minRow
                        ? `${fmtYMpretty(minRow.ym)} · ${rupee(minRow.amount)}`
                        : "-"}
                    </div>
                  </div>
                  <div className="stat col-12">
                    <small>Completion in range</small>
                    <div className="k">{completion.toFixed(0)}%</div>
                    <div
                      className="progress mt-2"
                      role="progressbar"
                      aria-label="Completion"
                    >
                      <div
                        className="progress-bar bg-success"
                        style={{ width: `${Math.min(100, completion)}%` }}
                      />
                    </div>
                  </div>
                </div>

                {/* Chart */}
                <div className="card mb-3">
                  <div className="card-body">
                    <h6 className="card-title">Monthly Amounts</h6>
                    <canvas ref={canvasRef} height="170"></canvas>
                  </div>
                </div>

                {/* Monthly breakdown */}
                <h6 className="mb-2">Monthly Breakdown</h6>
                {series.length ? (
                  <ul className="list-group">
                    {series.map((m, i) => (
                      <li
                        className="list-group-item d-flex justify-content-between"
                        key={i}
                      >
                        <span>{fmtYMpretty(m.ym)}</span>
                        <span className="fw-semibold">{rupee(m.amount)}</span>
                      </li>
                    ))}
                  </ul>
                ) : (
                  <p className="text-muted">No data.</p>
                )}
              </>
            )}
          </div>

          <div className="modal-footer">
            <button className="btn btn-secondary" data-bs-dismiss="modal">
              Close
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------------- Global Insights Modal (Readable) ---------------- */
function GlobalInsightsModal({ open, onClose }) {
  const modalRef = useRef(null);
  const barCanvas = useRef(null);
  const pieCanvas = useRef(null);
  const barChartRef = useRef(null);
  const pieChartRef = useRef(null);

  const [range, setRange] = useState("ytd"); // 6m | 12m | ytd
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);

  // --- NEW: exclude filters (persisted) ---
  const [exclude, setExclude] = useState(() => {
    try {
      return JSON.parse(
        localStorage.getItem("duewise_exclude") ||
          '{"billIds":[],"categories":[]}'
      );
    } catch {
      return { billIds: [], categories: [] };
    }
  });
  const [billOptions, setBillOptions] = useState([]);
  const [filtersOpen, setFiltersOpen] = useState(false);

  // Budget (existing)
  const [budget, setBudget] = useState(() => {
    const v = localStorage.getItem("duewise_budget");
    return v ? parseFloat(v) : "";
  });

  /* ---- format helpers ---- */
  const INR = new Intl.NumberFormat("en-IN", {
    style: "currency",
    currency: "INR",
    maximumFractionDigits: 2,
  });
  const rupee = (n) => INR.format(Number(n || 0));
  const fmtYMpretty = (ym) => {
    if (!ym) return "-";
    const [y, m] = ym.split("-").map(Number);
    const d = new Date(y, m - 1, 1);
    return d.toLocaleString(undefined, { month: "short", year: "2-digit" }); // e.g., Sep ’25
  };

  function computeRange() {
    const now = new Date();
    const to = ymFormat(now);
    if (range === "6m") {
      const d = new Date(now);
      d.setMonth(d.getMonth() - 5);
      return { from: ymFormat(d), to };
    }
    if (range === "ytd") {
      const d = new Date(now.getFullYear(), 0, 1);
      return { from: ymFormat(d), to };
    }
    const d = new Date(now);
    d.setMonth(d.getMonth() - 11);
    return { from: ymFormat(d), to };
  }

  /* ---- modal open/close & cleanup ---- */
  useEffect(() => {
    const el = modalRef.current;
    if (!el) return;
    const m = ensureBootstrapModal(el);
    const onHidden = () => {
      if (barChartRef.current) {
        barChartRef.current.destroy();
        barChartRef.current = null;
      }
      if (pieChartRef.current) {
        pieChartRef.current.destroy();
        pieChartRef.current = null;
      }
      cleanupBackdrops();
      onClose && onClose();
    };
    el.addEventListener("hidden.bs.modal", onHidden);
    if (open) {
      cleanupBackdrops();
      m.show();
    } else {
      m.hide();
    }
    return () => {
      el.removeEventListener("hidden.bs.modal", onHidden);
      if (barChartRef.current) {
        barChartRef.current.destroy();
        barChartRef.current = null;
      }
      if (pieChartRef.current) {
        pieChartRef.current.destroy();
        pieChartRef.current = null;
      }
    };
  }, [open, onClose]);

  /* ---- load bill options (for filters) when opened ---- */
  useEffect(() => {
    if (open)
      api
        .billsList()
        .then(setBillOptions)
        .catch(() => {});
  }, [open]);

  /* ---- fetch data when opened / range / excludes change ---- */
  useEffect(() => {
    if (!open) return;
    const { from, to } = computeRange();
    setLoading(true);
    api.overview(from, to, exclude).then((res) => {
      setData(res);
      setLoading(false);
    });
  }, [open, range, exclude]);

  /* ---- build charts once we have data ---- */
  useEffect(() => {
    (async () => {
      if (!open || !data) return;
      await loadChartJS();

      // Destroy old charts
      if (barChartRef.current) {
        barChartRef.current.destroy();
        barChartRef.current = null;
      }
      if (pieChartRef.current) {
        pieChartRef.current.destroy();
        pieChartRef.current = null;
      }

      // Bar chart (Monthly Totals)
      const labels = (data.months || []).map((m) => fmtYMpretty(m.ym));
      const totals = (data.months || []).map((m) => Number(m.total || 0));
      if (barCanvas.current) {
        barChartRef.current = new Chart(barCanvas.current.getContext("2d"), {
          type: "bar",
          data: {
            labels,
            datasets: [
              {
                label: "Monthly total",
                data: totals,
                backgroundColor: "rgba(16, 185, 129, .35)",
                borderColor: "rgba(16, 185, 129, .9)",
                borderWidth: 1.2,
                borderRadius: 6,
                maxBarThickness: 30,
              },
            ],
          },
          options: {
            responsive: true,
            plugins: {
              legend: { display: false },
              tooltip: { callbacks: { label: (ctx) => rupee(ctx.parsed.y) } },
            },
            scales: {
              y: { beginAtZero: true, ticks: { callback: (v) => rupee(v) } },
              x: { ticks: { maxRotation: 0, autoSkip: true } },
            },
          },
        });
      }

      // Doughnut chart (By Category)
      const catLabels = (data.categories || []).map((c) => c.category);
      const catTotals = (data.categories || []).map((c) =>
        Number(c.total || 0)
      );
      const palette = [
        "#10b981",
        "#06b6d4",
        "#3b82f6",
        "#f59e0b",
        "#ef4444",
        "#8b5cf6",
        "#22c55e",
        "#a3e635",
        "#14b8a6",
      ];
      if (pieCanvas.current) {
        pieChartRef.current = new Chart(pieCanvas.current.getContext("2d"), {
          type: "doughnut",
          data: {
            labels: catLabels,
            datasets: [
              {
                data: catTotals,
                backgroundColor: catTotals.map(
                  (_, i) => palette[i % palette.length]
                ),
                borderWidth: 0,
              },
            ],
          },
          options: {
            responsive: true,
            cutout: "58%",
            plugins: {
              legend: { position: "bottom" },
              tooltip: {
                callbacks: {
                  label: (ctx) => `${ctx.label}: ${rupee(ctx.parsed)}`,
                },
              },
            },
          },
        });
      }
    })();
  }, [open, data]);

  // Budget helpers
  const avgMonth = data?.summary?.avg_month ?? 0;
  const overUnder = budget !== "" ? avgMonth - Number(budget) : null;
  const totalAll = Number(data?.summary?.total || 0);
  const catWithPct = (data?.categories || []).map((c) => ({
    ...c,
    total: Number(c.total || 0),
    pct: totalAll ? (Number(c.total || 0) * 100) / totalAll : 0,
  }));

  // --- NEW: helpers to save exclusions ---
  function saveExclude(next) {
    setExclude(next);
    try {
      localStorage.setItem("duewise_exclude", JSON.stringify(next));
    } catch {}
  }

  // Active filter chips
  function FilterChips() {
    const cats = exclude.categories || [];
    const ids = exclude.billIds || [];
    if (!cats.length && !ids.length) return null;

    const idToTitle = {};
    billOptions.forEach((b) => (idToTitle[b.id] = b.title));

    return (
      <div className="mb-2">
        <div className="small text-muted mb-1">Excluding:</div>
        <div className="d-flex flex-wrap gap-2">
          {cats.map((c) => (
            <span
              key={`c-${c}`}
              className="badge bg-secondary-subtle text-dark border"
            >
              <span className="text-dark">{c}</span>
              <button
                className="btn btn-sm btn-link ms-1 p-0 text-dark"
                onClick={() =>
                  saveExclude({
                    ...exclude,
                    categories: cats.filter((x) => x !== c),
                  })
                }
                title="Remove"
              >
                <i className="bi bi-x"></i>
              </button>
            </span>
          ))}
          {ids.map((id) => (
            <span
              key={`b-${id}`}
              className="badge bg-secondary-subtle text-dark border"
            >
              <span className="text-dark">{idToTitle[id] || `#${id}`}</span>
              <button
                className="btn btn-sm btn-link ms-1 p-0 text-dark"
                onClick={() =>
                  saveExclude({
                    ...exclude,
                    billIds: ids.filter((x) => x !== id),
                  })
                }
                title="Remove"
              >
                <i className="bi bi-x"></i>
              </button>
            </span>
          ))}
          <button
            className="btn btn-sm btn-outline-secondary ms-1"
            onClick={() => saveExclude({ billIds: [], categories: [] })}
          >
            Clear all
          </button>
        </div>
      </div>
    );
  }

  // Category filter options from current data
  const categoryOptions = (data?.categories || []).map((c) => c.category);

  return (
    <div
      className="modal fade"
      id="globalInsightsModal"
      tabIndex="-1"
      ref={modalRef}
    >
      <div className="modal-dialog modal-dialog-scrollable modal-lg">
        <div className="modal-content">
          <div className="modal-header">
            <h5 className="modal-title">Global Insights</h5>
            <button
              type="button"
              className="btn-close"
              data-bs-dismiss="modal"
              aria-label="Close"
            ></button>
          </div>

          <div className="modal-body">
            {/* Controls */}
            <div class="d-grid gap-2 mb-3">
              <button
                className="btn btn-sm btn-outline-secondary"
                onClick={() => setFiltersOpen((s) => !s)}
              >
                {filtersOpen ? (
                  <i className="bi bi-x"></i>
                ) : (
                  <i className="bi bi-sliders"></i>
                )}{" "}
                Filters
              </button>
            </div>
            <div className="d-none d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
              <button
                className="btn btn-sm btn-outline-secondary"
                onClick={() => setFiltersOpen((s) => !s)}
              >
                {filtersOpen ? (
                  <i className="bi bi-x"></i>
                ) : (
                  <i className="bi bi-sliders"></i>
                )}{" "}
                Filters
              </button>
            </div>

            {/* NEW: Filters panel */}
            {filtersOpen && (
              <div className="card mb-2">
                <div className="card-body">
                  <div className="row g-3">
                    <div className="col-12 col-md-6">
                      <h6 className="mb-2">Exclude Categories</h6>
                      <div className="d-flex flex-wrap gap-2">
                        {categoryOptions.length ? (
                          categoryOptions.map((cat) => {
                            const key = (cat || "").toLowerCase();
                            const sel = exclude.categories.includes(key);
                            return (
                              <label
                                key={cat}
                                className={`btn btn-sm ${
                                  sel
                                    ? "btn-secondary"
                                    : "btn-outline-secondary"
                                }`}
                              >
                                <input
                                  type="checkbox"
                                  className="btn-check"
                                  checked={sel}
                                  onChange={() => {
                                    const next = sel
                                      ? exclude.categories.filter(
                                          (c) => c !== key
                                        )
                                      : [...exclude.categories, key];
                                    saveExclude({
                                      ...exclude,
                                      categories: next,
                                    });
                                  }}
                                />
                                {cat || "Uncategorized"}
                              </label>
                            );
                          })
                        ) : (
                          <div className="text-muted small">
                            No categories yet.
                          </div>
                        )}
                      </div>
                    </div>

                    <div className="col-12 col-md-6">
                      <h6 className="mb-2">Exclude Bills</h6>
                      <div
                        className="border rounded p-2"
                        style={{ maxHeight: 180, overflowY: "auto" }}
                      >
                        {billOptions.length ? (
                          billOptions.map((b) => {
                            const sel = exclude.billIds.includes(b.id);
                            return (
                              <label
                                key={b.id}
                                className="d-flex align-items-center justify-content-between py-1"
                              >
                                <span className="me-2 small text-truncate">
                                  {b.title}
                                </span>
                                <input
                                  type="checkbox"
                                  checked={sel}
                                  onChange={() => {
                                    const next = sel
                                      ? exclude.billIds.filter(
                                          (id) => id !== b.id
                                        )
                                      : [...exclude.billIds, b.id];
                                    saveExclude({ ...exclude, billIds: next });
                                  }}
                                />
                              </label>
                            );
                          })
                        ) : (
                          <div className="text-muted small">No bills yet.</div>
                        )}
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            )}
            <FilterChips />
            <div class="container text-center ">
  <div class="row mb-3">
    <div class="col p-0 text-start">
         <label className="text-muted small mb-1">
                Set Monthly Budget:
              </label>
              <input
                type="number"
                step="0.01"
                inputMode="decimal"
                className="form-control form-control-sm"
                style={{ width: '90%' }}
                placeholder="₹ e.g. 20000"
                value={budget}
                onChange={(e) => {
                  const v = e.target.value;
                  setBudget(v);
                  try {
                    localStorage.setItem("duewise_budget", v);
                  } catch {}
                }}
              />
    </div>
    <div class="col p-0 text-start">
      <label className="text-muted small mb-1">Range:</label>
              <select
                className="form-select form-select-sm"
                style={{ width: '90%' }}
                value={range}
                onChange={(e) => setRange(e.target.value)}
              >
                <option value="6m">Last 6 months</option>
                <option value="12m">Last 12 months</option>
                <option value="ytd">Year to date</option>
              </select>
    </div>
  </div>
</div>
            

            {loading && <p className="mb-0">Loading…</p>}
            <hr />
            {!loading && data && (
              <>
                {/* Stat Tiles */}
                <div className="insights-grid">
                  <div className="stat">
                    <small>Total</small>
                    <div className="k">{rupee(data.summary.total)}</div>
                  </div>
                  <div className="stat">
                    <small>Avg / month</small>
                    <div className="k">{rupee(data.summary.avg_month)}</div>
                  </div>
                  <div className="stat">
                    <small>Best month</small>
                    <div className="k">
                      {fmtYMpretty(data.summary.min_month?.ym)} ·{" "}
                      {rupee(data.summary.min_month?.total)}
                    </div>
                  </div>
                  <div className="stat">
                    <small>Peak month</small>
                    <div className="k">
                      {fmtYMpretty(data.summary.max_month?.ym)} ·{" "}
                      {rupee(data.summary.max_month?.total)}
                    </div>
                  </div>
                  {budget !== "" && (
                    <div className="stat col-12">
                      <small>Budget vs Avg</small>
                      <div
                        className={`k ${
                          overUnder > 0
                            ? "text-danger"
                            : overUnder < 0
                            ? "text-success"
                            : ""
                        }`}
                      >
                        {overUnder > 0
                          ? `Over by ${rupee(overUnder)}`
                          : overUnder < 0
                          ? `Under by ${rupee(Math.abs(overUnder))}`
                          : "On budget"}
                      </div>
                      <div
                        className="progress mt-2"
                        role="progressbar"
                        aria-label="Avg vs Budget"
                      >
                        <div
                          className={`progress-bar ${
                            overUnder > 0 ? "bg-danger" : "bg-success"
                          }`}
                          style={{
                            width: `${Math.min(
                              100,
                              (avgMonth / (Number(budget) || 1)) * 100
                            )}%`,
                          }}
                          title={`Avg ${rupee(avgMonth)} vs Budget ${rupee(
                            Number(budget || 0)
                          )}`}
                        />
                      </div>
                    </div>
                  )}
                </div>

                {/* Charts */}
                <div className="row g-3 mt-2">
                  <div className="col-12 col-lg-7">
                    <div className="card">
                      <div className="card-body">
                        <h6 className="card-title">Monthly Totals</h6>
                        <canvas ref={barCanvas} height="180"></canvas>
                      </div>
                    </div>
                  </div>
                  <div className="col-12 col-lg-5">
                    <div className="card">
                      <div className="card-body">
                        <h6 className="card-title">By Category</h6>
                        {data.categories?.length ? (
                          <canvas ref={pieCanvas} height="180"></canvas>
                        ) : (
                          <p className="text-muted m-0">No data.</p>
                        )}
                      </div>
                    </div>
                  </div>
                </div>

                {/* Category breakdown */}
                <div className="mt-3">
                  <h6 className="mb-2">Category Breakdown</h6>
                  {catWithPct.length ? (
                    <div className="list-group list-group-flush">
                      {catWithPct.map((c, i) => (
                        <div key={i} className="list-group-item py-2">
                          <div className="d-flex justify-content-between">
                            <strong className="me-2">{c.category}</strong>
                            <span className="fw-semibold">
                              {rupee(c.total)}
                            </span>
                          </div>
                          <div className="d-flex align-items-center gap-2">
                            <div
                              className="progress flex-grow-1"
                              style={{ height: 8 }}
                            >
                              <div
                                className="progress-bar"
                                style={{ width: `${c.pct.toFixed(2)}%` }}
                              />
                            </div>
                            <small className="text-muted">
                              {c.pct.toFixed(1)}%
                            </small>
                          </div>
                        </div>
                      ))}
                    </div>
                  ) : (
                    <p className="text-muted">No data.</p>
                  )}
                </div>

                {/* Top bills */}
                <div className="mt-3">
                  <h6>Top Bills (total in range)</h6>
                  {data.topBills?.length ? (
                    <ul className="list-group">
                      {data.topBills.map((t, i) => (
                        <li
                          className="list-group-item d-flex justify-content-between"
                          key={i}
                        >
                          <span className="text-truncate me-2">{t.title}</span>
                          <span className="fw-semibold">{rupee(t.total)}</span>
                        </li>
                      ))}
                    </ul>
                  ) : (
                    <p className="text-muted">No data.</p>
                  )}
                </div>
              </>
            )}
          </div>

          <div className="modal-footer">
            <button className="btn btn-secondary" data-bs-dismiss="modal">
              Close
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------------- Dashboard ---------------- */
/* ---------------- Dashboard (agenda-style month view) ---------------- */
/* ---------------- Dashboard (collapsible agenda-style) ---------------- */
function Dashboard({ month }) {
  const [data, setData] = React.useState({ paid: [], unpaid: [] });
  const [dueDayById, setDueDayById] = React.useState({}); // id -> true due day (1..31)
  const [payAmt, setPayAmt] = React.useState({});
  const [insightsBill, setInsightsBill] = React.useState(null);
  const [insightsOpen, setInsightsOpen] = React.useState(false);
  const [hidePaid, setHidePaid] = React.useState(false); // NEW: toggle to hide paid items

  // Quick Add (one-time for selected month)
  const [qa, setQa] = React.useState({
    open: false,
    title: "",
    amount: "",
    category: "Other",
    tags: "",
    markPaid: false,
    date: "",
  });
  const qaRef = React.useRef(null);

  // anchor for “today” (only used on month change)
  const todayRef = React.useRef(null);

  // ----- helpers -----
  const dayFromDateStr = (s) => {
    const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(s || ""));
    return m ? Number(m[3]) : null;
  };
  const daysInMonth = (ym) => {
    const [y, m] = ym.split("-").map(Number);
    return new Date(y, m, 0).getDate();
  };
  const weekdayShort = (Y, M, d) =>
    new Date(Y, M - 1, d).toLocaleDateString(undefined, { weekday: "short" });

  // ----- load dashboard + master list (for true due days) -----
  React.useEffect(() => {
    let alive = true;
    Promise.all([api.dashboard(month), api.billsList()]).then(
      ([dash, list]) => {
        if (!alive) return;
        setData(dash || { paid: [], unpaid: [] });
        const map = {};
        (list || []).forEach((b) => {
          const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(b.due_date || ""));
          map[b.id] = m ? Number(m[3]) : 1;
        });
        setDueDayById(map);
        setPayAmt({});
      }
    );
    return () => {
      alive = false;
    };
  }, [month]);

  const openInsights = (bill) => {
    setInsightsBill(bill);
    setInsightsOpen(true);
  };
  const closeInsights = () => {
    setInsightsOpen(false);
    setInsightsBill(null);
  };

  // optimistic move helper
  function moveBetween(state, id, fromKey, toKey) {
    const from = state[fromKey] || [];
    const to = state[toKey] || [];
    const idx = from.findIndex((x) => x.id === id);
    if (idx < 0) return state;
    const item = from[idx];
    return {
      ...state,
      [fromKey]: from.filter((_, i) => i !== idx),
      [toKey]: [{ ...item, __status: toKey.slice(0, -1) }, ...to],
    };
  }

  const markPaid = async (bill, overrideAmt) => {
    const displayAmt = getDisplayAmount(bill);
    const useAmt =
      overrideAmt !== undefined
        ? overrideAmt
        : isVariableBill(bill)
        ? payAmt[bill.id] ?? ""
        : displayAmt;

    if (useAmt === "" || isNaN(Number(useAmt))) {
      alert("Please enter a valid amount first.");
      return;
    }

    // optimistic
    setData((s) => moveBetween(s, bill.id, "unpaid", "paid"));
    try {
      await api.complete(bill.id, month, Number(useAmt));
      const fresh = await api.dashboard(month);
      setData(fresh || { paid: [], unpaid: [] });
      setPayAmt((s) => ({ ...s, [bill.id]: "" })); // harmless now, but keeps things tidy
    } catch {
      setData((s) => moveBetween(s, bill.id, "paid", "unpaid"));
      alert("Failed to mark as paid.");
    }
  };

  const unmarkPaid = async (id) => {
    // optimistic
    setData((s) => moveBetween(s, id, "paid", "unpaid"));
    try {
      await api.uncomplete(id, month);
      const fresh = await api.dashboard(month);
      setData(fresh || { paid: [], unpaid: [] });
    } catch {
      setData((s) => moveBetween(s, id, "unpaid", "paid"));
      alert("Failed to move back to pending.");
    }
  };

  // ------- Build "agenda" data: day -> bills[] (paid + unpaid) -------
  function groupByDay() {
    const map = {};
    const all = [
      ...(data.unpaid || []).map((b) => ({ ...b, __status: "unpaid" })),
      ...(data.paid || []).map((b) => ({ ...b, __status: "paid" })),
    ];
    all.forEach((b) => {
      const day = dueDayById[b.id] ?? dayFromDateStr(b.due_date) ?? 1;
      (map[day] ||= []).push(b);
    });
    // sort: unpaid first, then by title
    Object.values(map).forEach((list) => {
      list.sort((a, b) => {
        if (a.__status !== b.__status) return a.__status === "unpaid" ? -1 : 1;
        return String(a.title || "").localeCompare(String(b.title || ""));
      });
    });
    return map;
  }
  const byDay = groupByDay();

  // Scroll to today ONLY on month change (not on every data change)
  React.useEffect(() => {
    const [y, m] = month.split("-").map(Number);
    const now = new Date();
    if (now.getFullYear() === y && now.getMonth() + 1 === m) {
      setTimeout(() => {
        const el = todayRef.current;
        if (!el) return;
        const targetY = window.scrollY + el.getBoundingClientRect().top - 240; // leave 40px from top
        window.scrollTo({ top: targetY, behavior: "smooth" });
      }, 80);
    }
  }, [month]);

  // helpers for day loop
  const [Y, M] = month.split("-").map(Number);
  const totalDays = daysInMonth(month);
  const now = new Date();
  const isCurrentMonth = now.getFullYear() === Y && now.getMonth() + 1 === M;
  const todayNum = isCurrentMonth ? now.getDate() : -1;

  // ----- Single bill row (compact) -----
  const BillRow = React.memo(function BillRow({ b }) {
    console.log(b);
    const variable = isVariableBill(b);
    const displayAmt = getDisplayAmount(b);
    const completed = b.__status === "paid";

    // local input state so typing doesn't touch parent & cause remounts
    const [localAmt, setLocalAmt] = React.useState("");

    // if you want to prefill from parent occasionally, keep this:
    React.useEffect(() => {
      setLocalAmt(payAmt[b.id] ?? "");
    }, [b.id]); // keep very narrow deps so it doesn't fight typing

    const handleMarkPaid = async () => {
      await markPaid(b, variable ? localAmt : undefined);
      if (variable) setLocalAmt(""); // clear after success
    };

    return (
      <div
        className={`card bill-card mb-3 overflow-hidden ${
          completed ? "completed" : ""
        }`}
        onClick={() => openInsights(b)}
      >
        <div className="card-body p-0">
          {/* Row 1: Title + actions */}
          <div className="d-flex align-items-start justify-content-between pt-3 pe-3 ps-3">
            <div className="fs-4 fw-semibold text-truncate pe-2">{b.title}</div>

            <div
              className="d-flex align-items-center gap-2"
              onClick={(e) => e.stopPropagation()}
            >
              {completed ? (
                <button
                  className="btn btn-outline-secondary btn-sm check"
                  onClick={() => unmarkPaid(b.id)}
                  title="Move back to Pending"
                  aria-label="Move back to Pending"
                >
                  <i class="bi bi-check-square-fill"></i>
                </button>
              ) : (
                <>
                  {variable ? (
                    <>
                      <button
                        className="btn btn-outline-secondary btn-sm uncheck"
                        onClick={handleMarkPaid}
                        disabled={!localAmt}
                        aria-label="Mark paid"
                      >
                        <i className="d-none bi bi-check-lg"></i>
                      </button>
                    </>
                  ) : (
                    <>
                      <div className="amount text-primary d-none d-sm-block">
                        {fmtAmt(displayAmt)}
                      </div>
                      <button
                        className="btn btn-outline-secondary btn-sm uncheck"
                        onClick={handleMarkPaid}
                        aria-label="Mark paid"
                      >
                        <i className="d-none bi bi-check-lg"></i>
                      </button>
                    </>
                  )}
                </>
              )}
            </div>
          </div>

          {/* Amount + date */}
          <div className="d-flex flex-column align-items-start justify-content-between pe-3 ps-3">
            <div className="fs-5 mb-3" onClick={(e) => e.stopPropagation()}>
              {!completed && variable ? (
                <input
                  type="number"
                  step="0.01"
                  inputMode="decimal"
                  className="form-control form-control-sm"
                  style={{ width: 110 }}
                  placeholder="Enter Amount"
                  value={localAmt}
                  onChange={(e) => setLocalAmt(e.target.value)}
                  onMouseDown={(e) => e.stopPropagation()}
                  onTouchStart={(e) => e.stopPropagation()}
                />
              ) : (
                fmtAmt(displayAmt)
              )}

              {/* Optional tags */}
              {b.tags ? (
                <div className="fs-6 fst-italic text-muted">{b.tags}</div>
              ) : null}
            </div>
            {b.paid_at ? (
              <div className="fs-6 text-muted mb-3">
                <span className="badge border border-info-subtle">
                  {" "}
                  Paid on :{new Date(b.paid_at).toLocaleString() || " "}
                </span>
              </div>
            ) : undefined}
          </div>

          {/* Footer: category + recurring */}
          <div className="d-flex flex-wrap align-items-center gap-2 small p-2 border-top">
            <span className="badge border ">{b.category || "-"}</span>
            <span className="badge border ">
              {b.recurring == "6months" ? "Half-Yearly" : b.recurring}
            </span>
          </div>
        </div>
      </div>
    );
  });

  // ----- Day block (always open, sticky header like calendar agenda) -----
  function DayBlock({ d, items, wday, isToday }) {
    return (
      <div id={`day-${d}`} className="mb-3">
        <div
          ref={isToday ? todayRef : null}
          className="d-flex align-items-center"
          style={{
            position: "sticky",
            top: 64, // adjust if your topbar height differs
            zIndex: 1,
            background: "var(--bs-body-bg, #fff)",
            padding: "6px 8px",
          }}
        >
          <div className="fw-bold">
            {d} {wday}
          </div>
          {isToday && (
            <span
              className="badge ms-2 bg-primary"
              style={{ color: "black !important" }}
            >
              Today
            </span>
          )}
          <span className="ms-auto badge rounded-pill border text-secondary">
            {items.length}
          </span>
        </div>

        <div className="pt-2">
          {items.length ? (
            items.map((b) => <BillRow key={`${b.__status}-${b.id}`} b={b} />)
          ) : (
            <div className="text-muted small" style={{ padding: "6px 2px" }}>
              No bills.
            </div>
          )}
        </div>
      </div>
    );
  }

  // ---- Quick Add ----
  const openQuickAdd = () => {
    const [Y, M] = month.split("-").map(Number);
    const today = new Date();
    const isThisMonth = today.getFullYear() === Y && today.getMonth() + 1 === M;
    const defaultDay = isThisMonth ? today.getDate() : 1;
    const dd = String(defaultDay).padStart(2, "0");
    const mm = String(M).padStart(2, "0");
    const defaultDate = `${Y}-${mm}-${dd}`;

    setQa({
      open: true,
      title: "",
      amount: "",
      category: "Other",
      markPaid: false,
      date: defaultDate,
    });
    ensureBootstrapModal(qaRef.current).show();
    cleanupBackdrops();
  };

  const saveQuickAdd = async () => {
    if (!qa.title) {
      alert("Title is required");
      return;
    }
    const due =
      qa.date ||
      (() => {
        const d = ymParse(month);
        return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(
          2,
          "0"
        )}-01`;
      })();

    const res = await api.billCreate({
      title: qa.title,
      amount: qa.amount === "" ? null : Number(qa.amount),
      due_date: due,
      recurring: "once",
      category: qa.category || "Other",
      tags: "",
      comments: "",
    });

    if (qa.markPaid && res?.id) {
      await api.complete(
        res.id,
        month,
        qa.amount === "" ? undefined : Number(qa.amount)
      );
    }

    ensureBootstrapModal(qaRef.current).hide();
    cleanupBackdrops();
    setQa({
      open: false,
      title: "",
      amount: "",
      category: "Other",
      markPaid: false,
      date: "",
    });

    const fresh = await api.dashboard(month);
    setData(fresh || { paid: [], unpaid: [] });
  };

  // ---- render ----
  const [Y2, M2] = month.split("-").map(Number);

  return (
    <div className="mt-3 px-3 py-3 has-fab-padding">
      {/* Header + toggle */}
      <div className="d-flex align-items-center justify-content-between mb-2">
        <div className="section-title m-0"></div>
        <div className="form-check form-switch hidePaidSwitch">
          <input
            className="form-check-input"
            type="checkbox"
            id="hidePaidSwitch"
            checked={hidePaid}
            onChange={(e) => setHidePaid(e.target.checked)}
          />
          <label
            className="form-check-label fw-normal text-secondary text-capitalize"
            htmlFor="hidePaidSwitch"
          >
            Hide paid
          </label>
        </div>
      </div>

      {/* Agenda-style month view (always open) */}
      {Array.from({ length: daysInMonth(month) }, (_, i) => i + 1).map((d) => {
        const allItems = byDay[d] || [];
        const shown = hidePaid
          ? allItems.filter((b) => b.__status !== "paid")
          : allItems;
        if (hidePaid && shown.length === 0) return null; // hide empty day when hiding paid
        const wday = weekdayShort(Y2, M2, d);
        const now = new Date();
        const isToday =
          now.getFullYear() === Y2 &&
          now.getMonth() + 1 === M2 &&
          now.getDate() === d;
        return (
          <DayBlock key={d} d={d} items={shown} wday={wday} isToday={isToday} />
        );
      })}

      {/* Bill-level Insights */}
      <InsightsModal
        bill={insightsBill}
        open={insightsOpen}
        onClose={closeInsights}
      />

      {/* FAB */}
      <div className="fab">
        <button
          className="btn btn-primary"
          title="Add one-time expense"
          aria-label="Add one-time expense"
          onClick={openQuickAdd}
        >
          <i className="bi bi-plus-lg"></i>
        </button>
      </div>

      {/* Quick Add Modal */}
      <div className="modal fade" id="quickAddModal" tabIndex="-1" ref={qaRef}>
        <div className="modal-dialog">
          <div className="modal-content dw-modal-elevated">
            <div className="modal-header">
              <div>
                <h5 className="modal-title mb-0">Add One-time Expense</h5>
                <small className="text-muted">
                  For{" "}
                  {ymParse(month).toLocaleString(undefined, {
                    month: "short",
                    year: "numeric",
                  })}{" "}
                  only
                </small>
              </div>
              <button
                className="btn-close"
                data-bs-dismiss="modal"
                onClick={cleanupBackdrops}
              ></button>
            </div>
            <div className="modal-body">
              <div className="mb-3">
                <label className="form-label">Title</label>
                <input
                  className="form-control"
                  value={qa.title}
                  onChange={(e) => setQa({ ...qa, title: e.target.value })}
                  placeholder="e.g., Festival shopping"
                />
              </div>

              <div className="mb-3">
                <label className="form-label">Amount</label>
                <div className="input-group">
                  <span className="input-group-text">₹</span>
                  <input
                    type="number"
                    step="0.01"
                    inputMode="decimal"
                    className="form-control"
                    value={qa.amount}
                    onChange={(e) => setQa({ ...qa, amount: e.target.value })}
                    placeholder="Optional (leave blank if unknown now)"
                  />
                </div>
                <div className="form-hint">
                  If blank: you can enter it when marking as Paid.
                </div>
              </div>

              <div className="mb-3">
                <label className="form-label">Category</label>
                <select
                  className="form-select"
                  value={qa.category}
                  onChange={(e) => setQa({ ...qa, category: e.target.value })}
                >
                  {CATEGORIES.map((c) => (
                    <option key={c} value={c}>
                      {c}
                    </option>
                  ))}
                </select>
              </div>

              <div className="mb-3">
                <label className="form-label">Date (this month)</label>
                <input
                  type="date"
                  className="form-control"
                  value={qa.date}
                  min={`${month}-01`}
                  max={`${month}-${String(daysInMonth(month)).padStart(
                    2,
                    "0"
                  )}`}
                  onChange={(e) => setQa({ ...qa, date: e.target.value })}
                />
                <div className="form-hint">
                  This one-time expense will appear on the selected day.
                </div>
              </div>

              <div className="form-check">
                <input
                  id="qaPaid"
                  className="form-check-input"
                  type="checkbox"
                  checked={qa.markPaid}
                  onChange={(e) => setQa({ ...qa, markPaid: e.target.checked })}
                />
                <label className="form-label" htmlFor="qaPaid">
                  Mark as paid now
                </label>
              </div>
            </div>

            <div className="modal-footer">
              <button
                className="btn btn-outline-secondary"
                data-bs-dismiss="modal"
                onClick={cleanupBackdrops}
              >
                Cancel
              </button>
              <button className="btn btn-primary" onClick={saveQuickAdd}>
                Add
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------------- Master List ---------------- */
function MasterList() {
  const [rows, setRows] = useState([]);
  const [loading, setLoading] = useState(true);
  const [form, setForm] = useState({
    id: null,
    title: "",
    amount: "",
    due_date: "",
    recurring: "monthly",
    category: "",
    selectedCategory: "Other",
    customCategory: "",
    tags: "",
    comments: "",
  });

  // NEW: filter state
  const [filter, setFilter] = useState("all"); // 'all' | 'once' | 'monthly' | '6months' | 'yearly'

  const load = () =>
    api.billsList().then((d) => {
      setRows(d || []);
      setLoading(false);
    });
  useEffect(() => {
    load();
  }, []);

  const openAdd = () => {
    setForm({
      id: null,
      title: "",
      amount: "",
      due_date: "",
      recurring: "monthly",
      category: "",
      selectedCategory: "Other",
      customCategory: "",
      tags: "",
      comments: "",
    });
    cleanupBackdrops();
    // eslint-disable-next-line no-undef
    new bootstrap.Modal(document.getElementById("billModal")).show();
  };
  const openEdit = (b) => {
    const inList = CATEGORIES.includes(b.category);
    setForm({
      id: b.id,
      amount: b.default_amount ?? "",
      title: b.title,
      due_date: b.due_date,
      recurring: b.recurring,
      selectedCategory: inList ? b.category : "Other",
      customCategory: inList ? "" : b.category || "",
      category: b.category || "",
      tags: b.tags || "",
      comments: b.comments || "",
    });
    cleanupBackdrops();
    // eslint-disable-next-line no-undef
    new bootstrap.Modal(document.getElementById("billModal")).show();
  };

  const save = async () => {
      console.log(form);
    const categoryToSave =
      form.selectedCategory === "Other"
        ? form.customCategory || ""
        : form.selectedCategory;
    const amt =
      form.amount === "" || form.amount === null || form.amount === undefined
        ? null
        : parseFloat(form.amount);
    const data = { ...form, category: categoryToSave, amount: amt };
    if (!data.title || !data.due_date)
      return alert("Title and Due Date are required");
    if (data.id) await api.billUpdate(data.id, data);
    else await api.billCreate(data);
    // eslint-disable-next-line no-undef
    bootstrap.Modal.getInstance(document.getElementById("billModal")).hide();
    cleanupBackdrops();
    load();
  };

  const remove = async (id) => {
    if (confirm("Delete this bill?")) {
      await api.billDelete(id);
      load();
    }
  };

  // --- NEW: normalize recurring and compute counts + filtered rows ---
  const recKey = (r) => {
    const rr = String(r || "")
      .toLowerCase()
      .replace(/\s+/g, "");
    if (
      rr === "once" ||
      rr === "onetime" ||
      rr === "one-time" ||
      rr === "one_time" ||
      rr == ""
    )
      return "once";
    if (rr === "monthly") return "monthly";
    if (
      rr === "6months" ||
      rr === "sixmonths" ||
      rr === "semiannual" ||
      rr === "semiannually" ||
      rr === "semi-annual" ||
      rr === "semi-annually"
    )
      return "6months";
    if (rr === "yearly" || rr === "annual" || rr === "annually")
      return "yearly";
    return "other";
  };

  const counts = React.useMemo(() => {
    const c = {
      all: rows.length,
      once: 0,
      monthly: 0,
      "6months": 0,
      yearly: 0,
    };
    rows.forEach((b) => {
      const k = recKey(b.recurring);
      if (k in c) c[k]++;
    });
    return c;
  }, [rows]);

  const filteredRows = React.useMemo(() => {
    if (filter === "all") return rows;
    return rows.filter((b) => recKey(b.recurring) === filter);
  }, [rows, filter]);

  const labelOf = (k) =>
    ({
      all: "All",
      once: "Once",
      monthly: "Monthly",
      "6months": "Half-Yearly",
      yearly: "Yearly",
    }[k]);

  return (
    <>
      <div className="d-flex justify-content-between align-items-center px-3 py-3 mt-3">
        <h5 className="m-0 fw-bold">Master List</h5>
        <button className="btn btn-dark" onClick={openAdd}>
          <i className="bi bi-plus-lg me-1"></i>Add Bill
        </button>
      </div>

      {/* NEW: Filter bar */}
      <div className="px-3 mb-2">
        <div
          className="btn-group flex-wrap"
          role="group"
          aria-label="Recurring filter"
        >
          {["all", "once", "monthly", "6months", "yearly"].map((k) => (
            <button
              key={k}
              className={`btn btn-sm ${
                filter === k ? "btn-dark" : "btn-outline-secondary"
              }`}
              onClick={() => setFilter(k)}
              style={{ marginRight: 0, marginBottom: 6 }}
            >
              {labelOf(k)}{" "}
              <span className="badge rounded-pill bg-secondary-subtle text-secondary ms-1">
                {counts[k]}
              </span>
            </button>
          ))}
        </div>
      </div>

      {loading ? (
        <p className="mt-3">Loading…</p>
      ) : filteredRows.length ? (
        filteredRows.map((b) => {
          const rShort = (rec) => {
            const k = String(rec || "").toLowerCase();
            if (k === "monthly") return "m";
            if (k === "6months") return "6m";
            if (k === "yearly") return "y";
            if (k === "once") return "1x";
            return k || "-";
          };

          return (
            <div
              className="card bill-card mx-3 my-3 mb-3 overflow-hidden"
              key={b.id}
            >
              <div className="card-body p-0">
                {/* Row 1: Title + actions */}
                <div className="d-flex align-items-start justify-content-between pt-3 pe-3 ps-3">
                  <div className="fs-4 fw-semibold text-truncate pe-2">
                    {b.title}
                  </div>
                  <div className="d-flex align-items-center gap-2">
                    <button
                      className="btn btn-outline-primary btn-sm"
                      onClick={() => openEdit(b)}
                      aria-label="Edit"
                      title="Edit"
                    >
                      <i className="bi bi-pencil"></i>
                    </button>
                    <button
                      className="btn btn-outline-danger btn-sm"
                      onClick={() => remove(b.id)}
                      aria-label="Delete"
                      title="Delete"
                    >
                      <i className="bi bi-trash"></i>
                    </button>
                  </div>
                </div>

                <div className="d-flex flex-column align-items-start justify-content-between pe-3 ps-3">
                  <div className="fs-5">
                    {" "}
                    <i class="bi bi-currency-rupee"></i>
                    {fmtAmt(b.default_amount)}
                  </div>
                  <div className="fs-6 text-muted mb-3">
                    <span className="badge border border-dark-subtle">
                      Due date: {b.due_date || "-"}
                    </span>
                  </div>
                </div>

                {/* Row 2: Amount */}

                {b.tags ? (
                  <span className="text-muted ps-3 pe-3 pb-3">
                    {b.tags || ""}
                  </span>
                ) : undefined}

                {/* Divider */}
                <hr className="d-none mb-3 " />

                {/* Row 3: (recurring) (category) (date) */}
                <div className="d-flex flex-wrap align-items-center gap-2 small p-2 border-top">
                  <span className="badge border">{b.category || "-"}</span>
                  <span className="badge border">
                    {b.recurring == "6months" ? "Half-Yearly" : b.recurring}
                  </span>
                </div>
              </div>
            </div>
          );
        })
      ) : (
        <p className="text-muted mt-3 px-3">
          No bills for <b>{labelOf(filter)}</b>.
        </p>
      )}

      {/* Add/Edit modal */}
      <div
        className="modal fade"
        id="billModal"
        tabIndex="-1"
        data-bs-backdrop="static"
        data-bs-keyboard="false"
      >
        <div className="modal-dialog">
          <div className="modal-content dw-modal-elevated">
            {/* Header */}
            <div className="modal-header">
              <div>
                <h5 className="modal-title mb-0">
                  {form.id ? "Edit Bill" : "Add Bill"}
                </h5>
                <small className="text-muted">
                  Keep amount blank for variable bills (e.g., electricity).
                </small>
              </div>
              <button
                type="button"
                className="btn-close"
                data-bs-dismiss="modal"
                aria-label="Close"
                onClick={cleanupBackdrops}
              ></button>
            </div>

            {/* Body */}
            <div className="modal-body">
              {/* Title */}
              <div className="mb-3">
                <label className="form-label">Title</label>
                <input
                  className="form-control"
                  placeholder="Netflix, Electricity, Home EMI…"
                  value={form.title}
                  onChange={(e) => setForm({ ...form, title: e.target.value })}
                />
              </div>

              {/* Amount + Date */}
              <div className="row g-3">
                <div className="col-12">
                  <label className="form-label d-flex align-items-center justify-content-between">
                    <span>Amount</span>
                    <span className="badge badge-soft ms-2">
                      {form.amount === "" || form.amount === null
                        ? "Variable"
                        : "Fixed"}
                    </span>
                  </label>
                  <div className="input-group">
                    <span className="input-group-text">₹</span>
                    <input
                      type="number"
                      step="0.01"
                      inputMode="decimal"
                      className="form-control"
                      placeholder="Leave blank if variable"
                      value={form.amount}
                      onChange={(e) =>
                        setForm({ ...form, amount: e.target.value })
                      }
                    />
                  </div>
                  <div className="form-hint">
                    Leave empty to enter the exact amount each month when
                    paying.
                  </div>
                </div>

                <div className="col-12">
                  <label className="form-label">Start / Due Date</label>
                  <div className="input-group">
                    <span className="input-group-text">
                      <i className="bi bi-calendar-date"></i>
                    </span>
                    <input
                      type="date"
                      className="form-control"
                      value={form.due_date}
                      onChange={(e) =>
                        setForm({ ...form, due_date: e.target.value })
                      }
                    />
                  </div>
                </div>
              </div>

              {/* Recurrence */}
              <div className="mt-3">
                <label className="form-label">Recurring</label>
                <select
                  className="form-select"
                  value={form.recurring}
                  onChange={(e) =>
                    setForm({ ...form, recurring: e.target.value })
                  }
                >
                  <option value="once">Once</option>
                  <option value="monthly">Monthly</option>
                  <option value="6months">Half-Yearly</option>
                  <option value="yearly">Yearly</option>
                </select>
                <div className="form-hint">
                  We’ll auto-show it on the dashboard when it’s due.
                </div>
              </div>

              {/* Category */}
              <div className="mt-3">
                <label className="form-label">Category</label>
                <select
                  className="form-select"
                  value={form.selectedCategory}
                  onChange={(e) => {
                    const val = e.target.value;
                    setForm((f) => ({
                      ...f,
                      selectedCategory: val,
                      customCategory: val === "Other" ? f.customCategory : "",
                    }));
                  }}
                >
                  {CATEGORIES.map((c) => (
                    <option key={c} value={c}>
                      {c}
                    </option>
                  ))}
                </select>

                {form.selectedCategory === "Other" && (
                  <div className="mt-2">
                    <label className="form-label">Custom Category</label>
                    <input
                      className="form-control"
                      placeholder="e.g., SaaS/Tools"
                      value={form.customCategory}
                      onChange={(e) =>
                        setForm({ ...form, customCategory: e.target.value })
                      }
                    />
                  </div>
                )}
              </div>

              {/* Tags + Comments */}
              <div className="mt-3">
                <label className="form-label">Tags</label>
                <input
                  className="form-control"
                  placeholder="comma, separated"
                  value={form.tags}
                  onChange={(e) => setForm({ ...form, tags: e.target.value })}
                />
                <div className="form-hint">
                  Optional — helps search & filters.
                </div>
              </div>

              <div className="mt-3">
                <label className="form-label">Comments</label>
                <textarea
                  className="form-control"
                  rows="2"
                  placeholder="Notes or account details (optional)"
                  value={form.comments}
                  onChange={(e) =>
                    setForm({ ...form, comments: e.target.value })
                  }
                ></textarea>
              </div>
            </div>

            {/* Footer */}
            <div className="modal-footer">
              <button
                className="btn btn-outline-secondary"
                data-bs-dismiss="modal"
                onClick={cleanupBackdrops}
              >
                Cancel
              </button>
              <button className="btn btn-primary" onClick={save}>
                {form.id ? "Save Changes" : "Add Bill"}
              </button>
            </div>
          </div>
        </div>
      </div>
    </>
  );
}

/* ---------------- Auth & App ---------------- */
function Auth({ onAuthed }) {
  const [mode, setMode] = useState("login");
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [err, setErr] = useState("");
  async function submit() {
    setErr("");
    if (mode === "login") {
      const r = await auth.login(email, password);
      if (r.ok) onAuthed();
      else setErr(r.error || "Login failed");
    } else {
      const r = await auth.register(name, email, password);
      if (r.ok) {
        const r2 = await auth.login(email, password);
        if (r2.ok) onAuthed();
        else setErr(r2.error || "Login failed");
      } else setErr(r.error || "Register failed");
    }
  }
 return (
  <div className="auth-hero d-flex align-items-center justify-content-center">
    <div className="w-100" style={{ maxWidth: 420 }}>
      {/* Logo + Title */}
      <div className="text-center mb-3">
        <div className="dw-splash-logo d-inline-flex align-items-center justify-content-center">
          <svg viewBox="0 0 316 316" width="64" height="64" aria-hidden="true">
            <path d="M111.94.28c32.86-2.55,73.97,12.56,95.08,38.3,45.2,55.12,41.03,148.62-4.04,202.64-39.24,47.03-92.14,57.57-146.6,79.2-5.4.85-10.29-3.02-14.84-5.61-11.1-6.31-26.04-14.72-36.21-22.03-2.14-1.54-4.37-3.28-4.97-6.01-.17-51.39.11-102.79,0-154.18-.06-26.55-.77-53.1,0-79.68.2-6.82-2.54-17.72,5.45-20.32C35.51,24.45,64.81,11.82,94.37,3.72c5.66-1.55,11.69-2.98,17.57-3.44ZM59.53,308.99l1.82-.33c50.83-20.55,102.88-31.41,137.77-77.53,49.44-65.35,41.02-191.67-48.79-217.02-23.11-6.53-36.98-5.56-59.66,1.17-24.04,7.13-47.65,15.99-71.72,23.05-.26,1.14.21.77.62,1.06,10.73,7.46,25.1,12.75,36.93,18.38,23.26-5.41,48.2-18.78,72.48-16.1,38.99,4.31,62.5,36.62,66.6,73.75,6.09,55.21-13.16,102.37-62.52,129.37-22.68,12.41-49.02,19.45-73.53,27.44v36.76ZM49.98,65.54L9.88,45.96v237.49l2.91,2.35,37.19,22.24V65.54ZM59.53,261.74c.47.47,10.35-2.73,11.82-3.21,48.13-15.61,90.96-31.08,109.08-82.82,10.21-29.18,10.04-77.32-11.12-101.8-18.22-21.08-39.84-27.01-66.92-20.17-14.29,3.61-28.13,8.99-42.36,12.77l-.5,195.22Z"/>
          </svg>
        </div>
        <h4 className="brand mt-3 mb-0 d-block">Duewise <span className="text-muted">v1.0</span></h4>
      </div>

      {/* Card */}
      <div className="card bill-card auth-card shadow-lg rounded-4 border-0">
        <div className="card-body p-4">
          {mode === "register" && (
            <div className="mb-3">
              <label className="form-label small text-muted">Name</label>
              <input
                className="form-control"
                placeholder="Your name"
                value={name}
                onChange={(e) => setName(e.target.value)}
              />
            </div>
          )}

          <div className="mb-3">
            <label className="form-label small text-muted">Email</label>
            <input
              className="form-control"
              placeholder="you@example.com"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              inputMode="email"
              autoComplete="email"
            />
          </div>

          <div className="mb-3">
            <label className="form-label small text-muted">Password</label>
            <input
              className="form-control"
              placeholder="••••••••"
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              autoComplete={mode === 'login' ? 'current-password' : 'new-password'}
            />
          </div>

          {err && <div className="alert alert-danger py-2">{err}</div>}

          <button className="btn btn-dark w-100 mb-2" onClick={submit}>
            {mode === "login" ? "Login" : "Create account"}
          </button>
          <button
            className="btn btn-outline-secondary w-100"
            onClick={() => setMode(mode === "login" ? "register" : "login")}
          >
            {mode === "login"
              ? "Need an account? Register"
              : "Have an account? Login"}
          </button>
        </div>
      </div>

      {/* Tiny footer note */}
      <p className="text-center text-muted small mt-3 mb-0">
        Secure • Private • Local-first
      </p>
    </div>
  </div>
);

}

function App() {
  const [authed, setAuthed] = useState(false);
  const [view, setView] = useState("dashboard");
  const [month, setMonth] = useState(ymFormat(new Date()));
  const [globalOpen, setGlobalOpen] = useState(false);

  const todayYM = ymFormat(new Date());
  const isToday = month === todayYM;

  useEffect(() => {
    auth.me().then((u) => setAuthed(!!u.id));
  }, []);
  if (!authed) return <Auth onAuthed={() => setAuthed(true)} />;

  return (
    <>
      <TopBar
        view={view}
        setView={setView}
        onLogout={() => auth.logout().then(() => location.reload())}
        onRefresh={refreshApp}
        onToday={() => setMonth(ymFormat(new Date()))}
        showToday={!isToday}
        onOpenGlobal={() => setGlobalOpen(true)}
        month={month}
        setMonth={setMonth}
      />

      <div className=" py-3 pt-0">
        {view === "dashboard" && (
          <>
            <Dashboard month={month} />
          </>
        )}
        {view === "master" && <MasterList />}
      </div>

      {/* Global Insights Modal */}
      <GlobalInsightsModal
        open={globalOpen}
        onClose={() => setGlobalOpen(false)}
      />
    </>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);

// After render line:
const splash = document.getElementById("dw-splash");
if (splash) splash.style.display = "none";
