// useBalanceOverview — scoped useReducer hook backing BalancePage.
//
// Domain coverage (per spec-plan-bilan.md v2 / Issue #141, extended in #204):
//   - Time-series for the evolution chart (totals + per-category + per-vehicle)
//   - Per-account latest snapshot value + period-anchor value (for Δ%)
//   - Period selector (3M / 6M / 1A / 3A / Tout)
//   - Chart mode toggle (line / stacked-area)
//   - Group-axis toggle for the stacked mode (asset class / fiscal envelope)
//
// `chartMode` (line/stacked) and `groupAxis` (class/vehicle) are two ORTHOGONAL
// dimensions: `groupAxis` only changes which breakdown feeds the stacked chart;
// it has no effect in line mode. They are kept as independent state to avoid
// conflating "how to draw" with "what to group by" (Issue #204).
//
// Returns (Modified Dietz) are loaded by the accounts table itself, not here.

import { useReducer, useEffect, useCallback } from "react";
import {
  getSnapshotTotalsByDate,
  getSnapshotTotalsByCategoryAndDate,
  getSnapshotTotalsByVehicleAndDate,
  getAccountsLatestSnapshot,
  getAccountsPeriodAnchor,
  getAccountLatentGainByLine,
  rollupLatentGain,
  type SnapshotTotalPoint,
  type SnapshotCategoryBreakdownPoint,
  type SnapshotVehicleBreakdownPoint,
  type AccountLatestSnapshot,
  type AccountPeriodAnchor,
  type AccountUnrealizedGain,
  type LatentGainRollup,
  type SnapshotDateRange,
} from "../services/balance.service";

export type BalancePeriod = "3M" | "6M" | "1A" | "3A" | "all";
export type BalanceChartMode = "line" | "stacked";
/** Stacked-chart grouping axis: by asset class (category) or fiscal envelope. */
export type BalanceGroupAxis = "class" | "vehicle";

interface State {
  period: BalancePeriod;
  chartMode: BalanceChartMode;
  /** Orthogonal to `chartMode`; only meaningful in stacked mode. Default 'class'. */
  groupAxis: BalanceGroupAxis;
  evolutionTotals: SnapshotTotalPoint[];
  evolutionByCategory: SnapshotCategoryBreakdownPoint[];
  evolutionByVehicle: SnapshotVehicleBreakdownPoint[];
  accountsLatest: AccountLatestSnapshot[];
  accountsPeriodAnchor: AccountPeriodAnchor[];
  /**
   * Per-account unrealized (latent) gain keyed by `account_id`, computed from
   * each detailed account's LATEST snapshot-line holdings (Issue #216). Only
   * detailed accounts with holdings appear; simple accounts are absent.
   */
  latentGainByAccount: Record<number, AccountUnrealizedGain>;
  /** Latent gain rolled up by asset class, by envelope, and grand total (#216). */
  latentGainRollup: LatentGainRollup;
  isLoading: boolean;
  error: string | null;
}

/** Empty rollup — initial state and the no-detailed-account case. */
const EMPTY_ROLLUP: LatentGainRollup = {
  byClass: [],
  byVehicle: [],
  grandTotal: {
    total_value: 0,
    total_book_cost: 0,
    total_gain: 0,
    total_gain_pct: null,
    has_unknown_book_cost: false,
  },
};

type Action =
  | { type: "SET_PERIOD"; payload: BalancePeriod }
  | { type: "SET_CHART_MODE"; payload: BalanceChartMode }
  | { type: "SET_GROUP_AXIS"; payload: BalanceGroupAxis }
  | { type: "LOAD_START" }
  | {
      type: "LOAD_SUCCESS";
      payload: {
        evolutionTotals: SnapshotTotalPoint[];
        evolutionByCategory: SnapshotCategoryBreakdownPoint[];
        evolutionByVehicle: SnapshotVehicleBreakdownPoint[];
        accountsLatest: AccountLatestSnapshot[];
        accountsPeriodAnchor: AccountPeriodAnchor[];
        latentGainByAccount: Record<number, AccountUnrealizedGain>;
        latentGainRollup: LatentGainRollup;
      };
    }
  | { type: "LOAD_ERROR"; payload: string };

function initialState(): State {
  return {
    period: "1A",
    chartMode: "line",
    groupAxis: "class",
    evolutionTotals: [],
    evolutionByCategory: [],
    evolutionByVehicle: [],
    accountsLatest: [],
    accountsPeriodAnchor: [],
    latentGainByAccount: {},
    latentGainRollup: EMPTY_ROLLUP,
    isLoading: false,
    error: null,
  };
}

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "SET_PERIOD":
      return { ...state, period: action.payload };
    case "SET_CHART_MODE":
      return { ...state, chartMode: action.payload };
    case "SET_GROUP_AXIS":
      return { ...state, groupAxis: action.payload };
    case "LOAD_START":
      return { ...state, isLoading: true, error: null };
    case "LOAD_SUCCESS":
      return {
        ...state,
        ...action.payload,
        isLoading: false,
        error: null,
      };
    case "LOAD_ERROR":
      return { ...state, isLoading: false, error: action.payload };
    default:
      return state;
  }
}

