import { useEffect, useMemo, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { useDashboard, type DashboardResponse } from "../hooks/useDashboard";
import { Money } from "../components/ui";
import PercentGuard from "../components/PercentGuard";
import MonthlyTrendChart from "../components/charts/MonthlyTrendChart";
import FixedFundingBars, {
  type FixedItem,
} from "../components/charts/FixedFundingBars";
import VariableAllocationDonut, {
  type VariableSlice,
} from "../components/charts/VariableAllocationDonut";
import { useAuthSession } from "../hooks/useAuthSession";
import { fixedPlansApi } from "../api/fixedPlans";
import { useToast } from "../components/Toast";
import PaydayOverlay from "../components/PaydayOverlay";
import PaymentReconciliationModal from "../components/PaymentReconciliationModal";
import FundingConfirmationModal from "../components/FundingConfirmationModal";
import EarlyPaymentPromptModal from "../components/EarlyPaymentPromptModal";
import { isoToDateString, dateStringToUTCMidnight, getTodayInTimezone, getBrowserTimezone, formatDateInTimezone } from "../utils/timezone";

export default function DashboardPage() {
  const session = useAuthSession();
  const shouldLoadDashboard = !!session.data?.userId;
  const trendMonths = 6;
  const [trendPage, setTrendPage] = useState(0);
  const { data, isLoading, isError, isFetching, error, refetch } = useDashboard(
    shouldLoadDashboard,
    {
      trendPage,
      trendMonths,
    }
  );
  const navigate = useNavigate();
  const { push } = useToast();

  const [activeTab, setActiveTab] = useState<
    "overview" | "variable" | "fixed" | "trend"
  >("overview");
  const [isDesktop, setIsDesktop] = useState(() => {
    if (typeof window === "undefined") return true;
    return window.matchMedia("(min-width: 1024px)").matches;
  });

  // Get user timezone from dashboard data
  const userTimezone = data?.user?.timezone || getBrowserTimezone();
  const incomeType = data?.user?.incomeType ?? "regular";
  const incomeFrequency = data?.user?.incomeFrequency;
  const firstIncomeDate = data?.user?.firstIncomeDate ?? null;

  const variableSlices: VariableSlice[] = useMemo(() => {
    if (!data) return [];
    return data.variableCategories.map((c) => ({
      name: c.name,
      value: c.percent,
      isSavings: !!c.isSavings,
    }));
  }, [data]);

  const fixedChartData: FixedItem[] = useMemo(() => {
    if (!data) return [];
    return data.fixedPlans.map((plan) => {
      const total = plan.totalCents ?? 0;
      const funded = Math.min(plan.fundedCents ?? 0, total);
      const remaining = Math.max(total - funded, 0);
      const aheadCents = getFundingAhead(plan) ?? 0;
      
      // Calculate percentages for the chart (0-100 scale)
      const fundedPercent = total > 0 ? (funded / total) * 100 : 0;
      const remainingPercent = total > 0 ? (remaining / total) * 100 : 0;
      
      return {
        name: plan.name,
        funded: fundedPercent,
        remaining: remainingPercent,
        fundedCents: funded,
        remainingCents: remaining,
        aheadCents,
        isOverdue: (plan as { isOverdue?: boolean }).isOverdue ?? false,
        dueOn: plan.dueOn,
      };
    });
  }, [data]);

  // Due-today overlay state
  const [dueTodayPlan, setDueTodayPlan] = useState<
    | { id: string; name: string; dueOn: string; remainingCents: number }
    | null
  >(null);
  const [overrideDueISO, setOverrideDueISO] = useState<string>("");

  useEffect(() => {
    if (typeof window === "undefined") return;
    const media = window.matchMedia("(min-width: 1024px)");
    const update = () => setIsDesktop(media.matches);
    update();
    media.addEventListener("change", update);
    return () => {
      media.removeEventListener("change", update);
    };
  }, []);
  useEffect(() => {
    if (!isDesktop && activeTab === "overview") {
      setActiveTab("variable");
    }
  }, [isDesktop, activeTab]);
  const [showDueOverlay, setShowDueOverlay] = useState(false);
  const [isSubmittingDue, setIsSubmittingDue] = useState(false);
  const [dueQueue, setDueQueue] = useState<
    Array<{ id: string; name: string; dueOn: string; remainingCents: number }>
  >([]);
  const [queueIndex, setQueueIndex] = useState(0);
  const savingsCategories = useMemo(
    () => (data?.variableCategories || []).filter((c) => c.isSavings),
    [data]
  );
  const savingsReserveById = useMemo(() => {
    if (!data) return new Map<string, number>();
    const reserveTotal = data.fixedPlans.reduce(
      (sum, plan) => sum + (plan.fundedCents || 0),
      0
    );
    const savingsTotalPercent = savingsCategories.reduce(
      (sum, c) => sum + (c.percent || 0),
      0
    );
    const shares = savingsCategories.map((c) => {
      const raw = savingsTotalPercent
        ? (reserveTotal * (c.percent || 0)) / savingsTotalPercent
        : 0;
      const floored = Math.floor(raw);
      return { id: c.id, share: floored, frac: raw - floored };
    });

    let remainder =
      reserveTotal - shares.reduce((sum, item) => sum + item.share, 0);
    shares
      .slice()
      .sort((a, b) => b.frac - a.frac)
      .forEach((item) => {
        if (remainder > 0) {
          item.share += 1;
          remainder -= 1;
        }
      });

    return new Map(shares.map((item) => [item.id, item.share]));
  }, [data, savingsCategories]);
  const [fundingSource, setFundingSource] = useState<"savings" | "deficit">(
    "savings"
  );
  const [savingsCategoryId, setSavingsCategoryId] = useState<string | null>(
    null
  );
  const [hasCheckedDueToday, setHasCheckedDueToday] = useState(false);
  
  // Funding confirmation modal state
  const [showFundingConfirmation, setShowFundingConfirmation] = useState(false);
  const [fundingData, setFundingData] = useState<{
    planId: string;
    planName: string;
    totalCents: number;
    fundedCents: number;
    availableBudget: number;
  } | null>(null);

  // Payment reconciliation modal state
  const [showPaymentReconciliation, setShowPaymentReconciliation] = useState(false);
  const [reconciliationData, setReconciliationData] = useState<{
    planId: string;
    planName: string;
    totalCents: number;
    fundedCents: number;
    isOverdue: boolean;
    overdueAmount?: number;
    message: string;
    nextDueDate: string;
  } | null>(null);

  const [earlyPaymentPrompt, setEarlyPaymentPrompt] = useState<{
    planId: string;
    planName: string;
    dueOn: string;
  } | null>(null);
  
  const openDueItem = async (item: { id: string; name: string; dueOn: string }) => {
    const planDetail = data?.fixedPlans.find((p) => p.id === item.id);
    const totalCents = planDetail?.totalCents ?? 0;
    const fundedCents = planDetail?.fundedCents ?? 0;
    const isFullyFunded = fundedCents >= totalCents;

    if (!isFullyFunded) {
      const availableBudget = data?.totals.variableBalanceCents ?? 0;
      setFundingData({
        planId: item.id,
        planName: item.name,
        totalCents,
        fundedCents,
        availableBudget,
      });
      setShowFundingConfirmation(true);
      return;
    }

    try {
      const fundingResult = await fixedPlansApi.attemptFinalFunding(item.id);
      const freq = (planDetail as any)?.frequency;
      const nextDueISO = computeNextDueDateISO(item.dueOn, freq);
      const nextDueDate = nextDueISO
        ? isoToDateString(nextDueISO, userTimezone)
        : item.dueOn.slice(0, 10);

      setReconciliationData({
        planId: item.id,
        planName: item.name,
        totalCents: fundingResult.totalCents,
        fundedCents: fundingResult.fundedCents,
        isOverdue: !!fundingResult.isOverdue,
        overdueAmount: fundingResult.overdueAmount,
        message: fundingResult.message ?? "",
        nextDueDate: nextDueDate ?? getTodayInTimezone(userTimezone),
      });
      setShowPaymentReconciliation(true);
    } catch (fundingError) {
      console.error("[DueOverlay] attempt-final-funding failed", fundingError);
    }
  };

  const advanceDueQueue = async () => {
    const nextIdx = queueIndex + 1;
    if (nextIdx < dueQueue.length) {
      const nextItem = dueQueue[nextIdx];
      setQueueIndex(nextIdx);
      setDueTodayPlan(nextItem);
      setOverrideDueISO(nextItem.dueOn.slice(0, 10));
      const initialSavings = savingsCategories[0]?.id || null;
      setSavingsCategoryId(initialSavings);
      setFundingSource("savings");
      await openDueItem(nextItem);
    } else {
      setDueTodayPlan(null);
      setDueQueue([]);
      setQueueIndex(0);
    }
  };

  function toUserMidnight(iso: string, timezone: string) {
    const dateStr = isoToDateString(iso, timezone);
    return new Date(dateStringToUTCMidnight(dateStr, timezone));
  }

  function countPayPeriodsBetween(
    startIso: string,
    endIso: string,
    firstIncomeIso: string,
    frequency: NonNullable<typeof incomeFrequency>,
    timezone: string
  ) {
    let count = 0;
    let nextPayDate = toUserMidnight(firstIncomeIso, timezone);
    const normalizedStart = toUserMidnight(startIso, timezone);
    const normalizedEnd = toUserMidnight(endIso, timezone);

    const targetDay = Number(isoToDateString(firstIncomeIso, timezone).split("-")[2] || "1");
    const advanceByPeriod = () => {
      if (frequency === "monthly") {
        const year = nextPayDate.getUTCFullYear();
        const month = nextPayDate.getUTCMonth() + 1;
        const daysInMonth = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
        nextPayDate = new Date(Date.UTC(year, month, Math.min(targetDay, daysInMonth)));
      } else {
        const days = frequency === "biweekly" ? 14 : 7;
        nextPayDate = new Date(nextPayDate.getTime() + days * 86_400_000);
      }
    };

    while (nextPayDate < normalizedStart) {
      advanceByPeriod();
    }
    while (nextPayDate < normalizedEnd) {
      count++;
      advanceByPeriod();
    }
    return Math.max(1, count);
  }

  function getFundingAhead(plan: any) {
    if (
      incomeType !== "regular" ||
      !incomeFrequency ||
      !firstIncomeDate ||
      !plan.cycleStart ||
      !plan.dueOn
    ) {
      return null;
    }

    const nowOverride = new URLSearchParams(window.location.search).get("debugNow");
    const now = nowOverride ? new Date(`${nowOverride}T00:00:00.000Z`).toISOString() : new Date().toISOString();
    let cycleStart = plan.cycleStart as string;
    const dueOn = plan.dueOn as string;

    let cycleStartDate: Date;
    let dueDate: Date;
    let nowDate: Date;
    try {
      cycleStartDate = toUserMidnight(cycleStart, userTimezone);
      dueDate = toUserMidnight(dueOn, userTimezone);
      nowDate = toUserMidnight(now, userTimezone);
    } catch {
      return null;
    }

    if (cycleStartDate >= dueDate || cycleStartDate > nowDate) {
      cycleStart = now;
    }

    const totalPeriods = countPayPeriodsBetween(
      cycleStart,
      dueOn,
      firstIncomeDate,
      incomeFrequency,
      userTimezone
    );
    const elapsedPeriods = countPayPeriodsBetween(
      cycleStart,
      now,
      firstIncomeDate,
      incomeFrequency,
      userTimezone
    );
    const targetFunded = Math.min(
      plan.totalCents ?? 0,
      Math.ceil(((plan.totalCents ?? 0) * elapsedPeriods) / totalPeriods)
    );
    const funded = plan.fundedCents ?? 0;
    const aheadBy = Math.max(0, funded - targetFunded);
    return aheadBy > 0 ? aheadBy : null;
  }

  // Helper: compute next due date from current due using user's timezone
  function computeNextDueDateISO(dateISO: string, frequency?: string): string | null {
    if (!frequency || frequency === "one-time") return null;
    
    // Convert ISO to date string in user's timezone
    const dateStr = isoToDateString(dateISO, userTimezone);
    const [year, month, day] = dateStr.split('-').map(Number);
    const date = new Date(Date.UTC(year, month - 1, day));
    
    if (frequency === "weekly") {
      date.setUTCDate(date.getUTCDate() + 7);
    } else if (frequency === "biweekly") {
      date.setUTCDate(date.getUTCDate() + 14);
    } else if (frequency === "monthly") {
      date.setUTCMonth(date.getUTCMonth() + 1);
    }
    
    // Convert back to ISO with timezone handling
    const nextDateStr = `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')}`;
    return dateStringToUTCMidnight(nextDateStr, userTimezone);
  }

  // Detect if any fixed plan is due/overdue and needs user payment confirmation.
  // Include fully funded overdue plans so missed-day confirmations still surface.
  useEffect(() => {
    let cancelled = false;
    async function checkDueToday() {
      if (!data || !shouldLoadDashboard || hasCheckedDueToday) return;
      try {
        const res = await fixedPlansApi.due({ daysAhead: 0 });
        const items = (res.items || [])
          .filter((x) => x.isDue && (!x.isOverdue || x.remainingCents <= 0))
          .filter((x) => {
            const dismissedUntilStr = localStorage.getItem(`overdue-dismissed-${x.id}`);
            if (!dismissedUntilStr) return true;
            const dismissedUntil = parseInt(dismissedUntilStr, 10);
            if (!Number.isFinite(dismissedUntil) || Date.now() >= dismissedUntil) {
              localStorage.removeItem(`overdue-dismissed-${x.id}`);
              return true;
            }
            return false;
          });
        if (!cancelled && items.length > 0) {
          setHasCheckedDueToday(true);
          const first = items[0];

          await openDueItem(first);

          // Store queue for potential future bills
          setDueQueue(items.map((i) => ({
            id: i.id,
            name: i.name,
            dueOn: i.dueOn,
            remainingCents: i.remainingCents,
          })));
          setQueueIndex(0);
        }
      } catch (e: any) {
        // Non-blocking: just ignore failures
        try { console.warn("[DueOverlay] due check failed", e); } catch {}
      }
    }
    checkDueToday();
    return () => {
      cancelled = true;
    };
  }, [data, shouldLoadDashboard, hasCheckedDueToday, savingsCategories, userTimezone]);

  useEffect(() => {
    if (
      !data ||
      showDueOverlay ||
      showFundingConfirmation ||
      showPaymentReconciliation ||
      earlyPaymentPrompt
    ) {
      return;
    }

    const eligible = data.fixedPlans.find((plan: any) => {
      const total = plan.totalCents ?? 0;
      const funded = plan.fundedCents ?? 0;
      if (total <= 0 || funded < total) return false;
      if (plan.isOverdue) return false;
      if (plan.needsFundingThisPeriod !== false) return false;
      const ahead = getFundingAhead(plan);
      if (!ahead || ahead <= 0) return false;
      const key = `early-paid-prompt-${plan.id}-${plan.dueOn}`;
      if (localStorage.getItem(key)) return false;
      return true;
    });

    if (eligible) {
      setEarlyPaymentPrompt({
        planId: eligible.id,
        planName: eligible.name,
        dueOn: eligible.dueOn,
      });
    }
  }, [
    data,
    showDueOverlay,
    showFundingConfirmation,
    showPaymentReconciliation,
    earlyPaymentPrompt,
  ]);

  useEffect(() => {
    if (data && !data.hasBudgetSetup) {
      navigate("/onboarding", { replace: true });
    }
  }, [data?.hasBudgetSetup, navigate]);

  if (isLoading) {
    return (
      <div className="p-6 space-y-4">
        <div className="h-6 w-40 bg-[--color-panel] rounded animate-pulse" />
        <div className="grid gap-4 md:grid-cols-3">
          {Array.from({ length: 3 }).map((_, idx) => (
            <div
              key={idx}
              className="h-24 rounded-xl bg-[--color-panel] animate-pulse"
            />
          ))}
        </div>
      </div>
    );
  }

  if (isError || !data) {
    return (
      <div className="p-6 text-red-600 space-y-2">
        <p>{(error as any)?.message || "Failed to load dashboard"}</p>
        <button className="btn" onClick={() => refetch()}>
          Retry
        </button>
      </div>
    );
  }

  const currency = (cents: number) => (cents / 100).toFixed(2);

  // CSV export removed per UI simplification request.

  const hasCategories = data.variableCategories.length > 0;
  const hasPlans = data.fixedPlans.length > 0;
  const hasTx = data.recentTransactions.length > 0;
  const hasTransactionsOlderThanSixMonths =
    data.trendWindow?.hasTransactionsOlderThanSixMonths ?? false;
  const useTrendWindowPagination = hasTransactionsOlderThanSixMonths;
  const trendWindowLabel = data.trendWindow?.label ?? "Last 6 months";
  const canTrendNewer = data.trendWindow?.canGoNewer ?? trendPage > 0;
  const canTrendOlder = data.trendWindow?.canGoOlder ?? true;

  const greetingName =
    data.user.displayName ||
    session.data?.displayName ||
    session.data?.email?.split("@")[0] ||
    "friend";

  // Chart data for analytics section

  return (
    <div className="p-6 space-y-8 relative">
      {/* Payday Overlay */}
      <PaydayOverlay />
      
      {/* Due Today Overlay */}
      {showDueOverlay && dueTodayPlan && (
        <div className="fixed inset-0 bg-[color:var(--color-bg)]/80 backdrop-blur-sm flex items-center justify-center z-50">
          <div className="bg-[--color-bg] border rounded-xl p-6 max-w-md mx-4 space-y-4 shadow-2xl">
            <div className="space-y-1">
              <h3 className="font-semibold text-lg">{dueTodayPlan.name} is due today</h3>
              <p className="text-sm muted">
                Confirm the due date and record the payment. We'll roll the plan forward automatically.
              </p>
            </div>
            {(() => {
              const planFull = data?.fixedPlans?.find((p) => p.id === dueTodayPlan.id);
              // Extract frequency from plan.frequency or paymentSchedule.frequency
              let freq = (planFull as any)?.frequency;
              if (!freq && (planFull as any)?.paymentSchedule?.frequency) {
                freq = (planFull as any).paymentSchedule.frequency;
              }
              const suggestedISO = planFull ? computeNextDueDateISO(dueTodayPlan.dueOn, freq) : null;
              const suggestedDateOnly = suggestedISO ? suggestedISO.slice(0, 10) : "";
              return (
                <div className="space-y-2">
                  <div className="text-sm">Calculated next due date</div>
                  <div className="text-base font-mono">
                    {suggestedDateOnly || "n/a"}
                  </div>
                  <div className="text-xs muted">
                    If this looks correct, press Confirm. Or adjust below.
                  </div>
                  <label className="stack">
                    <span className="text-sm muted">Adjust next due date (optional)</span>
                    <input
                      type="date"
                      className="input"
                      value={overrideDueISO}
                      placeholder={suggestedDateOnly || dueTodayPlan.dueOn.slice(0,10)}
                      onChange={(e) => setOverrideDueISO(e.target.value)}
                    />
                  </label>
                </div>
              );
            })()}
            <div className="space-y-2">
              <div className="text-sm">Remaining</div>
              <div className="text-xl font-mono">
                <Money cents={dueTodayPlan.remainingCents} />
              </div>
              <div className="text-xs muted">{queueIndex + 1} of {dueQueue.length}</div>
            </div>
            {dueTodayPlan.remainingCents > 0 && (
              <div className="space-y-2">
                <div className="text-sm muted">Funding source</div>
                <div className="row gap-2">
                  <button
                    type="button"
                    className={"nav-link " + (fundingSource === "savings" ? "nav-link-active" : "")}
                    onClick={() => setFundingSource("savings")}
                    disabled={isSubmittingDue || savingsCategories.length === 0}
                  >
                    Use savings
                  </button>
                  <button
                    type="button"
                    className={"nav-link " + (fundingSource === "deficit" ? "nav-link-active" : "")}
                    onClick={() => setFundingSource("deficit")}
                    disabled={isSubmittingDue}
                  >
                    Record deficit
                  </button>
                </div>
                {fundingSource === "savings" && (
                  <label className="stack">
                    <span className="text-sm muted">Savings category</span>
                    {savingsCategories.length > 0 ? (
                      <select
                        className="input"
                        value={savingsCategoryId ?? ""}
                        onChange={(e) => setSavingsCategoryId(e.target.value)}
                      >
                        {savingsCategories.map((c) => (
                          <option key={c.id} value={c.id}>{c.name}</option>
                        ))}
                      </select>
                    ) : (
                      <div className="toast-err">No savings categories available.</div>
                    )}
                  </label>
                )}
              </div>
            )}
            <div className="flex gap-3 pt-2">
              <button
                type="button"
                className="btn bg-gray-600 hover:bg-gray-500"
                onClick={() => {
                  // Skip this item
                  const nextIdx = queueIndex + 1;
                  if (nextIdx < dueQueue.length) {
                    const next = dueQueue[nextIdx];
                    setQueueIndex(nextIdx);
                    setDueTodayPlan(next);
                    setOverrideDueISO(next.dueOn.slice(0, 10));
                    const initialSavings = savingsCategories[0]?.id || null;
                    setSavingsCategoryId(initialSavings);
                    setFundingSource("savings");
                  } else {
                    setShowDueOverlay(false);
                    setDueTodayPlan(null);
                    setDueQueue([]);
                    setQueueIndex(0);
                  }
                }}
                disabled={isSubmittingDue}
              >
                Skip
              </button>
              <button
                type="button"
                className="btn bg-gray-600 hover:bg-gray-500"
                onClick={() => {
                  setShowDueOverlay(false);
                  setDueTodayPlan(null);
                  setDueQueue([]);
                  setQueueIndex(0);
                }}
                disabled={isSubmittingDue}
              >
                Skip all
              </button>
              <button
                type="button"
                className="btn ml-auto"
                onClick={async () => {
                  if (!dueTodayPlan) return;
                  setIsSubmittingDue(true);
                  try {
                    const body: any = {};
                    if (overrideDueISO) {
                      // Convert date-only (YYYY-MM-DD) to full ISO datetime (UTC midnight)
                      body.overrideDueOnISO = dateStringToUTCMidnight(overrideDueISO, userTimezone);
                    }
                    // Handle shortage funding
                    if (dueTodayPlan.remainingCents > 0) {
                      if (fundingSource === "savings") {
                        if (!savingsCategoryId) {
                          throw new Error("Select a savings category to cover the shortage.");
                        }
                        body.fundingSource = "savings";
                        body.savingsCategoryId = savingsCategoryId;
                      } else if (fundingSource === "deficit") {
                        body.fundingSource = "deficit";
                      }
                    }
                    const res = await fixedPlansApi.payNow(dueTodayPlan.id, body);
                    const next = res.nextDueOn
                      ? formatDateInTimezone(res.nextDueOn, userTimezone)
                      : null;
                    push(
                      "ok",
                      next
                        ? `Payment recorded. Next due ${next}.`
                        : "Payment recorded."
                    );
                    await refetch();
                    // Advance queue
                    const nextIdx = queueIndex + 1;
                    if (nextIdx < dueQueue.length) {
                      const nextItem = dueQueue[nextIdx];
                      setQueueIndex(nextIdx);
                      setDueTodayPlan(nextItem);
                      setOverrideDueISO(nextItem.dueOn.slice(0, 10));
                      const initialSavings = savingsCategories[0]?.id || null;
                      setSavingsCategoryId(initialSavings);
                      setFundingSource("savings");
                    } else {
                      setShowDueOverlay(false);
                      setDueTodayPlan(null);
                      setDueQueue([]);
                      setQueueIndex(0);
                    }
                  } catch (err: any) {
                    push("err", err?.message ?? "Failed to record payment.");
                  } finally {
                    setIsSubmittingDue(false);
                  }
                }}
                disabled={isSubmittingDue}
              >
                {isSubmittingDue ? "Recording…" : "Confirm & Pay Now"}
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Funding Confirmation Modal */}
      {showFundingConfirmation && fundingData && (
        <FundingConfirmationModal
          planId={fundingData.planId}
          planName={fundingData.planName}
          totalCents={fundingData.totalCents}
          fundedCents={fundingData.fundedCents}
          availableBudget={fundingData.availableBudget}
          onClose={() => {
            setShowFundingConfirmation(false);
            setFundingData(null);
          }}
          onFundingComplete={async (fundingResult) => {
            setShowFundingConfirmation(false);
            try {
              const planDetail = data?.fixedPlans.find(p => p.id === fundingData.planId);
              const freq = (planDetail as any)?.frequency;
              const fallbackDueISO = dateStringToUTCMidnight(getTodayInTimezone(userTimezone), userTimezone);
              const nextDueISO = computeNextDueDateISO(planDetail?.dueOn ?? fallbackDueISO, freq);
              const nextDueDate = nextDueISO ? isoToDateString(nextDueISO, userTimezone) : getTodayInTimezone(userTimezone);
              
              setReconciliationData({
                planId: fundingData.planId,
                planName: fundingData.planName,
                totalCents: fundingResult.totalCents,
                fundedCents: fundingResult.fundedCents,
                isOverdue: !!fundingResult.isOverdue,
                overdueAmount: fundingResult.overdueAmount,
                message: fundingResult.message ?? "",
                nextDueDate: nextDueDate ?? getTodayInTimezone(userTimezone),
              });
              setFundingData(null);
              setShowPaymentReconciliation(true);
              await refetch();
            } catch (err) {
              console.error("Failed to proceed to payment reconciliation", err);
              push("err", "Failed to proceed with payment");
              setFundingData(null);
            }
          }}
        />
      )}

      {/* Payment Reconciliation Modal */}
      {showPaymentReconciliation && reconciliationData && (
        <PaymentReconciliationModal
          planId={reconciliationData.planId}
          planName={reconciliationData.planName}
          totalCents={reconciliationData.totalCents}
          fundedCents={reconciliationData.fundedCents}
          isOverdue={reconciliationData.isOverdue}
          overdueAmount={reconciliationData.overdueAmount}
          message={reconciliationData.message}
          nextDueDate={reconciliationData.nextDueDate}
          onClose={() => {
            setShowPaymentReconciliation(false);
            setReconciliationData(null);
          }}
          onSuccess={async () => {
            setShowPaymentReconciliation(false);
            setReconciliationData(null);
            // Clear any "remind me later" dismissal since they've handled it
            if (reconciliationData?.planId) {
              localStorage.removeItem(`overdue-dismissed-${reconciliationData.planId}`);
            }
            push("ok", "Payment recorded successfully");
            await refetch();
            if (dueQueue.length > 0) {
              await advanceDueQueue();
            }
          }}
        />
      )}

      {earlyPaymentPrompt && (
        <EarlyPaymentPromptModal
          planId={earlyPaymentPrompt.planId}
          planName={earlyPaymentPrompt.planName}
          dueOn={earlyPaymentPrompt.dueOn}
          timezone={userTimezone}
          onClose={() => {
            const key = `early-paid-prompt-${earlyPaymentPrompt.planId}-${earlyPaymentPrompt.dueOn}`;
            localStorage.setItem(key, "dismissed");
            setEarlyPaymentPrompt(null);
          }}
          onConfirmed={() => {
            const key = `early-paid-prompt-${earlyPaymentPrompt.planId}-${earlyPaymentPrompt.dueOn}`;
            localStorage.setItem(key, "confirmed");
            refetch();
          }}
        />
      )}

      {/* HEADER */}
      <header>
        <h1 className="text-2xl font-bold">Hello, {greetingName}!</h1>
        <p className="text-sm muted">
          Snapshot of balances, plans, and recent activity.
        </p>
      </header>

      {/* OVERDUE BILLS WARNING BANNER */}
      {(() => {
        const overduePlans = data.fixedPlans.filter((p: any) => p.isOverdue && (p.overdueAmount ?? 0) > 0);
        if (overduePlans.length === 0) return null;
        const totalOverdue = overduePlans.reduce((sum: number, p: any) => sum + (p.overdueAmount ?? 0), 0);
        return (
          <div className="bg-red-500/15 border-2 border-red-500 rounded-xl p-4 space-y-2">
            <div className="flex flex-col gap-3 sm:flex-row sm:items-center">
              <span className="text-2xl">!</span>
              <div>
                <div className="font-bold text-red-400">
                  {overduePlans.length === 1 
                    ? `${overduePlans[0].name} is overdue` 
                    : `${overduePlans.length} bills are overdue`}
                </div>
                <div className="text-sm text-red-300">
                  Total outstanding: <Money cents={totalOverdue} /> · Post income to auto-fund
                </div>
              </div>
              <Link to="/income" className="btn bg-red-500 hover:bg-red-600 w-full sm:w-auto sm:ml-auto">
                + Add Income
              </Link>
            </div>
            {overduePlans.length > 1 && (
              <div className="text-xs text-red-300 pl-9">
                Priority order: {overduePlans
                  .sort((a: any, b: any) => {
                    const aTime = a.overdueSince ? new Date(a.overdueSince).getTime() : 0;
                    const bTime = b.overdueSince ? new Date(b.overdueSince).getTime() : 0;
                    return aTime - bTime;
                  })
                  .map((p: any) => `${p.name} ($${((p.overdueAmount ?? 0) / 100).toFixed(0)})`)
                  .join(" → ")}
              </div>
            )}
          </div>
        );
      })()}

      <PercentGuard />

      {/* BUDGET OVERVIEW – key metrics */}
      <section className="grid gap-4 md:grid-cols-3">
        <Card label="Total Budget">
          <Money cents={data.totals.incomeCents} />
        </Card>
        <Card label="Available Budget">
          <Money cents={data.totals.variableBalanceCents} />
        </Card>
        <Card label="Funded Towards Fixed Plans">
          <Money cents={data.fixedPlans.reduce((sum, plan) => sum + (plan.fundedCents || 0), 0)} />
        </Card>
      </section>

      {/* ANALYTICS SECTION - Desktop: Show early with tabs */}
      {isDesktop && (
        <section className="space-y-4">
          <div className="row items-center gap-3">
            <h2 className="font-semibold">Analytics</h2>
            <div className="row gap-1 ml-auto rounded-full bg-[--color-panel] p-1">
              <AnalyticsTabButton
                label="Overview"
                active={activeTab === "overview"}
                onClick={() => setActiveTab("overview")}
              />
              <AnalyticsTabButton
                label="Variable"
                active={activeTab === "variable"}
                onClick={() => setActiveTab("variable")}
              />
              <AnalyticsTabButton
                label="Fixed"
                active={activeTab === "fixed"}
                onClick={() => setActiveTab("fixed")}
              />
              <AnalyticsTabButton
                label="Trend"
                active={activeTab === "trend"}
                onClick={() => setActiveTab("trend")}
              />
            </div>
          </div>

          {activeTab === "overview" && (
            <div className="grid gap-6 lg:grid-cols-2">
              <VariableAllocationDonut data={variableSlices} />
              <FixedFundingBars data={fixedChartData} />
            </div>
          )}

          {activeTab === "variable" && (
            <VariableAllocationDonut data={variableSlices} />
          )}

          {activeTab === "fixed" && (
            <FixedFundingBars data={fixedChartData} />
          )}

          {activeTab === "trend" && (
            <section className="space-y-4">
              {useTrendWindowPagination ? (
                <TrendWindowControls
                  label={trendWindowLabel}
                  canGoOlder={canTrendOlder}
                  canGoNewer={canTrendNewer}
                  busy={isFetching}
                  onOlder={() => setTrendPage((p) => p + 1)}
                  onNewer={() => setTrendPage((p) => Math.max(0, p - 1))}
                />
              ) : null}
              <section className="grid gap-6 lg:grid-cols-2">
                <MonthlyTrendChart data={data.monthlyTrend} />
                <RecentTransactionsPanel
                  transactions={data.recentTransactions}
                  hasData={hasTx}
                  rangeLabel={useTrendWindowPagination ? trendWindowLabel : undefined}
                  userTimezone={userTimezone}
                />
              </section>
            </section>
          )}
        </section>
      )}

      {/* UPCOMING PLAN ALERTS */}
      {data.upcomingPlans.length > 0 && (
        <UpcomingPlanAlerts plans={data.upcomingPlans} userTimezone={userTimezone} />
      )}

      {/* VARIABLE / FIXED LISTS */}
      <section className="grid gap-6 lg:grid-cols-2">
        <div className="space-y-3">
          <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
          <h2 className="font-semibold">Variable Categories</h2>
            <span className="text-sm muted">
              {data.percentTotal}%
            </span>
          </div>
          {!hasCategories ? (
            <EmptyState
              message="No categories yet"
              actionLabel="Create categories"
              actionTo="/settings/categories"
            />
          ) : (
            <div className="border rounded-xl divide-y">
              {data.variableCategories.map((c) => {
                return (
                  <div key={c.id} className={`p-3 space-y-2 ${c.isSavings ? 'bg-gradient-to-br from-emerald-50/10 via-emerald-50/5 to-transparent border-2 border-emerald-400/60 shadow-lg shadow-emerald-500/20 ring-1 ring-emerald-400/30' : ''}`}>
                    <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                      <div>
                        <div className="font-medium">{c.name}</div>
                        <div className="text-xs muted">
                          {c.isSavings ? "Savings • " : ""}
                          Priority {c.priority}
                        </div>
                        {c.isSavings &&
                          (c.savingsTargetCents ?? 0) > 0 && (
                            <div className="text-xs text-emerald-400 font-medium">
                              Goal ${currency(c.savingsTargetCents ?? 0)}
                            </div>
                          )}
                      </div>
                      <div className="text-right">
                        {c.isSavings ? (
                          <>
                            <Money
                              cents={
                                (c.balanceCents ?? 0) +
                                (savingsReserveById.get(c.id) ?? 0)
                              }
                            />
                            <div className="text-xs muted">
                              Available <Money cents={c.balanceCents ?? 0} /> ·
                              Reserved{" "}
                              <Money cents={savingsReserveById.get(c.id) ?? 0} />
                            </div>
                            <div className="text-xs muted">{c.percent}%</div>
                          </>
                        ) : (
                          <>
                            <Money cents={c.balanceCents ?? 0} />
                            <div className="text-xs muted">{c.percent}%</div>
                          </>
                        )}
                      </div>
                    </div>
                    <ProgressBar value={c.percent} max={100} />
                  </div>
                );
              })}
            </div>
          )}
        </div>

        <div className="space-y-3">
          <h2 className="font-semibold">Fixed plans</h2>
          {!hasPlans ? (
            <EmptyState
              message="No plans configured"
              actionLabel="Add a plan"
              actionTo="/settings/plans"
            />
          ) : (
            <div className="border rounded-xl divide-y">
              {data.fixedPlans.map((p) => {
                const total = p.totalCents ?? 0;
                const funded = Math.min(p.fundedCents ?? 0, total);
                const remaining = Math.max(total - funded, 0);
                const pct = total
                  ? Math.round((funded / total) * 100)
                  : 0;
                const isOverdue = (p as any).isOverdue;
                const overdueAmount = (p as any).overdueAmount ?? 0;
                const aheadCents = getFundingAhead(p);
                return (
                  <div 
                    key={p.id} 
                    className={`p-3 space-y-2 ${isOverdue ? 'bg-red-500/10 border-l-4 border-red-500' : ''}`}
                  >
                    <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                      <div>
                        <div className="flex items-center gap-2">
                          <div className="font-medium">{p.name}</div>
                          {isOverdue && (
                            <span className="text-xs px-2 py-0.5 bg-red-500/20 text-red-400 rounded-full font-semibold">
                              OVERDUE
                            </span>
                          )}
                          {aheadCents !== null && (
                            <span className="text-xs px-2 py-0.5 bg-emerald-500/20 text-emerald-400 rounded-full font-semibold">
                              Ahead {new Intl.NumberFormat("en-US", {
                                style: "currency",
                                currency: "USD",
                                maximumFractionDigits: 0,
                              }).format((aheadCents ?? 0) / 100)}
                            </span>
                          )}
                        </div>
                        <div className="text-xs muted">
                          Due{" "}
                          {formatDateInTimezone(p.dueOn, userTimezone)} ·
                          Priority {p.priority}
                        </div>
                        {isOverdue && overdueAmount > 0 && (
                          <div className="text-xs text-red-400 font-medium mt-1">
                            Outstanding: <Money cents={overdueAmount} />
                          </div>
                        )}
                      </div>
                      <div className="text-right text-sm">
                        Remaining <Money cents={remaining} />
                      </div>
                    </div>
                    <ProgressBar
                      value={funded}
                      max={total || 1}
                      label={`Funded ${pct}%`}
                      variant={isOverdue ? "danger" : "default"}
                    />
                  </div>
                );
              })}
            </div>
          )}
        </div>
      </section>

      {/* SAVINGS GOALS */}
      {data.savingsTargets.length > 0 && (
        <SavingsGoalsPanel goals={data.savingsTargets} />
      )}

      {/* Mobile / tablet: tabbed analytics */}
      <section className="space-y-4 lg:hidden">
        <div className="row items-center gap-3">
          <h2 className="font-semibold">Analytics</h2>
          <div className="row gap-1 ml-auto rounded-full bg-[--color-panel] p-1">
            <AnalyticsTabButton
              label="Variable"
              active={activeTab === "variable"}
              onClick={() => setActiveTab("variable")}
            />
            <AnalyticsTabButton
              label="Fixed"
              active={activeTab === "fixed"}
              onClick={() => setActiveTab("fixed")}
            />
            <AnalyticsTabButton
              label="Trend"
              active={activeTab === "trend"}
              onClick={() => setActiveTab("trend")}
            />
          </div>
        </div>
        {activeTab === "variable" && (
          <VariableAllocationDonut data={variableSlices} />
        )}
        {activeTab === "fixed" && (
          <FixedFundingBars data={fixedChartData} />
        )}
        {activeTab === "trend" && (
          <section className="space-y-4">
            {useTrendWindowPagination ? (
              <TrendWindowControls
                label={trendWindowLabel}
                canGoOlder={canTrendOlder}
                canGoNewer={canTrendNewer}
                busy={isFetching}
                onOlder={() => setTrendPage((p) => p + 1)}
                onNewer={() => setTrendPage((p) => Math.max(0, p - 1))}
              />
            ) : null}
            <MonthlyTrendChart data={data.monthlyTrend} />
            <RecentTransactionsPanel
              transactions={data.recentTransactions}
              hasData={hasTx}
              rangeLabel={useTrendWindowPagination ? trendWindowLabel : undefined}
              userTimezone={userTimezone}
            />
          </section>
        )}
      </section>
    </div>
  );
}

/* ───────────────────────────
 * Small components
 * ───────────────────────────*/

function AnalyticsTabButton({
  label,
  active,
  onClick,
}: {
  label: string;
  active: boolean;
  onClick: () => void;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      className={
        "px-3 py-1 rounded-full text-xs font-semibold transition " +
        (active
          ? "bg-[--color-ink] text-[--color-bg]"
          : "text-[--color-muted] hover:text-[--color-text]")
      }
    >
      {label}
    </button>
  );
}

function TrendWindowControls({
  label,
  canGoOlder,
  canGoNewer,
  busy,
  onOlder,
  onNewer,
}: {
  label: string;
  canGoOlder: boolean;
  canGoNewer: boolean;
  busy: boolean;
  onOlder: () => void;
  onNewer: () => void;
}) {
  return (
    <div className="row items-center gap-2 rounded-xl border px-3 py-2 bg-[--color-panel]">
      <button
        type="button"
        className="btn"
        onClick={onNewer}
        disabled={!canGoNewer || busy}
      >
        ← Newer
      </button>
      <div className="text-sm muted ml-1">{label}</div>
      <button
        type="button"
        className="btn ml-auto"
        onClick={onOlder}
        disabled={!canGoOlder || busy}
      >
        Older →
      </button>
    </div>
  );
}

function UpcomingPlanAlerts({
  plans,
  userTimezone,
}: {
  plans: { id: string; name: string; dueOn: string; remainingCents: number }[];
  userTimezone: string;
}) {
  if (plans.length === 0) return null;
  return (
    <section className="rounded-xl border border-amber-300/70 bg-amber-50/70 p-4 space-y-3 dark:border-amber-500/40 dark:bg-amber-500/10">
      <div className="flex items-center gap-2 text-sm font-semibold text-amber-900 dark:text-amber-50">
        Upcoming plan alerts (next 14 days)
      </div>
      <ul className="space-y-2">
        {plans.map((plan) => (
          <li
            key={plan.id}
            className="flex flex-col gap-2 rounded-lg bg-amber-100/60 px-3 py-2 text-sm text-amber-900 dark:bg-amber-200/15 dark:text-amber-50 sm:flex-row sm:items-center sm:justify-between"
          >
            <div>
              <div className="font-medium text-amber-950 dark:text-white">{plan.name}</div>
              <div className="text-xs text-amber-700 dark:text-amber-200">
                Due{" "}
                {formatDateInTimezone(plan.dueOn, userTimezone, {
                  month: "short",
                  day: "numeric",
                })}
              </div>
            </div>
            <div className="text-right">
              <div className="text-xs text-amber-700 dark:text-amber-200">Remaining</div>
              <Money cents={plan.remainingCents} />
            </div>
          </li>
        ))}
      </ul>
    </section>
  );
}

function SavingsGoalsPanel({
  goals,
}: {
  goals: Array<{
    id: string;
    name: string;
    balanceCents: number;
    targetCents: number;
    percent: number;
  }>;
}) {
  return (
    <section className="border rounded-xl p-4 space-y-3">
      <h2 className="font-semibold">Savings goals</h2>
      <ul className="space-y-3">
        {goals.map((goal) => (
          <li key={goal.id} className="space-y-1">
            <div className="flex items-center justify-between text-sm">
              <span>{goal.name}</span>
              <span className="text-xs muted">{goal.percent}%</span>
            </div>
            <div className="h-2 rounded-full bg-[--color-panel] overflow-hidden shadow-inner">
              <div
                className="h-full bg-gradient-to-r from-emerald-500 to-emerald-400 shadow-sm"
                style={{ width: `${Math.min(100, goal.percent)}%` }}
              />
            </div>
            <div className="flex items-center justify-between text-xs muted">
              <span>
                <Money cents={goal.balanceCents} /> saved
              </span>
              <span>
                Goal <Money cents={goal.targetCents} />
              </span>
            </div>
          </li>
        ))}
      </ul>
    </section>
  );
}

function RecentTransactionsPanel({
  transactions,
  hasData,
  rangeLabel,
  userTimezone,
}: {
  transactions: DashboardResponse["recentTransactions"];
  hasData: boolean;
  rangeLabel?: string;
  userTimezone: string;
}) {
  const title = rangeLabel ? "Transactions in window" : "Recent transactions";
  const visibleTransactions = transactions.slice(0, 10);
  const currentMonthLabel = new Intl.DateTimeFormat("en-US", {
    month: "long",
    year: "numeric",
    timeZone: userTimezone,
  }).format(new Date());

  if (!hasData) {
    return (
      <div className="space-y-3">
        <h2 className="font-semibold">{title}</h2>
        {rangeLabel ? <div className="text-xs muted">{rangeLabel}</div> : null}
        <div className="text-xs muted">
          Tip: Records defaults to This month ({currentMonthLabel}).
        </div>
        <EmptyState
          message="No transactions yet"
          actionLabel="Record one"
          actionTo="/spend"
        />
      </div>
    );
  }
  return (
    <div className="space-y-3">
      <h2 className="font-semibold">{title}</h2>
      {rangeLabel ? <div className="text-xs muted">{rangeLabel}</div> : null}
      <div className="text-xs muted">
        Tip: Records defaults to This month ({currentMonthLabel}).
      </div>
      
      {/* Desktop table view */}
      <div className="hidden sm:block border rounded-xl overflow-x-auto">
        <table className="w-full text-sm">
          <thead className="bg-[--color-panel]">
            <tr>
              <th className="text-left p-2">Date</th>
              <th className="text-left p-2">Kind</th>
              <th className="text-right p-2">Amount</th>
            </tr>
          </thead>
          <tbody>
            {visibleTransactions.map((tx) => (
              <tr key={tx.id} className="border-t">
                <td className="p-2">
                  {new Date(tx.occurredAt).toLocaleString()}
                </td>
                <td className="p-2 capitalize">
                  {tx.kind.replace("_", " ")}
                </td>
                <td className="p-2 text-right font-mono">
                  <Money cents={tx.amountCents} />
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* Mobile card view */}
      <div className="sm:hidden space-y-2">
        {visibleTransactions.map((tx) => (
          <div key={tx.id} className="border rounded-xl bg-[--color-panel] p-3">
            <div className="flex items-center justify-between">
              <div className="flex-1 min-w-0">
                <div className="text-sm font-medium capitalize">
                  {tx.kind.replace("_", " ")}
                </div>
                <div className="text-xs muted">
                  {new Date(tx.occurredAt).toLocaleString(undefined, {
                    month: 'short',
                    day: 'numeric',
                    hour: 'numeric',
                    minute: '2-digit'
                  })}
                </div>
              </div>
              <div className="text-base font-mono font-semibold">
                <Money cents={tx.amountCents} />
              </div>
            </div>
          </div>
        ))}
      </div>
      <div>
        <Link to="/transactions" className="btn text-sm">
          View more
        </Link>
      </div>
    </div>
  );
}

// Modernized KPI card
function Card({
  label,
  children,
}: {
  label: string;
  children: React.ReactNode;
}) {
  return (
    <div className="card space-y-2">
      <div className="text-xs uppercase tracking-[0.2em] muted">
        {label}
      </div>
      <div className="text-2xl font-semibold">{children}</div>
    </div>
  );
}

function ProgressBar({
  value,
  max,
  label,
  variant = "default",
}: {
  value: number;
  max: number;
  label?: string;
  variant?: "default" | "danger";
}) {
  const pct = Math.min(100, Math.round((value / Math.max(max, 1)) * 100));
  const barColor = variant === "danger" ? "bg-red-500" : "bg-[--color-ink]";
  return (
    <div className="space-y-1">
      {label && <div className="text-xs muted">{label}</div>}
      <div className="h-2 w-full rounded-full bg-[--color-panel] overflow-hidden">
        <div
          className={`h-full ${barColor}`}
          style={{ width: `${pct}%` }}
        />
      </div>
    </div>
  );
}

function EmptyState({
  message,
  actionLabel,
  actionTo,
}: {
  message: string;
  actionLabel: string;
  actionTo: string;
}) {
  return (
    <div className="rounded-xl border border-dashed p-6 text-center space-y-2">
      <p className="muted">{message}</p>
      <Link to={actionTo} className="btn text-sm">
        {actionLabel}
      </Link>
    </div>
  );
}
