// useBalanceAccounts — scoped useReducer hook backing AccountsPage.
//
// Domain coverage (per spec-plan-bilan.md v2): the AccountsPage CRUD over
// `balance_accounts` AND `balance_categories`. Snapshots, lines, transfers,
// and returns are out of scope here — they belong to `useSnapshotEditor`
// (Issue #146 / Bilan #1b) and `useBalanceOverview` (Issue #141 / Bilan #3).

import { useReducer, useCallback, useEffect, useRef } from "react";
import type {
  BalanceAccountWithCategory,
  BalanceCategory,
  BalanceCategoryKind,
} from "../shared/types";
import {
  listBalanceAccounts,
  listBalanceCategories,
  createBalanceAccount,
  updateBalanceAccount,
  archiveBalanceAccount,
  unarchiveBalanceAccount,
  createBalanceCategory,
  updateBalanceCategory,
  deleteBalanceCategory,
  BalanceServiceError,
  type CreateBalanceAccountInput,
  type CreateBalanceCategoryInput,
  type UpdateBalanceAccountInput,
  type UpdateBalanceCategoryInput,
} from "../services/balance.service";

interface State {
  accounts: BalanceAccountWithCategory[];
  categories: BalanceCategory[];
  includeArchived: boolean;
  isLoading: boolean;
  isSaving: boolean;
  error: string | null;
  /** Stable error code for UIs that want to localize via i18n (e.g. seed protection). */
  errorCode: string | null;
}

type Action =
  | { type: "SET_LOADING"; payload: boolean }
  | { type: "SET_SAVING"; payload: boolean }
  | { type: "SET_ERROR"; payload: { message: string | null; code: string | null } }
  | {
      type: "SET_DATA";
      payload: {
        accounts: BalanceAccountWithCategory[];
        categories: BalanceCategory[];
      };
    }
  | { type: "SET_INCLUDE_ARCHIVED"; payload: boolean };

function initialState(): State {
  return {
    accounts: [],
    categories: [],
    includeArchived: false,
    isLoading: false,
    isSaving: false,
    error: null,
    errorCode: null,
  };
}

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "SET_LOADING":
      return { ...state, isLoading: action.payload };
    case "SET_SAVING":
      return { ...state, isSaving: action.payload };
    case "SET_ERROR":
      return {
        ...state,
        error: action.payload.message,
        errorCode: action.payload.code,
        isLoading: false,
        isSaving: false,
      };
    case "SET_DATA":
      return {
        ...state,
        accounts: action.payload.accounts,
        categories: action.payload.categories,
        isLoading: false,
        error: null,
        errorCode: null,
      };
    case "SET_INCLUDE_ARCHIVED":
      return { ...state, includeArchived: action.payload };
    default:
      return state;
  }
}