/**
 * Pure helper: turn a `BalancePeriod` into a `SnapshotDateRange` anchored on
 * the supplied `today` (defaults to now). Exported so the unit tests can
 * exercise the date math without mocking time.
 *
 * Period anchor decision (decisions-log #141): we anchor on `today`, not on
 * the latest snapshot. Aggregators read snapshot rows so the answer is
 * identical either way, but anchoring on today keeps the chart's right edge
 * stable as the user enters new snapshots — intuitive UX.
 */
export function computeBalanceDateRange(
  period: BalancePeriod,
  today: Date = new Date()
): SnapshotDateRange {
  if (period === "all") return {};
  const days =
    period === "3M" ? 90 : period === "6M" ? 180 : period === "1A" ? 365 : 1095;
  const from = new Date(today);
  from.setDate(from.getDate() - days);
  // Local-civil `YYYY-MM-DD` (matches normalizeSnapshotDate's expectations).
  const yyyy = from.getFullYear();
  const mm = String(from.getMonth() + 1).padStart(2, "0");
  const dd = String(from.getDate()).padStart(2, "0");
  return { from: `${yyyy}-${mm}-${dd}` };
}

export interface UseBalanceOverviewResult {
  state: State;
  setPeriod: (period: BalancePeriod) => void;
  setChartMode: (mode: BalanceChartMode) => void;
  setGroupAxis: (axis: BalanceGroupAxis) => void;
  reload: () => Promise<void>;
}

export function useBalanceOverview(): UseBalanceOverviewResult {
  const [state, dispatch] = useReducer(reducer, undefined, initialState);

  const load = useCallback(async (period: BalancePeriod) => {
    dispatch({ type: "LOAD_START" });
    try {
      const range = computeBalanceDateRange(period);
      // Parallel fetches — no inter-dependency between the queries.
      const [totals, byCategory, byVehicle, latest, anchors] =
        await Promise.all([
          getSnapshotTotalsByDate(range),
          getSnapshotTotalsByCategoryAndDate(range),
          getSnapshotTotalsByVehicleAndDate(range),
          getAccountsLatestSnapshot(),
          getAccountsPeriodAnchor(range),
        ]);

      // Latent gain (Issue #216): only detailed accounts that actually carry a
      // latest snapshot line can have holdings. Fetch each in parallel, fold the
      // holdings through the shared unrealized-gain guard, then roll up by asset
      // class / envelope. Per-account fetch failures are isolated (the account
      // simply has no latent-gain figure). Simple accounts are skipped entirely.
      const detailed = latest.filter(
        (a) => a.kind === "detailed" && a.latest_snapshot_line_id != null
      );
      const latentEntries = await Promise.all(
        detailed.map(async (a) => {
          try {
            const gain = await getAccountLatentGainByLine(
              a.latest_snapshot_line_id as number
            );
            return { account: a, gain };
          } catch {
            return null;
          }
        })
      );
      const latentGainByAccount: Record<number, AccountUnrealizedGain> = {};
      for (const e of latentEntries) {
        if (e) latentGainByAccount[e.account.account_id] = e.gain;
      }
      const latentGainRollup = rollupLatentGain(
        latentEntries
          .filter((e): e is NonNullable<typeof e> => e !== null)
          .map((e) => ({
            category_key: e.account.category_key,
            vehicle_type: e.account.vehicle_type,
            gain: e.gain,
          }))
      );

      dispatch({
        type: "LOAD_SUCCESS",
        payload: {
          evolutionTotals: totals,
          evolutionByCategory: byCategory,
          evolutionByVehicle: byVehicle,
          accountsLatest: latest,
          accountsPeriodAnchor: anchors,
          latentGainByAccount,
          latentGainRollup,
        },
      });
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      dispatch({ type: "LOAD_ERROR", payload: message });
    }
  }, []);

  // Reload whenever the period changes (and on mount).
  useEffect(() => {
    void load(state.period);
  }, [state.period, load]);

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

  const setChartMode = useCallback((mode: BalanceChartMode) => {
    dispatch({ type: "SET_CHART_MODE", payload: mode });
  }, []);

  const setGroupAxis = useCallback((axis: BalanceGroupAxis) => {
    dispatch({ type: "SET_GROUP_AXIS", payload: axis });
  }, []);

  const reload = useCallback(() => load(state.period), [load, state.period]);

  return { state, setPeriod, setChartMode, setGroupAxis, reload };
}
