import { useState, useRef, useEffect, Fragment } from "react";
import { useTranslation } from "react-i18next";
import { AlertTriangle, ArrowUpDown, ChevronDown, ChevronRight, ChevronsDownUp, ChevronsUpDown } from "lucide-react";
import type { BudgetYearRow } from "../../shared/types";
import { reorderRows } from "../../utils/reorderRows";
import type { CollapseAccessors } from "../../utils/collapsibleRows";
import { useCollapsibleGroups } from "../../hooks/useCollapsibleGroups";
import { computeBudgetResults, sumLeavesForType, type BudgetTotals } from "./budgetTableResults";

const fmt = new Intl.NumberFormat("en-CA", {
  style: "currency",
  currency: "CAD",
  minimumFractionDigits: 0,
  maximumFractionDigits: 0,
});

const MONTH_KEYS = [
  "months.jan", "months.feb", "months.mar", "months.apr",
  "months.may", "months.jun", "months.jul", "months.aug",
  "months.sep", "months.oct", "months.nov", "months.dec",
] as const;

// Prefixed so the "subtotals on top/bottom" preference is INDEPENDENT of the
// real-vs-budget report's (BudgetVsActualTable still uses "subtotals-position") —
// they collided before (issue #289).
const STORAGE_KEY = "budget-subtotals-position";

// Collapse groups keyed by category id; a row is hidden when any ancestor
// (walking parent_id) is collapsed, so every parent level is collapsible.
// Declared inline, mirroring ComparePeriodTable / BudgetVsActualTable (issue #289).
const BUDGET_COLLAPSE_ACCESSORS: CollapseAccessors<BudgetYearRow> = {
  keyOf: (row) => `p:${row.category_id}`,
  parentKeyOf: (row) => (row.parent_id != null ? `p:${row.parent_id}` : null),
  depthOf: (row) => row.depth ?? 0,
  isParent: (row) => row.is_parent,
};

const BUDGET_EXPANDED_KEY = "budget-grid-expanded";

interface BudgetTableProps {
  rows: BudgetYearRow[];
  onUpdatePlanned: (categoryId: number, month: number, amount: number) => void;
  onSplitEvenly: (categoryId: number, annualAmount: number) => void;
}

