// BalancePage — overview of net worth at `/balance`.
//
// Issue #141 (Bilan #3). Composes:
//   - BalanceOverviewCard (latest total + Δ% + staleness warning + new-snapshot CTA)
//   - Period selector (3M / 6M / 1A / 3A / Tout)
//   - Chart-mode toggle (Line / Stacked-by-category)
//   - BalanceEvolutionChart
//   - BalanceAccountsTable (one row per active account with latest value + Δ%)
//
// All data flows through `useBalanceOverview` (scoped useReducer). Returns
// (Modified Dietz) are deferred to Issue #142 — the accounts table reserves
// columns with a TODO comment.

import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Wallet, FilePlus } from "lucide-react";
import {
  useBalanceOverview,
  type BalancePeriod,
  type BalanceChartMode,
  type BalanceGroupAxis,
} from "../hooks/useBalanceOverview";
import { BALANCE_VEHICLE_TYPES } from "../shared/types";
import { VEHICLE_NONE_BUCKET } from "../services/balance.service";
import {
  archiveBalanceAccount,
  listAccountTransfers,
  type AccountLatestSnapshot,
} from "../services/balance.service";
import { getAllCategories } from "../services/transactionService";
import type { Category, BalanceAccountTransferWithTransaction } from "../shared/types";
import BalanceOverviewCard from "../components/balance/BalanceOverviewCard";
import BalanceEvolutionChart from "../components/balance/BalanceEvolutionChart";
import BalanceAccountsTable from "../components/balance/BalanceAccountsTable";
import LinkTransfersModal from "../components/balance/LinkTransfersModal";
import DetailAccountWizard from "../components/balance/DetailAccountWizard";
import StarterAccountsModal from "../components/balance/StarterAccountsModal";
import HubReportNavCard, {
  type HubReportNavCardProps,
} from "../components/reports/HubReportNavCard";
import { deriveLandingState } from "../components/balance/balanceLanding";
import { getPreference, setPreference } from "../services/userPreferenceService";
import { renderCategoryLabelFromAccount } from "../utils/renderCategoryLabel";

const STARTER_PREF_KEY = "balance_starter_proposed";

const PERIOD_OPTIONS: BalancePeriod[] = ["3M", "6M", "1A", "3A", "all"];

