// useDashboard — scoped useReducer hook backing DashboardPage (the `/` home page).
//
// Converged onto the `/reports/cartes` model (Issue #279, epic #260): the KPI
// row, top movers and budget adherence are the exact Cartes widgets, sourced
// from `getCartesSnapshot` against a (year, month) reference period. The
// Dashboard keeps its OWN two temporal axes, neither of which is
// `useReportsPeriod` (M1/I4 deliberately left the Dashboard untouched):
//   - `period` / `customDateFrom` / `customDateTo` — a flexible date range,
//     feeding the two range-based transactional widgets (top-expenses bar
//     chart, category-over-time trend).
//   - `referenceYear` / `referenceMonth` — a single reference month, feeding
//     the Cartes snapshot (KPIs + deltas, top movers, budget adherence, the
//     income-vs-expenses overlay chart). Defaults to the previous complete
//     month, like Cartes/Compare (`defaultReferencePeriod`).
//
// The account (import-source) filter (`accountIds`) is Dashboard-local state
// (not routed through `useReportsPeriod`), but applies to every transactional
// widget above — both temporal axes. It does NOT scope the net-worth tile
// (see below): `balance_accounts` is a disjoint concept from `import_sources`.
//
// The net-worth tile is fetched independently, once, on mount — it is not a
// transactional figure, has no relationship to `period`/`referenceMonth`, and
// must not be scoped by `accountIds`. Keeping it in its own effect makes that
// decoupling explicit in the code, not just in the UI copy.

import { useReducer, useCallback, useEffect, useRef } from "react";
import type {
  DashboardPeriod,
  CategoryBreakdownItem,
  CategoryOverTimeData,
  CartesSnapshot,
  ImportSource,
} from "../shared/types";
import {
  getExpensesByCategory,
  deriveNetWorthTile,
  type NetWorthTileData,
} from "../services/dashboardService";
import { getCategoryOverTime, getCartesSnapshot } from "../services/reportService";
import { getAllImportSources } from "../services/transactionService";
import {
  getSnapshotTotalsByDate,
  listSnapshots,
  listBalanceAccounts,
} from "../services/balance.service";
import { computeDateRange } from "../utils/dateRange";
import { defaultReferencePeriod } from "../utils/referencePeriod";

interface DashboardState {
  categoryBreakdown: CategoryBreakdownItem[];
  categoryOverTime: CategoryOverTimeData;
  cartesSnapshot: CartesSnapshot | null;
  period: DashboardPeriod;
  customDateFrom: string;
  customDateTo: string;
  referenceYear: number;
  referenceMonth: number;
  accountIds: number[];
  accounts: ImportSource[];
  netWorth: NetWorthTileData;
  isLoading: boolean;
  error: string | null;
}

type DashboardAction =
  | { type: "SET_LOADING"; payload: boolean }
  | { type: "SET_ERROR"; payload: string | null }
  | {
      type: "SET_DATA";
      payload: {
        categoryBreakdown: CategoryBreakdownItem[];
        categoryOverTime: CategoryOverTimeData;
        cartesSnapshot: CartesSnapshot;
      };
    }
  | { type: "SET_PERIOD"; payload: DashboardPeriod }
  | { type: "SET_CUSTOM_DATES"; payload: { dateFrom: string; dateTo: string } }
  | { type: "SET_REFERENCE_PERIOD"; payload: { year: number; month: number } }
  | { type: "SET_ACCOUNT_IDS"; payload: number[] }
  | { type: "SET_ACCOUNTS"; payload: ImportSource[] }
  | { type: "SET_NET_WORTH"; payload: NetWorthTileData };

const now = new Date();
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
const yearStartStr = `${now.getFullYear()}-01-01`;
const defaultRef = defaultReferencePeriod();

const EMPTY_CATEGORY_OVER_TIME: CategoryOverTimeData = {
  categories: [],
  data: [],
  colors: {},
  categoryIds: {},
  types: {},
  tree: [],
};

const EMPTY_NET_WORTH: NetWorthTileData = { visible: false, total: null, asOfDate: null };

const initialState: DashboardState = {
  categoryBreakdown: [],
  categoryOverTime: EMPTY_CATEGORY_OVER_TIME,
  cartesSnapshot: null,
  period: "year",
  customDateFrom: yearStartStr,
  customDateTo: todayStr,
  referenceYear: defaultRef.year,
  referenceMonth: defaultRef.month,
  accountIds: [],
  accounts: [],
  netWorth: EMPTY_NET_WORTH,
  isLoading: false,
  error: null,
};

