import { useReducer, useCallback, useEffect, useRef } from "react";
import type { CategoryDelta, ImportSource } from "../shared/types";
import { getCompareMonthOverMonth, getCompareYearOverYear } from "../services/reportService";
import { getAllImportSources } from "../services/transactionService";
import { useReportsPeriod } from "./useReportsPeriod";
import { defaultReferencePeriod as sharedDefaultReferencePeriod } from "../utils/referencePeriod";

export type CompareMode = "actual" | "budget";
export type CompareSubMode = "mom" | "yoy";

interface State {
  mode: CompareMode;
  subMode: CompareSubMode;
  year: number;
  month: number;
  rows: CategoryDelta[];
  accounts: ImportSource[];
  isLoading: boolean;
  error: string | null;
}

type Action =
  | { type: "SET_MODE"; payload: CompareMode }
  | { type: "SET_SUB_MODE"; payload: CompareSubMode }
  | { type: "SET_REFERENCE_PERIOD"; payload: { year: number; month: number } }
  | { type: "SET_LOADING"; payload: boolean }
  | { type: "SET_ROWS"; payload: CategoryDelta[] }
  | { type: "SET_ACCOUNTS"; payload: ImportSource[] }
  | { type: "SET_ERROR"; payload: string };

/**
 * Wrap-around helper: returns (year, month) shifted back by one month.
 * Example: previousMonth(2026, 1) -> { year: 2025, month: 12 }.
 */
export function previousMonth(year: number, month: number): { year: number; month: number } {
  if (month === 1) return { year: year - 1, month: 12 };
  return { year, month: month - 1 };
}

/**
 * Default reference period for the Compare report: the month preceding `today`.
 * Thin wrapper around the shared helper — kept as a named export so existing
 * imports (and tests) keep working.
 */
export function defaultReferencePeriod(today: Date = new Date()): { year: number; month: number } {
  return sharedDefaultReferencePeriod(today);
}

/**
 * Returns the comparison meta for a given subMode + reference period.
 * - MoM: previous month vs current month
 * - YoY: same month previous year vs current year
 */
export function comparisonMeta(
  subMode: CompareSubMode,
  year: number,
  month: number,
): { previousYear: number; previousMonth: number } {
  if (subMode === "mom") {
    const prev = previousMonth(year, month);
    return { previousYear: prev.year, previousMonth: prev.month };
  }
  return { previousYear: year - 1, previousMonth: month };
}

/**
 * Pure decision for the URL-period → reference-month sync effect.
 *
 * Gates on a *change* of `to` (the period's upper bound) rather than a
 * fire-once flag: seeded with the initial `to`, the mount is a no-op, so the
 * previous-month default from initialState survives instead of snapping to the
 * civil-year December that useReportsPeriod yields by default (Issue #253).
 * Because it keys off a value change, it is idempotent under React StrictMode's
 * dev double-invoke of effects — called twice with the same `to` it dispatches
 * nothing the second time (a fire-once boolean would wrongly sync on run #2).
 *
 * Returns the `to` to remember plus an optional reference period to dispatch.
 */
export function syncReferenceOnPeriodChange(
  lastSyncedTo: string,
  to: string,
  currentYear: number,
  currentMonth: number,
): { lastSyncedTo: string; next: { year: number; month: number } | null } {
  if (to === lastSyncedTo) return { lastSyncedTo, next: null };
  const [y, m] = to.split("-").map(Number);
  if (!Number.isFinite(y) || !Number.isFinite(m)) return { lastSyncedTo: to, next: null };
  if (y === currentYear && m === currentMonth) return { lastSyncedTo: to, next: null };
  return { lastSyncedTo: to, next: { year: y, month: m } };
}

const defaultRef = defaultReferencePeriod();
const initialState: State = {
  mode: "actual",
  subMode: "mom",
  year: defaultRef.year,
  month: defaultRef.month,
  rows: [],
  accounts: [],
  isLoading: false,
  error: null,
};

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "SET_MODE":
      return { ...state, mode: action.payload };
    case "SET_SUB_MODE":
      return { ...state, subMode: action.payload };
    case "SET_REFERENCE_PERIOD":
      return { ...state, year: action.payload.year, month: action.payload.month };
    case "SET_LOADING":
      return { ...state, isLoading: action.payload };
    case "SET_ROWS":
      return { ...state, rows: action.payload, isLoading: false, error: null };
    case "SET_ACCOUNTS":
      return { ...state, accounts: action.payload };
    case "SET_ERROR":
      return { ...state, error: action.payload, isLoading: false };
    default:
      return state;
  }
}

export function useCompare() {
  const { from, to, accountIds } = useReportsPeriod();
  const [state, dispatch] = useReducer(reducer, initialState);
  const fetchIdRef = useRef(0);

  const fetch = useCallback(
    async (
      mode: CompareMode,
      subMode: CompareSubMode,
      year: number,
      month: number,
      ids: number[],
    ) => {
      if (mode === "budget") return; // Budget view uses BudgetVsActualTable directly
      const id = ++fetchIdRef.current;
      dispatch({ type: "SET_LOADING", payload: true });
      try {
        const rows =
          subMode === "mom"
            ? await getCompareMonthOverMonth(year, month, ids)
            : await getCompareYearOverYear(year, month, ids);
        if (id !== fetchIdRef.current) return;
        dispatch({ type: "SET_ROWS", payload: rows });
      } catch (e) {
        if (id !== fetchIdRef.current) return;
        dispatch({ type: "SET_ERROR", payload: e instanceof Error ? e.message : String(e) });
      }
    },
    [],
  );

  useEffect(() => {
    fetch(state.mode, state.subMode, state.year, state.month, accountIds);
  }, [fetch, state.mode, state.subMode, state.year, state.month, accountIds]);

  // Load the import-source list once, for the FilterPanel's account checkboxes
  // (same query as the Trends/Transactions source filters — 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) });
      }
    })();
  }, []);

  // Keep the reference month in sync with the URL period when the user navigates
  // via PeriodSelector — but not on mount. The ref is seeded with the initial
  // `to`, so syncReferenceOnPeriodChange only fires on an actual change of `to`,
  // leaving the previous-month default from initialState intact (Issue #253) and
  // staying idempotent under StrictMode's dev double-invoke.
  const lastSyncedToRef = useRef(to);
  useEffect(() => {
    const { lastSyncedTo, next } = syncReferenceOnPeriodChange(
      lastSyncedToRef.current,
      to,
      state.year,
      state.month,
    );
    lastSyncedToRef.current = lastSyncedTo;
    if (next) dispatch({ type: "SET_REFERENCE_PERIOD", payload: next });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [to]);

  const setMode = useCallback((m: CompareMode) => {
    dispatch({ type: "SET_MODE", payload: m });
  }, []);

  const setSubMode = useCallback((s: CompareSubMode) => {
    dispatch({ type: "SET_SUB_MODE", payload: s });
  }, []);

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

  return { ...state, setMode, setSubMode, setReferencePeriod, from, to };
}