export default function BalancePage() {
  const { t } = useTranslation();
  const { state, setPeriod, setChartMode, setGroupAxis, reload } =
    useBalanceOverview();

  // Issue #142 — link-transfers modal state. Categories list is loaded once
  // on mount (used by the modal's filter dropdown).
  const [linkTarget, setLinkTarget] = useState<AccountLatestSnapshot | null>(
    null
  );
  // Issue #215 — "détailler en titres" wizard target (a simple account being
  // flipped to detailed entry mode).
  const [detailTarget, setDetailTarget] =
    useState<AccountLatestSnapshot | null>(null);
  const [categories, setCategories] = useState<Category[]>([]);
  const [transfersByAccount, setTransfersByAccount] = useState<
    Map<number, BalanceAccountTransferWithTransaction[]>
  >(new Map());

  useEffect(() => {
    void getAllCategories().then(setCategories).catch(() => setCategories([]));
  }, []);

  // Issue #179 — one-shot starter-accounts modal for existing profiles. The
  // pref `balance_starter_proposed` is written once (confirmed or dismissed),
  // so the modal never re-appears. New profiles get both the 4 starters AND
  // the pref pre-seeded via consolidated_schema.sql, so they never hit this
  // branch at all (S1 fix from #187).
  const [showStarterModal, setShowStarterModal] = useState(false);
  useEffect(() => {
    let cancelled = false;
    void (async () => {
      try {
        const existing = await getPreference(STARTER_PREF_KEY);
        if (!cancelled && existing == null) {
          setShowStarterModal(true);
        }
      } catch {
        // Pref read failure: leave modal hidden — privacy-first default.
      }
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  const handleStarterModalClose = async (acceptedIds: number[]) => {
    setShowStarterModal(false);
    try {
      await setPreference(
        STARTER_PREF_KEY,
        JSON.stringify({
          shown_at: new Date().toISOString(),
          accepted: acceptedIds,
        })
      );
    } catch {
      // Best-effort: a write failure here would cause the modal to re-show
      // on next visit, which is acceptable (data still consistent).
    }
    if (acceptedIds.length > 0) {
      await reload();
    }
  };

  // Refresh per-account transfer lists used by the chart markers. Keyed by
  // account_id → [transfers]. Used by `BalanceEvolutionChart` to plot
  // ReferenceLine markers (green for in, red for out).
  useEffect(() => {
    let cancelled = false;
    async function run() {
      const map = new Map<number, BalanceAccountTransferWithTransaction[]>();
      await Promise.all(
        state.accountsLatest.map(async (acc) => {
          try {
            const list = await listAccountTransfers(acc.account_id);
            map.set(acc.account_id, list);
          } catch {
            map.set(acc.account_id, []);
          }
        })
      );
      if (!cancelled) setTransfersByAccount(map);
    }
    void run();
    return () => {
      cancelled = true;
    };
  }, [state.accountsLatest]);

  const allTransferMarkers = useMemo(() => {
    const flat: BalanceAccountTransferWithTransaction[] = [];
    for (const list of transfersByAccount.values()) flat.push(...list);
    return flat;
  }, [transfersByAccount]);

  // Earliest snapshot date in the dataset, used to anchor the "depuis
  // création" Modified Dietz horizon in the accounts table.
  const earliestSnapshotDate = useMemo(() => {
    if (state.evolutionTotals.length === 0) return null;
    return state.evolutionTotals[0].snapshot_date;
  }, [state.evolutionTotals]);

  // Build a category_key → translated label map from the accounts payload —
  // the byCategory series is keyed by `key`, not by id, and the same
  // taxonomy is already loaded with `accountsLatest` joins.
  const categoryLabels = useMemo(() => {
    const m: Record<string, string> = {};
    for (const a of state.accountsLatest) {
      if (!m[a.category_key]) {
        m[a.category_key] = renderCategoryLabelFromAccount(a, t);
      }
    }
    return m;
  }, [state.accountsLatest, t]);

  // Map vehicle_key → translated label for the "par enveloppe" stacked axis.
  // Reuses the #203 account-form labels (`balance.account.form.vehicleType.*`)
  // so the legend never duplicates strings; the NULL-envelope bucket uses the
  // dedicated `balance.vehicle.none` key.
  const vehicleLabels = useMemo(() => {
    const m: Record<string, string> = {
      [VEHICLE_NONE_BUCKET]: t("balance.vehicle.none"),
    };
    for (const v of BALANCE_VEHICLE_TYPES) {
      m[v] = t(`balance.account.form.vehicleType.${v}`);
    }
    return m;
  }, [t]);

  const handleArchiveAccount = async (accountId: number) => {
    try {
      await archiveBalanceAccount(accountId);
      await reload();
    } catch {
      // Reload swallows; the row simply stays. UX feedback can be added later.
    }
  };

  return (
    <div className={state.isLoading ? "opacity-60 pointer-events-none" : ""}>
      <div className="flex items-center gap-3 mb-6">
        <Wallet size={24} className="text-[var(--primary)]" />
        <h1 className="text-2xl font-bold">{t("balance.overview.title")}</h1>
      </div>

      {state.error && (
        <div className="mb-4 p-3 rounded-lg bg-[var(--negative)]/10 text-[var(--negative)] text-sm border border-[var(--negative)]/20">
          {state.error}
        </div>
      )}

      {/* Issue #244 — landing state. We probe accountsLatest for ANY snapshot
          date (independent of the active period filter, state.period) and
          derive one of three states via `deriveLandingState`. The old boolean
          guard conflated "no account" with "accounts but no snapshot" and
          stranded the latter on a snapshot-only onboarding card, leaving the
          account-management page (/balance/accounts) unreachable. We now render
          navigation tiles (HubReportNavCard, modeled on the Reports hub) so
          account management is reachable in every state, and only the
          "populated" state (>=1 account WITH a snapshot) renders the full
          dashboard — otherwise period selector, chart and accounts table would
          all stack empty states (S2 from #187). */}
      {(() => {
        const accountsCount = state.accountsLatest.length;
        const hasAnySnapshot = state.accountsLatest.some(
          (a) => a.latest_snapshot_date != null
        );
        const landingState = deriveLandingState(accountsCount, hasAnySnapshot);

        // Navigation tiles reused across states. "Manage accounts" is present
        // everywhere so /balance/accounts is reachable at all times (#244); the
        // snapshot tile is withheld in the empty state (nothing to snapshot).
        const accountsTile: HubReportNavCardProps = {
          to: "/balance/accounts",
          icon: <Wallet size={24} />,
          title: t("balance.hub.accounts.title"),
          description: t("balance.hub.accounts.description"),
        };
        const snapshotTile: HubReportNavCardProps = {
          to: "/balance/snapshot",
          icon: <FilePlus size={24} />,
          title: t("balance.hub.snapshot.title"),
          description: t("balance.hub.snapshot.description"),
        };
        const renderHub = (
          tiles: HubReportNavCardProps[],
          headingKey: string
        ) => (
          <section>
            <h2 className="text-sm font-semibold uppercase tracking-wide text-[var(--muted-foreground)] mb-3">
              {t(headingKey)}
            </h2>
            <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
              {tiles.map((card) => (
                <HubReportNavCard key={card.to} {...card} />
              ))}
            </div>
          </section>
        );

        if (landingState === "empty") {
          // No account yet. StarterAccountsModal auto-opens (one-shot, #179);
          // once dismissed the "Manage accounts" tile keeps account creation
          // reachable. The snapshot tile is withheld — nothing to snapshot yet.
          return (
            <div className="space-y-6">
              <div className="bg-[var(--card)] rounded-xl border border-[var(--border)] p-6">
                <h2 className="text-lg font-semibold mb-1">
                  {t("balance.landing.empty.title")}
                </h2>
                <p className="text-sm text-[var(--muted-foreground)]">
                  {t("balance.landing.empty.subtitle")}
                </p>
              </div>
              {renderHub([accountsTile], "balance.hub.getStarted")}
            </div>
          );
        }

        if (landingState === "accounts-no-snapshot") {
          // Accounts exist but no snapshot. Offer BOTH paths (manage accounts
          // OR enter a snapshot) without forcing the snapshot flow (#244).
          return (
            <div className="space-y-6">
              <div className="bg-[var(--card)] rounded-xl border border-[var(--border)] p-6">
                <h2 className="text-lg font-semibold mb-1">
                  {t("balance.landing.noSnapshot.title")}
                </h2>
                <p className="text-sm text-[var(--muted-foreground)]">
                  {t("balance.landing.noSnapshot.subtitle")}
                </p>
              </div>
              {renderHub([accountsTile, snapshotTile], "balance.hub.getStarted")}
            </div>
          );
        }

        return (
          <div className="space-y-6">
            <BalanceOverviewCard
              totals={state.evolutionTotals}
              latentGainRollup={state.latentGainRollup}
            />

            <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
              {/* Period selector */}
              <div
                role="group"
                aria-label={t("balance.period.legend")}
                className="inline-flex rounded-lg border border-[var(--border)] overflow-hidden"
              >
                {PERIOD_OPTIONS.map((p) => (
                  <button
                    key={p}
                    type="button"
                    onClick={() => setPeriod(p)}
                    className={`px-3 py-1.5 text-sm font-medium ${
                      state.period === p
                        ? "bg-[var(--primary)] text-white"
                        : "bg-[var(--card)] text-[var(--foreground)] hover:bg-[var(--muted)]/40"
                    }`}
                    aria-pressed={state.period === p}
                  >
                    {t(`balance.period.${p}`)}
                  </button>
                ))}
              </div>

              <div className="flex flex-wrap items-center gap-3">
                {/* Stacked-mode group-axis sub-toggle (asset class / envelope).
                    Only meaningful while the stacked mode is active. */}
                {state.chartMode === "stacked" && (
                  <div
                    role="group"
                    aria-label={t("balance.chart.axisLegend")}
                    className="inline-flex rounded-lg border border-[var(--border)] overflow-hidden"
                  >
                    {(["class", "vehicle"] as BalanceGroupAxis[]).map((axis) => (
                      <button
                        key={axis}
                        type="button"
                        onClick={() => setGroupAxis(axis)}
                        className={`px-3 py-1.5 text-sm font-medium ${
                          state.groupAxis === axis
                            ? "bg-[var(--primary)] text-white"
                            : "bg-[var(--card)] text-[var(--foreground)] hover:bg-[var(--muted)]/40"
                        }`}
                        aria-pressed={state.groupAxis === axis}
                      >
                        {t(
                          axis === "class"
                            ? "balance.chart.axis.byAssetClass"
                            : "balance.chart.axis.byVehicle"
                        )}
                      </button>
                    ))}
                  </div>
                )}

                {/* Chart mode toggle */}
                <div
                  role="group"
                  aria-label={t("balance.chart.modeLegend")}
                  className="inline-flex rounded-lg border border-[var(--border)] overflow-hidden"
                >
                  {(["line", "stacked"] as BalanceChartMode[]).map((mode) => (
                    <button
                      key={mode}
                      type="button"
                      onClick={() => setChartMode(mode)}
                      className={`px-3 py-1.5 text-sm font-medium ${
                        state.chartMode === mode
                          ? "bg-[var(--primary)] text-white"
                          : "bg-[var(--card)] text-[var(--foreground)] hover:bg-[var(--muted)]/40"
                      }`}
                      aria-pressed={state.chartMode === mode}
                    >
                      {t(`balance.chart.mode.${mode}`)}
                    </button>
                  ))}
                </div>
              </div>
            </div>

            <BalanceEvolutionChart
              mode={state.chartMode}
              groupAxis={state.groupAxis}
              totals={state.evolutionTotals}
              byCategory={state.evolutionByCategory}
              byVehicle={state.evolutionByVehicle}
              categoryLabels={categoryLabels}
              vehicleLabels={vehicleLabels}
              transferMarkers={allTransferMarkers}
            />

            <div>
              <h2 className="text-lg font-semibold mb-3">
                {t("balance.overview.accountsTitle")}
              </h2>
              <BalanceAccountsTable
                accounts={state.accountsLatest}
                periodAnchor={state.accountsPeriodAnchor}
                sinceCreationDate={earliestSnapshotDate}
                latentGainByAccount={state.latentGainByAccount}
                latentGainRollup={state.latentGainRollup}
                vehicleLabels={vehicleLabels}
                onArchiveAccount={(acc) => handleArchiveAccount(acc.account_id)}
                onLinkTransfers={(acc) => setLinkTarget(acc)}
                onDetailAccount={(acc) => setDetailTarget(acc)}
              />
            </div>

            {renderHub([accountsTile, snapshotTile], "balance.hub.manage")}
          </div>
        );
      })()}

      <StarterAccountsModal
        isOpen={showStarterModal}
        onClose={(ids) => {
          void handleStarterModalClose(ids);
        }}
      />

      {linkTarget && (
        <LinkTransfersModal
          accountId={linkTarget.account_id}
          accountName={linkTarget.account_name}
          categories={categories}
          onClose={() => setLinkTarget(null)}
          onLinked={() => {
            void reload();
          }}
        />
      )}

      {detailTarget && (
        <DetailAccountWizard
          accountId={detailTarget.account_id}
          accountName={detailTarget.account_name}
          onClose={() => setDetailTarget(null)}
          onDetailed={() => {
            void reload();
          }}
        />
      )}
    </div>
  );
}
