// BalanceAccountsTable — one-row-per-active-account table on /balance.
//
// Issue #141 (Bilan #3) introduced the table with name/category/latest-value/Δ%
// + actions menu. Issue #142 (Bilan #4) adds 4 return columns, computed via
// the Modified Dietz `compute_account_return` Tauri command:
//
//   - 3M (last 90 days)
//   - 1A (last 365 days)
//   - Depuis création (from earliest snapshot date to today)
//   - Non-ajusté (simple `(V_end - V_start) / V_start`, no contribution
//     weighting — shown side-by-side as a sanity check / explanation)
//
// Returns load lazily on mount via `Promise.all` over (account × horizon),
// keyed by `account_id`. Each cell renders "—" while loading and shows the
// `is_partial` / `has_no_transfers_warning` badges via tooltip when set.
//
// Issue #142 also adds a "Lier transferts" item in the per-row actions menu
// that opens `LinkTransfersModal` (the modal handles its own state; this
// component just bubbles up the request via `onLinkTransfers`).

import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import {
  Archive,
  MoreVertical,
  Link as LinkIcon,
  AlertTriangle,
  ChevronDown,
  ChevronRight,
} from "lucide-react";
import type {
  AccountLatestSnapshot,
  AccountPeriodAnchor,
} from "../../services/balance.service";
import { computeAccountReturn } from "../../services/balance.service";
import {
  getPreference,
  setPreference,
} from "../../services/userPreferenceService";
import type { AccountReturn } from "../../shared/types";
import { renderCategoryLabelFromAccount } from "../../utils/renderCategoryLabel";

/**
 * Preference key persisting whether the 4 Modified-Dietz return columns are
 * expanded. Absent/anything-but-"1" → collapsed (the spec default). Stored via
 * `userPreferenceService` so the choice survives across sessions (Issue #204).
 */
const SHOW_RETURNS_PREF_KEY = "balance_show_returns";

const cadFormatter = (locale: string) =>
  new Intl.NumberFormat(locale, {
    style: "currency",
    currency: "CAD",
    maximumFractionDigits: 2,
  });

/** Horizon definition: how many days back from today to start the period. */
type HorizonKey = "3M" | "1A" | "since";

interface HorizonRange {
  key: HorizonKey;
  /** ISO date for `period_start`. */
  from: string;
  /** ISO date for `period_end` (always today, computed in the local civil day). */
  to: string;
}

function localISO(d: Date): string {
  const yy = d.getFullYear();
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  return `${yy}-${mm}-${dd}`;
}

function isoDaysAgo(days: number, today: Date): string {
  const d = new Date(today);
  d.setDate(d.getDate() - days);
  return localISO(d);
}

interface BalanceAccountsTableProps {
  accounts: AccountLatestSnapshot[];
  periodAnchor: AccountPeriodAnchor[];
  onArchiveAccount?: (account: AccountLatestSnapshot) => void;
  onLinkTransfers?: (account: AccountLatestSnapshot) => void;
  /**
   * Earliest snapshot date across the whole profile, used to anchor the
   * "depuis création" horizon. Falls back to "1A" range if not provided
   * (avoids triggering computation against the unix epoch).
   */
  sinceCreationDate?: string | null;
}

/**
 * Per-account, per-horizon return — shape used by the local cache state.
 * Indexed `[accountId][horizonKey]`.
 */
type ReturnsByAccount = Record<number, Partial<Record<HorizonKey, AccountReturn>>>;