function reducer(state: DashboardState, action: DashboardAction): DashboardState {
  switch (action.type) {
    case "SET_LOADING":
      return { ...state, isLoading: action.payload };
    case "SET_ERROR":
      return { ...state, error: action.payload, isLoading: false };
    case "SET_DATA":
      return {
        ...state,
        categoryBreakdown: action.payload.categoryBreakdown,
        categoryOverTime: action.payload.categoryOverTime,
        cartesSnapshot: action.payload.cartesSnapshot,
        isLoading: false,
      };
    case "SET_PERIOD":
      return { ...state, period: action.payload };
    case "SET_CUSTOM_DATES":
      return {
        ...state,
        period: "custom" as DashboardPeriod,
        customDateFrom: action.payload.dateFrom,
        customDateTo: action.payload.dateTo,
      };
    case "SET_REFERENCE_PERIOD":
      return { ...state, referenceYear: action.payload.year, referenceMonth: action.payload.month };
    case "SET_ACCOUNT_IDS":
      return { ...state, accountIds: action.payload };
    case "SET_ACCOUNTS":
      return { ...state, accounts: action.payload };
    case "SET_NET_WORTH":
      return { ...state, netWorth: action.payload };
    default:
      return state;
  }
}

export function useDashboard() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const fetchIdRef = useRef(0);

  const fetchData = useCallback(async (
    period: DashboardPeriod,
    customFrom: string | undefined,
    customTo: string | undefined,
    refYear: number,
    refMonth: number,
    accountIds: number[],
  ) => {
    const fetchId = ++fetchIdRef.current;
    dispatch({ type: "SET_LOADING", payload: true });
    dispatch({ type: "SET_ERROR", payload: null });

    try {
      const { dateFrom, dateTo } = computeDateRange(period, customFrom, customTo);
      const [categoryBreakdown, categoryOverTime, cartesSnapshot] = await Promise.all([
        getExpensesByCategory(dateFrom, dateTo, accountIds),
        // typeFilter "expense" (Issue #279): this chart is titled "expenses
        // over time" but previously aggregated every category type — a
        // revenue category could silently show up in an "expenses" trend.
        getCategoryOverTime(dateFrom, dateTo, undefined, accountIds, "expense"),
        getCartesSnapshot(refYear, refMonth, "month", accountIds),
      ]);

      if (fetchId !== fetchIdRef.current) return;
      dispatch({ type: "SET_DATA", payload: { categoryBreakdown, categoryOverTime, cartesSnapshot } });
    } catch (e) {
      if (fetchId !== fetchIdRef.current) return;
      dispatch({
        type: "SET_ERROR",
        payload: e instanceof Error ? e.message : String(e),
      });
    }
  }, []);

  useEffect(() => {
    fetchData(
      state.period,
      state.customDateFrom,
      state.customDateTo,
      state.referenceYear,
      state.referenceMonth,
      state.accountIds,
    );
  }, [
    state.period,
    state.customDateFrom,
    state.customDateTo,
    state.referenceYear,
    state.referenceMonth,
    state.accountIds,
    fetchData,
  ]);

  // One-time: import sources for the FilterPanel's account checkboxes (same
  // query as Trends/Compare/Budget — see useTrends.ts).
  useEffect(() => {
    (async () => {
      try {
        const accounts = await getAllImportSources();
        dispatch({ type: "SET_ACCOUNTS", payload: accounts });
      } catch (e) {
        dispatch({ type: "SET_ERROR", payload: e instanceof Error ? e.message : String(e) });
      }
    })();
  }, []);

  // One-time: net-worth tile (Bilan). Independent of `period`/`referenceMonth`
  // and NOT scoped by `accountIds` — a different "account" concept
  // (balance_accounts vs import_sources) and a different metric (net worth vs
  // transaction P&L). A failure here only hides the tile (best-effort); it
  // never surfaces as a page-wide error for what is a secondary widget.
  useEffect(() => {
    (async () => {
      try {
        const [accounts, snapshots, totals] = await Promise.all([
          listBalanceAccounts(),
          listSnapshots(),
          getSnapshotTotalsByDate(),
        ]);
        dispatch({
          type: "SET_NET_WORTH",
          payload: deriveNetWorthTile(accounts.length, snapshots.length > 0, totals),
        });
      } catch {
        dispatch({ type: "SET_NET_WORTH", payload: EMPTY_NET_WORTH });
      }
    })();
  }, []);

  const setPeriod = useCallback((period: DashboardPeriod) => {
    dispatch({ type: "SET_PERIOD", payload: period });
  }, []);

  const setCustomDates = useCallback((dateFrom: string, dateTo: string) => {
    dispatch({ type: "SET_CUSTOM_DATES", payload: { dateFrom, dateTo } });
  }, []);

  const setReferencePeriod = useCallback((year: number, month: number) => {
    dispatch({ type: "SET_REFERENCE_PERIOD", payload: { year, month } });
  }, []);

  const setAccountIds = useCallback((accountIds: number[]) => {
    dispatch({ type: "SET_ACCOUNT_IDS", payload: accountIds });
  }, []);

  return { state, setPeriod, setCustomDates, setReferencePeriod, setAccountIds };
}