export default function BudgetTable({ rows, onUpdatePlanned, onSplitEvenly }: BudgetTableProps) {
  const { t } = useTranslation();
  const [editingCell, setEditingCell] = useState<{ categoryId: number; monthIdx: number } | null>(null);
  const [editingAnnual, setEditingAnnual] = useState<{ categoryId: number } | null>(null);
  const [editingValue, setEditingValue] = useState("");
  const [subtotalsOnTop, setSubtotalsOnTop] = useState(() => {
    const stored = localStorage.getItem(STORAGE_KEY);
    return stored === null ? true : stored === "top";
  });

  const toggleSubtotals = () => {
    setSubtotalsOnTop((prev) => {
      const next = !prev;
      localStorage.setItem(STORAGE_KEY, next ? "top" : "bottom");
      return next;
    });
  };

  // Collapse/expand at every hierarchy level — collapsed by default (issue #289),
  // matching the hierarchical reports. Persisted per profile in user_preferences.
  const groups = useCollapsibleGroups<BudgetYearRow>(
    BUDGET_EXPANDED_KEY,
    BUDGET_COLLAPSE_ACCESSORS,
    { defaultExpanded: false },
  );

  const inputRef = useRef<HTMLInputElement>(null);
  const annualInputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (editingCell && inputRef.current) {
      inputRef.current.focus();
      inputRef.current.select();
    }
  }, [editingCell]);

  useEffect(() => {
    if (editingAnnual && annualInputRef.current) {
      annualInputRef.current.focus();
      annualInputRef.current.select();
    }
  }, [editingAnnual]);

  const handleStartEdit = (categoryId: number, monthIdx: number, currentValue: number) => {
    setEditingAnnual(null);
    setEditingCell({ categoryId, monthIdx });
    setEditingValue(currentValue === 0 ? "" : String(currentValue));
  };

  const handleStartEditAnnual = (categoryId: number, currentValue: number) => {
    setEditingCell(null);
    setEditingAnnual({ categoryId });
    setEditingValue(currentValue === 0 ? "" : String(currentValue));
  };

  const handleSave = () => {
    if (!editingCell) return;
    const amount = parseFloat(editingValue) || 0;
    onUpdatePlanned(editingCell.categoryId, editingCell.monthIdx + 1, amount);
    setEditingCell(null);
  };

  const handleSaveAnnual = () => {
    if (!editingAnnual) return;
    const amount = parseFloat(editingValue) || 0;
    onSplitEvenly(editingAnnual.categoryId, amount);
    setEditingAnnual(null);
  };

  const handleCancel = () => {
    setEditingCell(null);
    setEditingAnnual(null);
  };

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Enter") handleSave();
    if (e.key === "Escape") handleCancel();
    if (e.key === "Tab") {
      e.preventDefault();
      if (!editingCell) return;
      const amount = parseFloat(editingValue) || 0;
      onUpdatePlanned(editingCell.categoryId, editingCell.monthIdx + 1, amount);
      // Move to next month cell
      const nextMonth = editingCell.monthIdx + (e.shiftKey ? -1 : 1);
      if (nextMonth >= 0 && nextMonth < 12) {
        const row = rows.find((r) => r.category_id === editingCell.categoryId && !r.is_parent);
        if (row) {
          handleStartEdit(editingCell.categoryId, nextMonth, row.months[nextMonth]);
        }
      } else {
        setEditingCell(null);
      }
    }
  };

  const handleAnnualKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Enter") handleSaveAnnual();
    if (e.key === "Escape") handleCancel();
  };

  // Sign multiplier: expenses negative, income/transfer positive
  const signFor = (type: string) => (type === "expense" ? -1 : 1);

  // Group rows by type
  const grouped: Record<string, BudgetYearRow[]> = {};
  for (const row of rows) {
    const key = row.category_type;
    if (!grouped[key]) grouped[key] = [];
    grouped[key].push(row);
  }

  // Income-statement reading order: revenue first, then expenses, then
  // transfers (Issue #278) — matches the compare/trend reports' ordering.
  const typeOrder = ["income", "expense", "transfer"] as const;
  const typeLabelKeys: Record<string, string> = {
    expense: "budget.expenses",
    income: "budget.income",
    transfer: "budget.transfers",
  };
  const typeTotalKeys: Record<string, string> = {
    expense: "budget.totalExpenses",
    income: "budget.totalIncome",
    transfer: "budget.totalTransfers",
  };

  // Income-statement roll-up (Issue #278): Résultat avant transferts / net,
  // computed on the budgeted + previous-year-actual leaves. Mathematically
  // equivalent to the old plain grand-total (income − expense + transfer over
  // every leaf), now surfaced as the two labeled result rows below instead of
  // a single unlabeled "Total".
  const results = computeBudgetResults(rows);

  const totalCols = 15; // category + prev year + annual + 12 months

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

  const formatSigned = (value: number) => {
    if (value === 0) return <span className="text-[var(--muted-foreground)]">—</span>;
    const color = value > 0 ? "text-[var(--positive)]" : "text-[var(--negative)]";
    return <span className={color}>{fmt.format(value)}</span>;
  };

  const renderRow = (row: BudgetYearRow) => {
    const sign = signFor(row.category_type);
    const isChild = row.parent_id !== null && !row.is_parent;
    const depth = row.depth ?? (isChild ? 1 : 0);
    // Unique key: parent rows and "(direct)" fake children can share the same category_id
    const rowKey = row.is_parent ? `parent-${row.category_id}` : `leaf-${row.category_id}-${row.category_name}`;

    if (row.is_parent) {
      // Parent subtotal row: read-only, bold, distinct background. Collapsible
      // at every level (issue #289) — a chevron toggles its subtree.
      const parentDepth = row.depth ?? 0;
      const isTopParent = parentDepth === 0;
      const isIntermediateParent = parentDepth >= 1;
      const collapsed = groups.isCollapsed(row);
      const parentPaddingClass = parentDepth >= 3 ? "pl-20 pr-3" : parentDepth === 2 ? "pl-14 pr-3" : parentDepth === 1 ? "pl-8 pr-3" : "px-3";
      return (
        <tr
          key={rowKey}
          aria-level={parentDepth + 1}
          className={`border-b border-[var(--border)] ${isTopParent ? "bg-[var(--muted)]/30" : "bg-[var(--muted)]/15"}`}
        >
          <td className={`py-2 sticky left-0 z-10 ${isTopParent ? "px-3 bg-[var(--muted)]/30" : `${parentPaddingClass} bg-[var(--muted)]/15`}`}>
            <button
              type="button"
              onClick={() => groups.toggle(row)}
              aria-expanded={!collapsed}
              className="flex items-center gap-2 w-full text-left hover:opacity-80 transition-opacity"
            >
              {collapsed ? (
                <ChevronRight size={14} className="shrink-0 text-[var(--muted-foreground)]" />
              ) : (
                <ChevronDown size={14} className="shrink-0 text-[var(--muted-foreground)]" />
              )}
              <span
                className="w-2.5 h-2.5 rounded-full shrink-0"
                style={{ backgroundColor: row.category_color }}
              />
              <span className={`truncate text-xs ${isIntermediateParent ? "font-medium" : "font-semibold"}`}>{row.category_name}</span>
            </button>
          </td>
          <td className={`py-2 px-2 text-right text-xs ${isIntermediateParent ? "font-medium" : "font-semibold"} text-[var(--muted-foreground)]`}>
            {formatSigned(row.previousYearTotal)}
          </td>
          <td className={`py-2 px-2 text-right text-xs ${isIntermediateParent ? "font-medium" : "font-semibold"}`}>
            {formatSigned(row.annual * sign)}
          </td>
          {row.months.map((val, mIdx) => (
            <td key={mIdx} className={`py-2 px-2 text-right text-xs ${isIntermediateParent ? "font-medium" : "font-semibold"}`}>
              {formatSigned(val * sign)}
            </td>
          ))}
        </tr>
      );
    }

    // Leaf / child row: editable
    return (
      <tr
        key={rowKey}
        aria-level={depth + 1}
        className="border-b border-[var(--border)] last:border-b-0 hover:bg-[var(--muted)]/50 transition-colors"
      >
        {/* Category name - sticky */}
        <td className={`py-2 sticky left-0 bg-[var(--card)] z-10 ${depth >= 3 ? "pl-20 pr-3" : depth === 2 ? "pl-14 pr-3" : depth === 1 ? "pl-8 pr-3" : "px-3"}`}>
          <div className="flex items-center gap-2">
            <span
              className="w-2.5 h-2.5 rounded-full shrink-0"
              style={{ backgroundColor: row.category_color }}
            />
            <span className="truncate text-xs">{row.category_name}</span>
          </div>
        </td>
        {/* Previous year total — read-only */}
        <td className="py-2 px-2 text-right text-[var(--muted-foreground)]">
          <span className="text-xs px-1 py-0.5">
            {formatSigned(row.previousYearTotal)}
          </span>
        </td>
        {/* Annual total — editable */}
        <td className="py-2 px-2 text-right">
          {editingAnnual?.categoryId === row.category_id ? (
            <input
              ref={annualInputRef}
              type="number"
              step="0.01"
              value={editingValue}
              onChange={(e) => setEditingValue(e.target.value)}
              onBlur={handleSaveAnnual}
              onKeyDown={handleAnnualKeyDown}
              className="w-full text-right bg-[var(--background)] border border-[var(--border)] rounded px-1 py-0.5 text-xs focus:outline-none focus:ring-1 focus:ring-[var(--primary)]"
            />
          ) : (
            <div className="flex items-center justify-end gap-1">
              <button
                onClick={() => handleStartEditAnnual(row.category_id, row.annual)}
                title={t("budget.clickToEdit")}
                className="font-medium text-xs hover:text-[var(--primary)] hover:bg-[var(--muted)]/40 transition-colors cursor-pointer rounded px-1 py-0.5"
              >
                {formatSigned(row.annual * sign)}
              </button>
              {(() => {
                const monthSum = row.months.reduce((s, v) => s + v, 0);
                return row.annual !== 0 && Math.abs(row.annual - monthSum) > 0.01 ? (
                  <span title={t("budget.annualMismatch")} className="text-[var(--negative)]">
                    <AlertTriangle size={13} />
                  </span>
                ) : null;
              })()}
            </div>
          )}
        </td>
        {/* 12 month cells */}
        {row.months.map((val, mIdx) => (
          <td key={mIdx} className="py-2 px-2 text-right">
            {editingCell?.categoryId === row.category_id && editingCell.monthIdx === mIdx ? (
              <input
                ref={inputRef}
                type="number"
                step="0.01"
                value={editingValue}
                onChange={(e) => setEditingValue(e.target.value)}
                onBlur={handleSave}
                onKeyDown={handleKeyDown}
                className="w-full text-right bg-[var(--background)] border border-[var(--border)] rounded px-1 py-0.5 text-xs focus:outline-none focus:ring-1 focus:ring-[var(--primary)]"
              />
            ) : (
              <button
                onClick={() => handleStartEdit(row.category_id, mIdx, val)}
                title={t("budget.clickToEdit")}
                className="w-full text-right hover:text-[var(--primary)] hover:bg-[var(--muted)]/40 transition-colors cursor-pointer text-xs rounded px-1 py-0.5"
              >
                {formatSigned(val * sign)}
              </button>
            )}
          </td>
        ))}
      </tr>
    );
  };

  // One type's section: header, its (reorderable) rows, and a leaf-summed
  // subtotal row. Extracted so it can be called for income/expense, then
  // again for transfer once the Résultat rows are interleaved between them.
  const renderTypeSection = (type: (typeof typeOrder)[number]) => {
    const group = grouped[type];
    if (!group || group.length === 0) return null;
    // Section subtotal is summed from the RAW group via the tested
    // `sumLeavesForType` (leaves only, sign applied to budgeted figures), never
    // from the collapse-filtered rows — folding a parent stays purely visual and
    // never moves a total (issue #289).
    const sectionTotals = sumLeavesForType(group, type);
    return (
      <Fragment key={type}>
        <tr>
          <td
            colSpan={totalCols}
            className="py-1.5 px-3 text-xs font-semibold uppercase tracking-wider text-[var(--muted-foreground)] bg-[var(--muted)]"
          >
            {t(typeLabelKeys[type])}
          </td>
        </tr>
        {reorderRows(groups.visible(group), subtotalsOnTop).map((row) => renderRow(row))}
        <tr className="bg-[var(--muted)]/40 border-b border-[var(--border)]">
          <td className="py-2.5 px-3 sticky left-0 bg-[var(--muted)]/40 z-10 text-sm font-semibold">
            {t(typeTotalKeys[type])}
          </td>
          <td className="py-2.5 px-2 text-right text-sm font-semibold text-[var(--muted-foreground)]">{formatSigned(sectionTotals.previousYearTotal)}</td>
          <td className="py-2.5 px-2 text-right text-sm font-semibold">{formatSigned(sectionTotals.annual)}</td>
          {sectionTotals.months.map((total, mIdx) => (
            <td key={mIdx} className="py-2.5 px-2 text-right text-sm font-semibold">
              {formatSigned(total)}
            </td>
          ))}
        </tr>
      </Fragment>
    );
  };

  // A Résultat row (avant-transferts subtotal, or the net bottom line).
  // `strong` mirrors the previous grand-total row's weight (bold, border-t-2);
  // the softer variant mirrors the per-type section-subtotal row above.
  const renderResultRow = (labelKey: string, tot: BudgetTotals, strong: boolean) => {
    const rowClass = strong
      ? "bg-[var(--muted)] font-bold border-t-2 border-[var(--border)]"
      : "bg-[var(--muted)]/40 border-b border-[var(--border)]";
    const stickyBg = strong ? "bg-[var(--muted)]" : "bg-[var(--muted)]/40";
    const cellWeight = strong ? "" : "font-semibold";
    const pad = strong ? "py-3" : "py-2.5";
    return (
      <tr key={labelKey} className={rowClass}>
        <td className={`${pad} px-3 sticky left-0 z-10 text-sm ${stickyBg}`}>{t(labelKey)}</td>
        <td className={`${pad} px-2 text-right text-sm ${cellWeight} text-[var(--muted-foreground)]`}>
          {formatSigned(tot.previousYearTotal)}
        </td>
        <td className={`${pad} px-2 text-right text-sm ${cellWeight}`}>{formatSigned(tot.annual)}</td>
        {tot.months.map((val, mIdx) => (
          <td key={mIdx} className={`${pad} px-2 text-right text-sm ${cellWeight}`}>
            {formatSigned(val)}
          </td>
        ))}
      </tr>
    );
  };

  const hasGroups = groups.groupCount(rows) > 0;
  const allExpanded = groups.allExpanded(rows);

  return (
    <div className="bg-[var(--card)] rounded-xl border border-[var(--border)] overflow-hidden">
      <div className="flex justify-end items-center gap-1 px-3 py-2 border-b border-[var(--border)]">
        {hasGroups && (
          <button
            type="button"
            onClick={() => (allExpanded ? groups.collapseAll(rows) : groups.expandAll(rows))}
            className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium text-[var(--muted-foreground)] hover:bg-[var(--muted)] transition-colors"
          >
            {allExpanded ? <ChevronsDownUp size={13} /> : <ChevronsUpDown size={13} />}
            {allExpanded ? t("reports.collapse.collapseAll") : t("reports.collapse.expandAll")}
          </button>
        )}
        <button
          onClick={toggleSubtotals}
          className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium text-[var(--muted-foreground)] hover:bg-[var(--muted)] transition-colors"
        >
          <ArrowUpDown size={13} />
          {subtotalsOnTop ? t("reports.subtotalsOnTop") : t("reports.subtotalsOnBottom")}
        </button>
      </div>
      <div className="overflow-x-auto overflow-y-auto" style={{ maxHeight: "calc(100vh - 220px)" }}>
      <table className="w-full text-sm whitespace-nowrap">
        <thead className="sticky top-0 z-20">
          <tr className="border-b border-[var(--border)] bg-[var(--card)]">
            <th className="text-left py-2.5 px-3 font-medium text-[var(--muted-foreground)] sticky left-0 bg-[var(--card)] z-30 min-w-[140px]">
              {t("budget.category")}
            </th>
            <th className="text-right py-2.5 px-2 font-medium text-[var(--muted-foreground)] min-w-[90px]">
              {t("budget.previousYear")}
            </th>
            <th className="text-right py-2.5 px-2 font-medium text-[var(--muted-foreground)] min-w-[90px]">
              {t("budget.annual")}
            </th>
            {MONTH_KEYS.map((key) => (
              <th key={key} className="text-right py-2.5 px-2 font-medium text-[var(--muted-foreground)] min-w-[70px]">
                {t(key)}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {renderTypeSection("income")}
          {renderTypeSection("expense")}
          {/* Operating result (revenues − expenses), interleaved before the
              transfers section only when transfers exist — otherwise it
              equals the net result below and would just be noise. */}
          {results.hasTransfers &&
            renderResultRow("reports.compare.resultBeforeTransfers", results.resultBefore, false)}
          {renderTypeSection("transfer")}
          {/* Bottom line: result after netting transfers. */}
          {renderResultRow("reports.compare.resultNet", results.resultNet, true)}
        </tbody>
      </table>
      </div>
    </div>
  );
}