export default function BalanceAccountsTable({
  accounts,
  periodAnchor,
  onArchiveAccount,
  onLinkTransfers,
  sinceCreationDate,
}: BalanceAccountsTableProps) {
  const { t, i18n } = useTranslation();
  const fmt = cadFormatter(i18n.language === "fr" ? "fr-CA" : "en-CA");

  /** account_id → period anchor (start-of-period value). */
  const anchorMap = useMemo(() => {
    const m = new Map<number, AccountPeriodAnchor>();
    for (const a of periodAnchor) m.set(a.account_id, a);
    return m;
  }, [periodAnchor]);

  const [openMenuFor, setOpenMenuFor] = useState<number | null>(null);

  // Progressive disclosure of the 4 return columns (Issue #204). Collapsed by
  // default; the persisted choice is read once on mount. We start `false` so
  // the columns never flash open before the preference resolves.
  const [showReturns, setShowReturns] = useState(false);
  useEffect(() => {
    let cancelled = false;
    void (async () => {
      try {
        const stored = await getPreference(SHOW_RETURNS_PREF_KEY);
        if (!cancelled && stored === "1") setShowReturns(true);
      } catch {
        // Pref read failure: keep the collapsed default.
      }
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  const toggleReturns = () => {
    setShowReturns((prev) => {
      const next = !prev;
      // Best-effort persist; a write failure just means the next session
      // falls back to the collapsed default.
      void setPreference(SHOW_RETURNS_PREF_KEY, next ? "1" : "0").catch(
        () => {}
      );
      return next;
    });
  };

  // Returns cache. Cleared whenever the account list changes (new accounts,
  // archive, etc.). Loaded lazily after mount — only while the columns are
  // shown, so a collapsed table never runs the Modified-Dietz computation.
  const [returns, setReturns] = useState<ReturnsByAccount>({});
  const [returnsLoading, setReturnsLoading] = useState(false);

  // Horizon definitions — recomputed once per mount via today's local civil
  // day. We don't memoize against `accounts` because the dates don't depend
  // on the row list.
  const horizons = useMemo<HorizonRange[]>(() => {
    const today = new Date();
    const todayISO = localISO(today);
    const sinceFrom = sinceCreationDate ?? isoDaysAgo(365, today);
    return [
      { key: "3M", from: isoDaysAgo(90, today), to: todayISO },
      { key: "1A", from: isoDaysAgo(365, today), to: todayISO },
      { key: "since", from: sinceFrom, to: todayISO },
    ];
  }, [sinceCreationDate]);

  useEffect(() => {
    let cancelled = false;
    async function loadReturns() {
      if (!showReturns || accounts.length === 0) {
        setReturns({});
        return;
      }
      setReturnsLoading(true);
      const next: ReturnsByAccount = {};
      // Run sequentially per account to avoid SQLite contention; per-horizon
      // we can parallelize because they hit the same table set.
      await Promise.all(
        accounts.map(async (acc) => {
          next[acc.account_id] = {};
          const tasks = horizons.map(async (h) => {
            try {
              const r = await computeAccountReturn(
                acc.account_id,
                h.from,
                h.to
              );
              next[acc.account_id]![h.key] = r;
            } catch {
              // Per-cell failure: leave the slot undefined → renders "—".
            }
          });
          await Promise.all(tasks);
        })
      );
      if (!cancelled) {
        setReturns(next);
        setReturnsLoading(false);
      }
    }
    void loadReturns();
    return () => {
      cancelled = true;
    };
  }, [accounts, horizons, showReturns]);

  if (accounts.length === 0) {
    return (
      <div className="bg-[var(--card)] border border-[var(--border)] rounded-xl p-8 text-center text-[var(--muted-foreground)] italic">
        {t("balance.overview.noAccounts")}
      </div>
    );
  }

  /** Format a return percentage with sign + colour-aware classname. */
  function renderReturnCell(r: AccountReturn | undefined) {
    if (!r) {
      return <span className="text-[var(--muted-foreground)]">—</span>;
    }
    if (r.return_pct === null) {
      return (
        <span
          className="text-[var(--muted-foreground)] inline-flex items-center gap-1"
          title={t("balance.returns.partialTooltip")}
        >
          <AlertTriangle size={12} />
          —
        </span>
      );
    }
    const pct = r.return_pct * 100;
    return (
      <span className="inline-flex items-center gap-1">
        <span
          className={
            pct >= 0 ? "text-[var(--positive)]" : "text-[var(--negative)]"
          }
        >
          {pct >= 0 ? "+" : ""}
          {pct.toFixed(2)}%
        </span>
        {r.has_no_transfers_warning && (
          <AlertTriangle
            size={12}
            className="text-amber-500"
            aria-label={t("balance.returns.noTransfersWarning")}
          />
        )}
      </span>
    );
  }

  /**
   * Unadjusted (simple) return = `(value_end - value_start) / value_start`
   * — same numbers Modified Dietz already returns when no flows exist, but
   * this column shows the simple version for ALL accounts as a side-by-side
   * sanity check. Computed from the same `AccountReturn` payload (uses the
   * `value_start` / `value_end` fields filled by the Rust side).
   */
  function renderUnadjustedCell(r: AccountReturn | undefined) {
    if (!r || r.value_start === null || r.value_end === null) {
      return <span className="text-[var(--muted-foreground)]">—</span>;
    }
    if (r.value_start === 0) {
      return <span className="text-[var(--muted-foreground)]">—</span>;
    }
    const simple = ((r.value_end - r.value_start) / r.value_start) * 100;
    return (
      <span
        className={
          simple >= 0 ? "text-[var(--positive)]" : "text-[var(--negative)]"
        }
      >
        {simple >= 0 ? "+" : ""}
        {simple.toFixed(2)}%
      </span>
    );
  }

  return (
    <div className="space-y-2">
      <div className="flex justify-end">
        <button
          type="button"
          onClick={toggleReturns}
          aria-expanded={showReturns}
          className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-lg border border-[var(--border)] bg-[var(--card)] text-[var(--foreground)] hover:bg-[var(--muted)]/40"
        >
          {showReturns ? (
            <ChevronDown size={14} />
          ) : (
            <ChevronRight size={14} />
          )}
          {t(
            showReturns
              ? "balance.accountsTable.toggleReturns.hide"
              : "balance.accountsTable.toggleReturns.show"
          )}
        </button>
      </div>
      <div className="bg-[var(--card)] border border-[var(--border)] rounded-xl overflow-x-auto">
        <table className="w-full text-sm">
          <thead className="bg-[var(--muted)]/30">
          <tr>
            <th className="text-left px-4 py-3 font-medium">
              {t("balance.account.fields.name")}
            </th>
            <th className="text-left px-4 py-3 font-medium">
              {t("balance.account.fields.category")}
            </th>
            <th className="text-right px-4 py-3 font-medium">
              {t("balance.overview.latestValue")}
            </th>
            <th className="text-right px-4 py-3 font-medium">
              {t("balance.overview.periodDelta")}
            </th>
            {showReturns && (
              <>
                <th className="text-right px-4 py-3 font-medium" title={t("balance.accountsTable.return3mTooltip")}>
                  {t("balance.accountsTable.return3m")}
                </th>
                <th className="text-right px-4 py-3 font-medium" title={t("balance.accountsTable.return1yTooltip")}>
                  {t("balance.accountsTable.return1y")}
                </th>
                <th className="text-right px-4 py-3 font-medium" title={t("balance.accountsTable.sinceCreationTooltip")}>
                  {t("balance.accountsTable.sinceCreation")}
                </th>
                <th className="text-right px-4 py-3 font-medium" title={t("balance.accountsTable.unadjustedTooltip")}>
                  {t("balance.accountsTable.unadjusted")}
                </th>
              </>
            )}
            <th className="text-right px-4 py-3 font-medium w-12">
              {t("balance.account.fields.actions")}
            </th>
          </tr>
        </thead>
        <tbody>
          {accounts.map((acc) => {
            const anchor = anchorMap.get(acc.account_id);
            const deltaPct =
              acc.latest_value !== null && anchor && anchor.anchor_value !== 0
                ? ((acc.latest_value - anchor.anchor_value) /
                    Math.abs(anchor.anchor_value)) *
                  100
                : null;
            const accReturns = returns[acc.account_id] ?? {};
            return (
              <tr
                key={acc.account_id}
                className="border-t border-[var(--border)] hover:bg-[var(--muted)]/10"
              >
                <td className="px-4 py-3 font-medium">
                  {acc.account_name}
                  {acc.symbol ? (
                    <span className="ml-2 text-xs text-[var(--muted-foreground)]">
                      ({acc.symbol})
                    </span>
                  ) : null}
                </td>
                <td className="px-4 py-3 text-[var(--muted-foreground)]">
                  {renderCategoryLabelFromAccount(acc, t)}
                </td>
                <td className="px-4 py-3 text-right tabular-nums">
                  {acc.latest_value !== null ? fmt.format(acc.latest_value) : "—"}
                </td>
                <td className="px-4 py-3 text-right tabular-nums">
                  {deltaPct !== null ? (
                    <span
                      className={
                        deltaPct >= 0
                          ? "text-[var(--positive)]"
                          : "text-[var(--negative)]"
                      }
                    >
                      {deltaPct >= 0 ? "+" : ""}
                      {deltaPct.toFixed(2)}%
                    </span>
                  ) : (
                    "—"
                  )}
                </td>
                {showReturns && (
                  <>
                    <td className="px-4 py-3 text-right tabular-nums">
                      {returnsLoading && !accReturns["3M"]
                        ? "…"
                        : renderReturnCell(accReturns["3M"])}
                    </td>
                    <td className="px-4 py-3 text-right tabular-nums">
                      {returnsLoading && !accReturns["1A"]
                        ? "…"
                        : renderReturnCell(accReturns["1A"])}
                    </td>
                    <td className="px-4 py-3 text-right tabular-nums">
                      {returnsLoading && !accReturns["since"]
                        ? "…"
                        : renderReturnCell(accReturns["since"])}
                    </td>
                    <td className="px-4 py-3 text-right tabular-nums">
                      {returnsLoading && !accReturns["1A"]
                        ? "…"
                        : renderUnadjustedCell(accReturns["1A"])}
                    </td>
                  </>
                )}
                <td className="px-4 py-3 text-right relative">
                  <button
                    type="button"
                    onClick={() =>
                      setOpenMenuFor(
                        openMenuFor === acc.account_id ? null : acc.account_id
                      )
                    }
                    className="p-1 rounded hover:bg-[var(--muted)]/40"
                    aria-label={t("balance.account.fields.actions")}
                  >
                    <MoreVertical size={16} />
                  </button>
                  {openMenuFor === acc.account_id && (
                    <div className="absolute right-2 top-full z-10 mt-1 bg-[var(--card)] border border-[var(--border)] rounded-lg shadow-md py-1 min-w-[180px] text-left">
                      <button
                        type="button"
                        disabled
                        className="block w-full px-3 py-2 text-sm text-[var(--muted-foreground)] cursor-not-allowed"
                        title={t("balance.overview.detailComingSoon")}
                      >
                        {t("balance.overview.detailAction")}
                      </button>
                      {onLinkTransfers && (
                        <button
                          type="button"
                          onClick={() => {
                            setOpenMenuFor(null);
                            onLinkTransfers(acc);
                          }}
                          className="flex items-center gap-2 w-full px-3 py-2 text-sm hover:bg-[var(--muted)]/40"
                        >
                          <LinkIcon size={14} />
                          {t("balance.transfers.linkAction")}
                        </button>
                      )}
                      <button
                        type="button"
                        onClick={() => {
                          setOpenMenuFor(null);
                          onArchiveAccount?.(acc);
                        }}
                        className="flex items-center gap-2 w-full px-3 py-2 text-sm hover:bg-[var(--muted)]/40"
                      >
                        <Archive size={14} />
                        {t("balance.account.actions.archive")}
                      </button>
                    </div>
                  )}
                </td>
              </tr>
            );
          })}
        </tbody>
        </table>
      </div>
    </div>
  );
}