function describeError(e: unknown): { message: string; code: string | null } {
  if (e instanceof BalanceServiceError) {
    return { message: e.message, code: e.code };
  }
  return {
    message: e instanceof Error ? e.message : String(e),
    code: null,
  };
}

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

  const refreshData = useCallback(async (includeArchived: boolean) => {
    const fetchId = ++fetchIdRef.current;
    dispatch({ type: "SET_LOADING", payload: true });
    dispatch({ type: "SET_ERROR", payload: { message: null, code: null } });
    try {
      const [accounts, categories] = await Promise.all([
        listBalanceAccounts({ includeArchived }),
        // Exclude v13-deactivated ex-envelope seeds (tfsa/rrsp) from both the
        // account-form category dropdown AND the category-management list
        // (Issue #203). Existing accounts were re-linked to `other` by v13, so
        // nothing points at a deactivated seed — filtering is safe.
        listBalanceCategories({ includeInactive: false }),
      ]);
      if (fetchId !== fetchIdRef.current) return;
      dispatch({ type: "SET_DATA", payload: { accounts, categories } });
    } catch (e) {
      if (fetchId !== fetchIdRef.current) return;
      dispatch({ type: "SET_ERROR", payload: describeError(e) });
    }
  }, []);

  useEffect(() => {
    refreshData(state.includeArchived);
  }, [state.includeArchived, refreshData]);

  const setIncludeArchived = useCallback((next: boolean) => {
    dispatch({ type: "SET_INCLUDE_ARCHIVED", payload: next });
  }, []);

  // ---------------------------------------------------------------------------
  // Account mutations
  // ---------------------------------------------------------------------------

  const addAccount = useCallback(
    async (input: CreateBalanceAccountInput) => {
      dispatch({ type: "SET_SAVING", payload: true });
      try {
        await createBalanceAccount(input);
        await refreshData(state.includeArchived);
      } catch (e) {
        dispatch({ type: "SET_ERROR", payload: describeError(e) });
        throw e;
      } finally {
        dispatch({ type: "SET_SAVING", payload: false });
      }
    },
    [state.includeArchived, refreshData]
  );

  const editAccount = useCallback(
    async (id: number, input: UpdateBalanceAccountInput) => {
      dispatch({ type: "SET_SAVING", payload: true });
      try {
        await updateBalanceAccount(id, input);
        await refreshData(state.includeArchived);
      } catch (e) {
        dispatch({ type: "SET_ERROR", payload: describeError(e) });
        throw e;
      } finally {
        dispatch({ type: "SET_SAVING", payload: false });
      }
    },
    [state.includeArchived, refreshData]
  );

  const archiveAccount = useCallback(
    async (id: number) => {
      dispatch({ type: "SET_SAVING", payload: true });
      try {
        await archiveBalanceAccount(id);
        await refreshData(state.includeArchived);
      } catch (e) {
        dispatch({ type: "SET_ERROR", payload: describeError(e) });
        throw e;
      } finally {
        dispatch({ type: "SET_SAVING", payload: false });
      }
    },
    [state.includeArchived, refreshData]
  );

  const unarchiveAccount = useCallback(
    async (id: number) => {
      dispatch({ type: "SET_SAVING", payload: true });
      try {
        await unarchiveBalanceAccount(id);
        await refreshData(state.includeArchived);
      } catch (e) {
        dispatch({ type: "SET_ERROR", payload: describeError(e) });
        throw e;
      } finally {
        dispatch({ type: "SET_SAVING", payload: false });
      }
    },
    [state.includeArchived, refreshData]
  );

  // ---------------------------------------------------------------------------
  // Category mutations
  // ---------------------------------------------------------------------------

  /**
   * Issue #138 keeps the AccountsPage Categories tab to user-created
   * `simple` kind only. The priced creation UI lands in #140 — until then,
   * callers should pass kind = 'simple'.
   */
  const addCategory = useCallback(
    async (input: CreateBalanceCategoryInput) => {
      const kind: BalanceCategoryKind = input.kind ?? "simple";
      dispatch({ type: "SET_SAVING", payload: true });
      try {
        await createBalanceCategory({ ...input, kind });
        await refreshData(state.includeArchived);
      } catch (e) {
        dispatch({ type: "SET_ERROR", payload: describeError(e) });
        throw e;
      } finally {
        dispatch({ type: "SET_SAVING", payload: false });
      }
    },
    [state.includeArchived, refreshData]
  );

  const editCategory = useCallback(
    async (id: number, input: UpdateBalanceCategoryInput) => {
      dispatch({ type: "SET_SAVING", payload: true });
      try {
        await updateBalanceCategory(id, input);
        await refreshData(state.includeArchived);
      } catch (e) {
        dispatch({ type: "SET_ERROR", payload: describeError(e) });
        throw e;
      } finally {
        dispatch({ type: "SET_SAVING", payload: false });
      }
    },
    [state.includeArchived, refreshData]
  );

  const removeCategory = useCallback(
    async (id: number) => {
      dispatch({ type: "SET_SAVING", payload: true });
      try {
        await deleteBalanceCategory(id);
        await refreshData(state.includeArchived);
      } catch (e) {
        dispatch({ type: "SET_ERROR", payload: describeError(e) });
        throw e;
      } finally {
        dispatch({ type: "SET_SAVING", payload: false });
      }
    },
    [state.includeArchived, refreshData]
  );

  return {
    state,
    setIncludeArchived,
    refresh: () => refreshData(state.includeArchived),
    // Account ops
    addAccount,
    editAccount,
    archiveAccount,
    unarchiveAccount,
    // Category ops
    addCategory,
    editCategory,
    removeCategory,
  };
}
