"use client";

import { useEffect, useMemo, useState } from "react";
import { useLang } from "@/context/LanguageContext";
import {
  Alert,
  Button,
  Card,
  DatePicker,
  EmptyState,
  Input,
  Modal,
  MonthPicker,
  PageHeader,
  PageLoader,
  Pagination,
  Select,
  StatCard,
  StatusBadge,
  Td,
  Th,
  usePagination,
} from "@/components/ui";
import { api, useApi } from "@/lib/client";
import { PAYMENT_METHODS } from "@/lib/constants";
import { formatBDT, formatDate } from "@/lib/utils";
import { emsConfirm } from "@/components/ConfirmDialog";
import {
  IconBook,
  IconCheck,
  IconChevronDown,
  IconCard,
  IconDownload,
  IconEdit,
  IconPlus,
  IconTrash,
  IconX,
  IconTrend,
  IconUsers,
  IconWallet,
} from "@/components/icons";

interface Head {
  id: number;
  name: string;
  nameBn: string | null;
  type: string;
  defaultAmount: string;
  isActive: boolean;
  _count: { schedules: number };
}

interface Schedule {
  id: number;
  title: string;
  headName: string;
  headNameBn: string | null;
  headType: string;
  student: { id: number; roll: string; name: string } | null;
  amount: number;
  paid: number;
  due: number;
  status: string;
  dueDate: string;
  month: string | null;
}

interface Collection {
  details?: Record<string, string> | null;
  id: number;
  invoiceNo: string;
  amount: string;
  method: string;
  paidAt: string;
  student: { roll: string; name: string };
  feeSchedule: { title: string; feeHead: { name: string } } | null;
}

interface Dashboard {
  totals: { assigned: number; collected: number; due: number; students: number; assignments: number; dueCount: number };
  heads: { name: string; nameBn: string | null; type: string; assigned: number; collected: number }[];
  dueRows: {
    scheduleId: number; title: string; headName: string;
    studentId: number; roll: string; name: string;
    amount: number; paid: number; due: number; dueDate: string; month: string | null;
  }[];
  monthly: { key: string; label: string; total: number }[];
}

interface Student {
  id: number;
  roll: string;
  name: string;
  className: string | null;
}

interface Item {
  id: number;
  category: string;
  name: string;
  nameBn: string;
}

interface SfStudent {
  id: number;
  roll: string;
  name: string;
  nameBn: string | null;
  classItemId: number | null;
  className: string | null;
  classNameBn: string | null;
  subjectCount: number;
  monthlyTotal: number;
}

type Tab = "overview" | "heads" | "assign" | "collections" | "subjectFees";

const emptyHead = { name: "", nameBn: "", type: "TUITION", defaultAmount: "" };
const emptyAssign = { target: "CLASS" as "CLASS" | "STUDENT", feeHeadId: "", classItemId: "", studentId: "", title: "", amount: "", dueDate: "", month: "", note: "" };

interface ClaimRow {
  id: number;
  amount: number;
  method: string;
  accountLabel: string | null;
  details: Record<string, string> | null;
  note: string | null;
  status: "PENDING" | "APPROVED" | "REJECTED";
  decisionNote: string | null;
  decidedAt: string | null;
  createdAt: string;
  student: { id: number; roll: string; name: string };
  feeTitle: string | null;
  month: string | null;
  invoiceNo: string | null;
}

export default function PortalFeesPage() {
  const { t, locale } = useLang();
  const dashboard = useApi<Dashboard>("/api/portal/fees/dashboard");
  const heads = useApi<{ heads: Head[] }>("/api/portal/fees/heads");
  const schedules = useApi<{ schedules: Schedule[] }>("/api/portal/fees/schedules");
  const collections = useApi<{ collections: Collection[] }>("/api/portal/fees/collections");
  const students = useApi<{ students: Student[] }>("/api/portal/students");
  const academic = useApi<{ items: Item[] }>("/api/portal/academic");

  const [tab, setTab] = useState<Tab>("overview");
  const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
  const [busy, setBusy] = useState(false);

  // head modal
  const [headModal, setHeadModal] = useState(false);
  const [editingHead, setEditingHead] = useState<Head | null>(null);
  const [headForm, setHeadForm] = useState(emptyHead);

  // assign modal
  const [assignModal, setAssignModal] = useState(false);
  const [assignForm, setAssignForm] = useState(emptyAssign);

  // collect modal
  const [collectModal, setCollectModal] = useState(false);
  const [collectForm, setCollectForm] = useState({
    studentId: "", feeScheduleId: "", amount: "", method: "CASH", note: "",
    details: {} as Record<string, string>,
  });
  // online payment claims (submitted by students/parents from /me/fees)
  const [claimStatus, setClaimStatus] = useState("PENDING");
  const claims = useApi<{ pendingCount: number; claims: ClaimRow[] }>(
    `/api/portal/fees/claims?status=${claimStatus}`
  );

  async function decideClaim(claimId: number, action: "approve" | "reject") {
    if (!(await emsConfirm({ message: action === "approve" ? t.portal.feesPage.claimApproveConfirm : t.portal.feesPage.claimRejectConfirm, danger: action === "reject" }))) return;
    setBusy(true);
    try {
      await api(`/api/portal/fees/claims/${claimId}`, {
        method: "PATCH",
        body: JSON.stringify({ action }),
      });
      setMessage({
        type: "success",
        text: action === "approve" ? t.portal.feesPage.claimApprovedMsg : t.portal.feesPage.claimRejectedMsg,
      });
      claims.reload();
      collections.reload();
    } catch (e) {
      const msg = e instanceof Error ? e.message : "";
      setMessage({ type: "error", text: msg === "CLAIM_STALE" ? t.portal.feesPage.claimStale : t.common.tryAgain });
    } finally {
      setBusy(false);
    }
  }

  // mobile-banking / bank / card transactions carry reference details;
  // cash needs none
  const MOBILE_METHODS = ["BKASH", "NAGAD", "ROCKET"];
  function collectRequiredFields(): string[] {
    const m = collectForm.method;
    if (MOBILE_METHODS.includes(m)) return ["txnId"];
    if (m === "BANK_TRANSFER") return ["bankName", "refNo"];
    if (m === "CARD") return ["refNo"];
    return [];
  }
  const collectMissing = collectRequiredFields().filter((k) => !(collectForm.details[k] ?? "").trim());
  function setDetail(key: string, value: string) {
    setCollectForm((f) => ({ ...f, details: { ...f.details, [key]: value } }));
  }

  // subject-based monthly fees (feature switch from the Control Panel)
  const cp = useApi<{ subjectFeeEnabled?: boolean }>("/api/portal/control-panel");
  const [sfClassId, setSfClassId] = useState("");
  const sfList = useApi<{ students: SfStudent[]; rates: { subjectItemId: number; monthlyFee: number }[] }>(
    cp.data?.subjectFeeEnabled ? `/api/portal/subject-fees${sfClassId ? `?classItemId=${sfClassId}` : ""}` : null
  );
  const now = new Date();
  const [sfMonth, setSfMonth] = useState(`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`);
  // institution-wide default monthly fee per subject (Math 1000, Bangla 800 …)
  const [sfRates, setSfRates] = useState<Record<number, string>>({});
  const [sfRatesOpen, setSfRatesOpen] = useState(false);
  const [sfRatesSaving, setSfRatesSaving] = useState(false);

  const [sfModal, setSfModal] = useState(false);
  const [sfBasis, setSfBasis] = useState<"GROUP" | "DEPARTMENT" | null>(null);
  const [sfStudent, setSfStudent] = useState<SfStudent | null>(null);
  const [sfItems, setSfItems] = useState<{ subjectItemId: number; name: string; nameBn: string; on: boolean; fee: string }[]>([]);
  const [sfSaving, setSfSaving] = useState(false);
  const [sfGenerating, setSfGenerating] = useState(false);
  const subjects = (academic.data?.items ?? []).filter((i) => i.category === "SUBJECT");
  useEffect(() => {
    if (!sfList.data?.rates) return;
    setSfRates(Object.fromEntries(sfList.data.rates.map((r) => [r.subjectItemId, String(r.monthlyFee)])));
  }, [sfList.data]);

  async function openSfAssign(s: SfStudent) {
    setSfStudent(s);
    setSfModal(true);
    setSfItems(subjects.map((sub) => ({ subjectItemId: sub.id, name: sub.name, nameBn: sub.nameBn, on: false, fee: "" })));
    try {
      const res = await api<{
        items: { subjectItemId: number; monthlyFee: number }[];
        rates: { subjectItemId: number; monthlyFee: number }[];
        suggested?: number[];
        suggestedBasis?: "GROUP" | "DEPARTMENT" | null;
      }>(`/api/portal/subject-fees?studentId=${s.id}`);
      const byId = new Map(res.items.map((i) => [i.subjectItemId, String(i.monthlyFee)]));
      const rateById = new Map(res.rates.map((r) => [r.subjectItemId, String(r.monthlyFee)]));
      const suggested = new Set(res.suggested ?? []);
      setSfBasis(res.suggestedBasis ?? null);
      setSfItems((prev) => prev.map((row) => ({
        ...row,
        // already-assigned amount wins; otherwise the subject's default rate;
        // unassigned rows start ticked when in the student's group/department set
        on: byId.has(row.subjectItemId) || (!byId.size && suggested.has(row.subjectItemId)),
        fee: byId.get(row.subjectItemId) ?? rateById.get(row.subjectItemId) ?? "",
      })));
    } catch {
      /* start blank on failure */
    }
  }

  // ticking a subject auto-fills its default rate (Math → 1000) when empty
  function toggleSfItem(idx: number, on: boolean) {
    setSfItems((prev) => prev.map((r, i) => {
      if (i !== idx) return r;
      const rate = sfRates[r.subjectItemId];
      return { ...r, on, fee: on && !r.fee && rate ? String(rate) : r.fee };
    }));
  }

  async function saveSfRates(e: React.FormEvent) {
    e.preventDefault();
    setSfRatesSaving(true);
    setMessage(null);
    try {
      await api("/api/portal/subject-fees", {
        method: "PUT",
        body: JSON.stringify({
          rates: subjects.map((sub) => ({
            subjectItemId: sub.id,
            monthlyFee: Number(sfRates[sub.id] ?? 0) || 0,
          })),
        }),
      });
      setMessage({ type: "success", text: t.portal.feesPage.sfRatesSaved });
      sfList.reload();
    } catch {
      setMessage({ type: "error", text: t.common.tryAgain });
    } finally {
      setSfRatesSaving(false);
    }
  }

  async function saveSfAssign(e: React.FormEvent) {
    e.preventDefault();
    if (!sfStudent) return;
    setSfSaving(true);
    setMessage(null);
    try {
      await api("/api/portal/subject-fees", {
        method: "PUT",
        body: JSON.stringify({
          studentId: sfStudent.id,
          items: sfItems.filter((i) => i.on).map((i) => ({ subjectItemId: i.subjectItemId, monthlyFee: Number(i.fee) || 0 })),
        }),
      });
      setMessage({ type: "success", text: t.portal.feesPage.sfSaved });
      setSfModal(false);
      sfList.reload();
      reloadAll();
    } catch {
      setMessage({ type: "error", text: t.common.tryAgain });
    } finally {
      setSfSaving(false);
    }
  }

  async function generateSfMonth() {
    setSfGenerating(true);
    setMessage(null);
    try {
      const res = await api<{ created: number; skipped: number }>("/api/portal/subject-fees/generate", {
        method: "POST",
        body: JSON.stringify({ month: sfMonth, classItemId: sfClassId ? Number(sfClassId) : undefined }),
      });
      setMessage({ type: "success", text: t.portal.feesPage.sfGenerated.replace("{created}", String(res.created)).replace("{skipped}", String(res.skipped)) });
      reloadAll();
    } catch (err) {
      setMessage({
        type: "error",
        text: err instanceof Error && err.message === "MONTH_REQUIRED" ? t.portal.feesPage.sfMonthRequired : t.common.tryAgain,
      });
    } finally {
      setSfGenerating(false);
    }
  }

  const classItems = (academic.data?.items ?? []).filter((i) => i.category === "CLASS");

  function reloadAll() {
    dashboard.reload();
    heads.reload();
    schedules.reload();
    collections.reload();
    students.reload();
  }

  /* ---------- Fee heads ---------- */
  function openHeadCreate() {
    setEditingHead(null);
    setHeadForm(emptyHead);
    setHeadModal(true);
  }
  function openHeadEdit(h: Head) {
    setEditingHead(h);
    setHeadForm({ name: h.name, nameBn: h.nameBn ?? "", type: h.type, defaultAmount: String(h.defaultAmount) });
    setHeadModal(true);
  }
  async function submitHead(e: React.FormEvent) {
    e.preventDefault();
    setBusy(true);
    setMessage(null);
    try {
      if (editingHead) {
        await api(`/api/portal/fees/heads/${editingHead.id}`, {
          method: "PUT",
          body: JSON.stringify({ ...headForm, defaultAmount: Number(headForm.defaultAmount) || 0 }),
        });
        setMessage({ type: "success", text: t.portal.feesPage.headUpdated });
      } else {
        await api("/api/portal/fees/heads", {
          method: "POST",
          body: JSON.stringify({ ...headForm, defaultAmount: Number(headForm.defaultAmount) || 0 }),
        });
        setMessage({ type: "success", text: t.portal.feesPage.headCreated });
      }
      setHeadModal(false);
      reloadAll();
    } catch {
      setMessage({ type: "error", text: t.common.tryAgain });
    } finally {
      setBusy(false);
    }
  }
  async function removeHead(h: Head) {
    if (!(await emsConfirm({ message: t.common.confirmDelete, danger: true }))) return;
    try {
      await api(`/api/portal/fees/heads/${h.id}`, { method: "DELETE" });
      reloadAll();
    } catch (err) {
      setMessage({
        type: "error",
        text: err instanceof Error && err.message === "IN_USE" ? t.portal.feesPage.headInUse : t.common.tryAgain,
      });
    }
  }

  /* ---------- Assign ---------- */
  async function submitAssign(e: React.FormEvent) {
    e.preventDefault();
    setBusy(true);
    setMessage(null);
    try {
      const res = await api<{ created: number }>("/api/portal/fees/schedules", {
        method: "POST",
        body: JSON.stringify({
          feeHeadId: Number(assignForm.feeHeadId),
          studentId: assignForm.target === "STUDENT" && assignForm.studentId ? Number(assignForm.studentId) : null,
          classItemId: assignForm.target === "CLASS" && assignForm.classItemId ? Number(assignForm.classItemId) : null,
          title: assignForm.title,
          amount: Number(assignForm.amount),
          dueDate: assignForm.dueDate || undefined,
          month: assignForm.month || null,
        }),
      });
      setMessage({
        type: "success",
        text:
          res.created === 1
            ? t.portal.feesPage.scheduleCreated
            : `${res.created} ${t.portal.feesPage.schedulesCreated}`,
      });
      setAssignModal(false);
      setAssignForm(emptyAssign);
      reloadAll();
    } catch (err) {
      setMessage({
        type: "error",
        text:
          err instanceof Error && err.message === "NO_STUDENTS_IN_CLASS"
            ? t.portal.feesPage.classStudentsNone
            : err instanceof Error && err.message === "TARGET_REQUIRED"
              ? t.portal.feesPage.targetRequired
              : t.common.tryAgain,
      });
    } finally {
      setBusy(false);
    }
  }

  /* ---------- Collect ---------- */
  function openCollect(studentId?: number, scheduleId?: number, dueAmount?: number) {
    setCollectForm({
      studentId: studentId ? String(studentId) : "",
      feeScheduleId: scheduleId ? String(scheduleId) : "",
      amount: dueAmount ? String(dueAmount) : "",
      method: "CASH",
      note: "",
      details: {},
    });
    setCollectModal(true);
  }
  async function submitCollect(e: React.FormEvent) {
    e.preventDefault();
    if (collectMissing.length > 0) {
      setMessage({ type: "error", text: t.portal.feesPage.cfDetailsRequired });
      return;
    }
    setBusy(true);
    setMessage(null);
    try {
      const res = await api<{ collection: Collection }>("/api/portal/fees/collections", {
        method: "POST",
        body: JSON.stringify({
          studentId: Number(collectForm.studentId),
          feeScheduleId: collectForm.feeScheduleId ? Number(collectForm.feeScheduleId) : null,
          amount: Number(collectForm.amount),
          method: collectForm.method,
          note: collectForm.note,
          details: collectForm.details,
        }),
      });
      setMessage({ type: "success", text: `${t.portal.feesPage.collectionRecorded} ${res.collection.invoiceNo}` });
      setCollectModal(false);
      reloadAll();
    } catch (err) {
      setMessage({
        type: "error",
        text:
          err instanceof Error && (err.message === "AMOUNT_EXCEEDS" || err.message === "INVALID_AMOUNT")
            ? t.central.paymentsPage.amountExceeds
            : t.common.tryAgain,
      });
    } finally {
      setBusy(false);
    }
  }

  const db = dashboard.data;
  const maxMonthly = Math.max(1, ...(db?.monthly.map((m) => m.total) ?? [1]));

  const scheduleRows = schedules.data?.schedules ?? [];
  const collectionRows = collections.data?.collections ?? [];
  const dueRowList = db?.dueRows ?? [];

  /* Due report search + filters (client-side over the dashboard rows) */
  const [dueSearch, setDueSearch] = useState("");
  const [dueHead, setDueHead] = useState("");
  const [dueStatus, setDueStatus] = useState("");
  const todayISO = new Date().toISOString().slice(0, 10);
  const dueHeadNames = useMemo(
    () => [...new Set(dueRowList.map((r) => r.headName))].sort((a, b) => a.localeCompare(b)),
    [dueRowList]
  );
  const filteredDueRows = useMemo(() => {
    const q = dueSearch.trim().toLowerCase();
    return dueRowList.filter((r) => {
      if (dueHead && r.headName !== dueHead) return false;
      if (dueStatus) {
        const overdue = r.dueDate.slice(0, 10) < todayISO;
        if (dueStatus === "OVERDUE" && !overdue) return false;
        if (dueStatus === "UPCOMING" && overdue) return false;
      }
      if (!q) return true;
      return (
        r.roll.toLowerCase().includes(q) ||
        r.name.toLowerCase().includes(q) ||
        r.title.toLowerCase().includes(q) ||
        r.headName.toLowerCase().includes(q)
      );
    });
  }, [dueRowList, dueSearch, dueHead, dueStatus, todayISO]);
  const dueRowsPager = usePagination(filteredDueRows, 10);
  // any filter change lands the user back on the first results page
  useEffect(() => {
    dueRowsPager.setPage(1);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [dueSearch, dueHead, dueStatus]);
  const dueFiltersActive = dueSearch !== "" || dueHead !== "" || dueStatus !== "";

  const schedulesPager = usePagination(scheduleRows, 10);
  const collectionsPager = usePagination(collectionRows, 10);

  const tabs: { id: Tab; label: string }[] = [
    { id: "overview", label: t.portal.feesPage.tabOverview },
    { id: "heads", label: t.portal.feesPage.tabHeads },
    { id: "assign", label: t.portal.feesPage.tabAssign },
    { id: "collections", label: t.portal.feesPage.tabCollections },
    // subject-based monthly fees (Control Panel feature switch)
    ...(cp.data?.subjectFeeEnabled ? [{ id: "subjectFees" as Tab, label: t.portal.feesPage.tabSubjectFees }] : []),
  ];

  return (
    <div>
      <PageHeader
        title={t.portal.feesPage.title}
        subtitle={t.portal.feesPage.subtitle}
        action={
          <div className="d-flex gap-2">
            <Button variant="secondary" onClick={() => { setAssignForm(emptyAssign); setAssignModal(true); }}>
              <IconPlus className="svg-16" />
              <span className="ms-1">{t.portal.feesPage.assignFee}</span>
            </Button>
            <Button onClick={() => openCollect()}>
              <IconWallet className="svg-16" />
              <span className="ms-1">{t.portal.feesPage.collectFee}</span>
            </Button>
          </div>
        }
      />

      {message && (
        <div className="mb-4">
          <Alert type={message.type} onClose={() => setMessage(null)}>{message.text}</Alert>
        </div>
      )}

      {/* Tabs */}
      <ul className="nav nav-tabs mb-4">
        {tabs.map((x) => (
          <li className="nav-item" key={x.id}>
            <button className={`nav-link f-w-600 ${tab === x.id ? "active" : ""}`} type="button" onClick={() => setTab(x.id)}>
              {x.label}
            </button>
          </li>
        ))}
      </ul>

      {/* ---------------- Overview ---------------- */}
      {tab === "overview" && (
        dashboard.loading ? <PageLoader text={t.common.loading} /> : !db ? null : (
          <div>
            <div className="row g-4 mb-4">
              <div className="col-xl-3 col-md-6">
                <StatCard label={t.portal.feesPage.assignedTotal} value={formatBDT(db.totals.assigned, locale)} icon={<IconCard />} accent="teal" hint={`${db.totals.assignments} ${t.portal.feesPage.assignments}`} />
              </div>
              <div className="col-xl-3 col-md-6">
                <StatCard label={t.portal.feesPage.collectedTotal} value={formatBDT(db.totals.collected, locale)} icon={<IconWallet />} accent="blue" />
              </div>
              <div className="col-xl-3 col-md-6">
                <StatCard label={t.portal.feesPage.dueTotal} value={formatBDT(db.totals.due, locale)} icon={<IconTrend />} accent="red" hint={`${db.totals.dueCount} ${t.common.due}`} />
              </div>
              <div className="col-xl-3 col-md-6">
                <StatCard label={t.portal.feesPage.activeStudents} value={String(db.totals.students)} icon={<IconUsers />} accent="purple" />
              </div>
            </div>

            <div className="row g-4 mb-4">
              <div className="col-xl-7">
                <Card title={t.portal.feesPage.monthlyCollections} icon={<IconTrend className="svg-20" />}>
                  <div className="d-flex align-items-end gap-3" style={{ height: 170 }}>
                    {db.monthly.map((m) => (
                      <div className="flex-fill text-center" key={m.key}>
                        <p className="ems-fs-11 f-w-700 text-muted mb-1">{m.total > 0 ? formatBDT(m.total, locale) : ""}</p>
                        <div className="ems-bar" style={{ height: `${Math.max(4, (m.total / maxMonthly) * 100)}%` }} />
                        <p className="ems-fs-12 f-w-600 text-muted mt-2 mb-0">{m.label}</p>
                      </div>
                    ))}
                  </div>
                </Card>
              </div>
              <div className="col-xl-5">
                <Card title={t.portal.feesPage.byHead} icon={<IconCard className="svg-20" />}>
                  {db.heads.length === 0 ? (
                    <EmptyState title={t.common.noData} />
                  ) : (
                    db.heads.map((h) => {
                      const pct = h.assigned > 0 ? (h.collected / h.assigned) * 100 : 0;
                      return (
                        <div className="mb-3" key={h.name}>
                          <div className="d-flex justify-content-between ems-fs-12 f-w-700">
                            <span>{locale === "bn" && h.nameBn ? h.nameBn : h.name}</span>
                            <span className="text-success">{Math.round(pct)}%</span>
                          </div>
                          <div className="progress ems-progress-thin mt-1">
                            <div className="progress-bar bg-success" style={{ width: `${pct}%` }} />
                          </div>
                          <div className="d-flex justify-content-between ems-fs-11 text-muted mt-1">
                            <span>{formatBDT(h.assigned, locale)}</span>
                            <span>{formatBDT(h.collected, locale)}</span>
                          </div>
                        </div>
                      );
                    })
                  )}
                </Card>
              </div>
            </div>

            {/* Due report */}
            <Card title={t.portal.feesPage.dueReport} icon={<IconWallet className="svg-20" />}>
              {db.dueRows.length === 0 ? (
                <EmptyState icon={<IconWallet />} title={t.portal.feesPage.noDueFees} />
              ) : (
                <>
                {/* search + filters (client-side over the loaded rows) — kept
                    visible even on zero matches so filters can be cleared */}
                <div className="d-flex flex-wrap align-items-center gap-2 mb-3">
                  <div className="has-search" style={{ flexGrow: 1, maxWidth: 300, minWidth: 190 }}>
                    <span className="fa fa-search form-control-feedback" />
                    <input
                      className="form-control"
                      placeholder={t.portal.feesPage.dueSearchPlaceholder}
                      value={dueSearch}
                      onChange={(e) => setDueSearch(e.target.value)}
                    />
                  </div>
                  <div style={{ maxWidth: 180 }}>
                    <Select
                      wrapperClassName="form-group mb-0"
                      value={dueHead}
                      onChange={(e) => setDueHead(e.target.value)}
                    >
                      <option value="">{t.portal.feesPage.dueHeadAll}</option>
                      {dueHeadNames.map((h) => (
                        <option key={h} value={h}>
                          {locale === "bn" ? db?.heads.find((x) => x.name === h)?.nameBn ?? h : h}
                        </option>
                      ))}
                    </Select>
                  </div>
                  <div style={{ maxWidth: 160 }}>
                    <Select
                      wrapperClassName="form-group mb-0"
                      value={dueStatus}
                      onChange={(e) => setDueStatus(e.target.value)}
                    >
                      <option value="">{t.common.all} · {t.common.status}</option>
                      <option value="OVERDUE">{t.portal.feesPage.dueOverdue}</option>
                      <option value="UPCOMING">{t.portal.feesPage.dueUpcoming}</option>
                    </Select>
                  </div>
                  <span className="ems-fs-12 text-muted ms-auto f-w-600">
                    {dueRowsPager.total} / {dueRowList.length}
                    {dueFiltersActive ? ` · ${t.common.filter}` : ""}
                  </span>
                </div>
                {filteredDueRows.length === 0 ? (
                  <EmptyState icon={<IconWallet />} title={t.common.noResults} />
                ) : (
                <>
                <div className="table-responsive">
                  <table className="table table-hover align-middle mb-0">
                    <thead className="ems-table-head">
                      <tr>
                        <th>{t.portal.studentsPage.rollNo}</th>
                        <th>{t.common.name}</th>
                        <th>{t.portal.feesPage.feeTitle}</th>
                        <th>{t.common.amount}</th>
                        <th>{t.portal.feesPage.collectedTotal}</th>
                        <th>{t.common.due}</th>
                        <th>{t.portal.feesPage.dueDate}</th>
                        <th className="text-end">{t.common.actions}</th>
                      </tr>
                    </thead>
                    <tbody>
                      {dueRowsPager.paged.map((r) => (
                        <tr key={r.scheduleId}>
                          <td><span className="ems-roll-badge">{r.roll}</span></td>
                          <td className="f-w-600">{r.name}</td>
                          <td>
                            <p className="mb-0 ems-fs-13 f-w-600">{r.title}</p>
                            <p className="mb-0 ems-fs-11 text-muted">{r.headName}{r.month ? ` · ${r.month}` : ""}</p>
                          </td>
                          <td className="f-w-700">{formatBDT(r.amount, locale)}</td>
                          <td className="text-success">{formatBDT(r.paid, locale)}</td>
                          <td className="text-danger f-w-800">{formatBDT(r.due, locale)}</td>
                          <td className="ems-fs-12 text-muted">{formatDate(r.dueDate, locale)}</td>
                          <td className="text-end">
                            <Button size="sm" variant="success" onClick={() => openCollect(r.studentId, r.scheduleId, r.due)}>
                              <IconWallet className="svg-16" />
                              <span className="ms-1">{t.central.paymentsPage.collect}</span>
                            </Button>
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
                <Pagination {...dueRowsPager} onPageChange={dueRowsPager.setPage} onSizeChange={dueRowsPager.setSize} />
                </>
                )}
                </>
              )}
            </Card>
          </div>
        )
      )}

      {/* ---------------- Fee heads ---------------- */}
      {tab === "heads" && (
        <div>
          <div className="d-flex justify-content-end mb-3">
            <Button onClick={openHeadCreate}>
              <IconPlus className="svg-16" />
              <span className="ms-1">{t.portal.feesPage.newHead}</span>
            </Button>
          </div>
          {heads.loading ? (
            <PageLoader text={t.common.loading} />
          ) : (
            <div className="row g-3">
              {(heads.data?.heads ?? []).map((h) => (
                <div className="col-xl-3 col-lg-4 col-sm-6" key={h.id}>
                  <div className="card h-100">
                    <div className="card-body d-flex align-items-center gap-3">
                      <span className={`ems-cat ems-cat-${h.type === "ADMISSION" ? "SESSION" : h.type === "TUITION" ? "CLASS" : h.type === "EXAM" ? "SUBJECT" : "ROOM"}`} style={{ width: 46, height: 46 }}>
                        <IconWallet />
                      </span>
                      <div className="min-w-0 flex-grow-1">
                        <h6 className="mb-0 f-w-700 text-truncate">{locale === "bn" && h.nameBn ? h.nameBn : h.name}</h6>
                        <p className="mb-0 ems-fs-11 text-muted">
                          {t.feeTypes[h.type as keyof typeof t.feeTypes]} · {formatBDT(h.defaultAmount, locale)}
                        </p>
                        <p className="mb-0 ems-fs-11 text-muted">{h._count.schedules} {t.portal.feesPage.schedulesCount}</p>
                      </div>
                      <div className="d-flex flex-column gap-1">
                        <button className="btn btn-soft-primary btn-sm p-1" type="button" onClick={() => openHeadEdit(h)}>
                          <IconEdit className="svg-16" />
                        </button>
                        <button className="btn btn-soft-danger btn-sm p-1" type="button" onClick={() => removeHead(h)}>
                          <IconTrash className="svg-16" />
                        </button>
                      </div>
                    </div>
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      )}

      {/* ---------------- Assignments list ---------------- */}
      {tab === "assign" && (
        schedules.loading ? <PageLoader text={t.common.loading} /> : (
          <div className="card">
            <div className="table-responsive">
              <table className="table table-hover align-middle mb-0">
                <thead className="ems-table-head">
                  <tr>
                    <th>{t.portal.feesPage.feeTitle}</th>
                    <th>{t.portal.feesPage.studentLabel}</th>
                    <th>{t.common.amount}</th>
                    <th>{t.portal.feesPage.collectedTotal}</th>
                    <th>{t.common.due}</th>
                    <th>{t.common.status}</th>
                    <th className="text-end">{t.common.actions}</th>
                  </tr>
                </thead>
                <tbody>
                  {schedulesPager.paged.map((fs) => (
                    <tr key={fs.id}>
                      <td>
                        <p className="mb-0 ems-fs-13 f-w-700">{fs.title}</p>
                        <p className="mb-0 ems-fs-11 text-muted">
                          {locale === "bn" ? fs.headNameBn : fs.headName}
                          {fs.month ? ` · ${fs.month}` : ""} · {formatDate(fs.dueDate, locale)}
                        </p>
                      </td>
                      <td>
                        {fs.student ? (
                          <p className="mb-0 ems-fs-13">
                            <span className="ems-roll-badge me-1">{fs.student.roll}</span> {fs.student.name}
                          </p>
                        ) : "—"}
                      </td>
                      <td className="f-w-700">{formatBDT(fs.amount, locale)}</td>
                      <td className="text-success">{formatBDT(fs.paid, locale)}</td>
                      <td className={fs.due > 0 ? "text-danger f-w-700" : "text-muted"}>{formatBDT(fs.due, locale)}</td>
                      <td><StatusBadge status={fs.status} label={fs.status === "PAID" ? t.common.paid : fs.status === "PARTIAL" ? "PARTIAL" : t.common.due} /></td>
                      <td className="text-end">
                        {fs.due > 0 && fs.student && (
                          <Button size="sm" variant="success" onClick={() => openCollect(fs.student!.id, fs.id, fs.due)}>
                            <IconWallet className="svg-16" />
                            <span className="ms-1">{t.central.paymentsPage.collect}</span>
                          </Button>
                        )}
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <Pagination {...schedulesPager} onPageChange={schedulesPager.setPage} onSizeChange={schedulesPager.setSize} />
          </div>
        )
      )}

      {/* ---------------- Collections ---------------- */}
      {tab === "subjectFees" && (
        <Card title={t.portal.feesPage.tabSubjectFees} icon={<IconBook className="svg-20" />}>
          <div className="d-flex flex-wrap align-items-end gap-3 mb-3">
            <div style={{ minWidth: 200 }}>
              <Select wrapperClassName="form-group mb-0" value={sfClassId} onChange={(e) => setSfClassId(e.target.value)}>
                <option value="">{t.common.all} — {t.portal.studentsPage.classLabel}</option>
                {classItems.map((c) => (
                  <option key={c.id} value={c.id}>{locale === "bn" ? c.nameBn : c.name}</option>
                ))}
              </Select>
            </div>
            <div>
              <MonthPicker
                wrapperClassName="form-group mb-0"
                label={t.portal.feesPage.sfMonth}
                value={sfMonth}
                onChange={(e) => setSfMonth(e.target.value)}
              />
            </div>
            <Button onClick={generateSfMonth} loading={sfGenerating}>
              <IconDownload className="svg-16" />
              <span className="ms-1">{t.portal.feesPage.sfGenerate}</span>
            </Button>
            <p className="ems-fs-11 text-muted mb-0 flex-grow-1" style={{ minWidth: 220 }}>
              {t.portal.feesPage.sfGenerateHint}
            </p>
          </div>

          {/* default monthly fee per subject (management) */}
          <div className={`ems-notify-block ${sfRatesOpen ? "" : "collapsed"} mb-3`} style={{ background: "rgba(36,105,92,0.04)", borderRadius: 12 }}>
            <div className="ems-notify-head" onClick={() => setSfRatesOpen((o) => !o)} role="button" tabIndex={0}
                 onKeyDown={(e) => e.key === "Enter" && setSfRatesOpen((o) => !o)}>
              <div className="d-flex align-items-center min-w-0">
                <span className="ic"><IconBook className="svg-18" /></span>
                <div className="min-w-0">
                  <div className="f-w-700 ems-fs-14">{t.portal.feesPage.sfRatesTitle}</div>
                  <div className="ems-fs-11 text-muted">
                    {t.portal.feesPage.sfRatesSubtitle}
                    {Object.values(sfRates).filter((v) => Number(v) > 0).length > 0 && (
                      <span className="badge badge-soft-teal ms-2">
                        {Object.values(sfRates).filter((v) => Number(v) > 0).length} {t.portal.feesPage.sfRatesSet}
                      </span>
                    )}
                  </div>
                </div>
              </div>
              <IconChevronDown className="svg-16 caret" />
            </div>
            <div className="ems-notify-body">
              <form onSubmit={saveSfRates}>
                <div className="row g-2">
                  {subjects.map((sub) => (
                    <div className="col-md-4 col-6" key={sub.id}>
                      <div className="d-flex align-items-center gap-2">
                        <span className="ems-fs-12 f-w-600 text-truncate flex-grow-1">
                          {locale === "bn" ? sub.nameBn : sub.name}
                        </span>
                        <input
                          type="number"
                          min="0"
                          className="form-control form-control-sm text-end"
                          style={{ maxWidth: 110 }}
                          placeholder="0"
                          value={sfRates[sub.id] ?? ""}
                          onChange={(e) => setSfRates((prev) => ({ ...prev, [sub.id]: e.target.value }))}
                        />
                      </div>
                    </div>
                  ))}
                </div>
                <div className="d-flex align-items-center justify-content-between pt-3">
                  <span className="ems-fs-11 text-muted">{t.portal.feesPage.sfRatesHint}</span>
                  <Button type="submit" size="sm" loading={sfRatesSaving}>{t.common.save}</Button>
                </div>
              </form>
            </div>
          </div>

          {sfList.loading ? (
            <PageLoader text={t.common.loading} />
          ) : (sfList.data?.students ?? []).length === 0 ? (
            <EmptyState icon={<IconBook />} title={t.common.noData} />
          ) : (
            <div className="table-responsive">
              <table className="table table-hover align-middle mb-0">
                <thead className="ems-table-head">
                  <tr>
                    <th>{t.portal.studentsPage.rollNo}</th>
                    <th>{t.portal.feesPage.studentLabel}</th>
                    <th>{t.portal.studentsPage.classLabel}</th>
                    <th className="text-center">{t.portal.feesPage.sfSubjects}</th>
                    <th className="text-end">{t.portal.feesPage.sfMonthly}</th>
                    <th className="text-end">{t.common.actions}</th>
                  </tr>
                </thead>
                <tbody>
                  {sfList.data!.students.map((s) => (
                    <tr key={s.id}>
                      <td><span className="ems-roll-badge">{s.roll}</span></td>
                      <td className="f-w-600">{locale === "bn" && s.nameBn ? s.nameBn : s.name}</td>
                      <td className="ems-fs-12 text-muted">
                        {s.classItemId ? (locale === "bn" ? (s.classNameBn ?? s.className) : s.className) : "—"}
                      </td>
                      <td className="text-center">
                        <span className={`badge ${s.subjectCount ? "badge-soft-teal" : "badge-secondary"}`}>{s.subjectCount}</span>
                      </td>
                      <td className="text-end f-w-800">{s.subjectCount ? formatBDT(s.monthlyTotal, locale) : "—"}</td>
                      <td className="text-end">
                        <Button size="sm" variant="secondary" onClick={() => openSfAssign(s)}>
                          <IconEdit className="svg-14" />
                          <span className="ms-1">{t.portal.feesPage.sfAssign}</span>
                        </Button>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </Card>
      )}

      {tab === "collections" && (
        collections.loading ? <PageLoader text={t.common.loading} /> : (
          <>
          {(claims.data?.claims ?? []).length > 0 && (
          <Card
            title={`${t.portal.feesPage.claimsTitle}${claimStatus === "PENDING" && claims.data?.pendingCount ? ` (${claims.data.pendingCount})` : ""}`}
            icon={<IconWallet className="svg-20" />}
            className="mb-4"
            action={
              <div className="btn-group btn-group-sm">
                {["PENDING", "APPROVED", "REJECTED", "ALL"].map((st) => (
                  <button
                    key={st}
                    type="button"
                    className={`btn f-w-700 ${claimStatus === st ? "btn-primary" : "btn-outline-secondary"}`}
                    onClick={() => setClaimStatus(st)}
                  >
                    {st === "PENDING" ? t.portal.feesPage.claimPending : st === "APPROVED" ? t.portal.feesPage.claimApproved : st === "REJECTED" ? t.portal.feesPage.claimRejected : t.portal.feesPage.claimAll}
                  </button>
                ))}
              </div>
            }
          >
            <div className="table-responsive">
              <table className="table table-hover align-middle mb-0">
                <thead className="ems-table-head">
                  <tr>
                    <th>{t.common.date}</th>
                    <th>{t.portal.feesPage.studentLabel}</th>
                    <th>{t.portal.feesPage.feeTitle}</th>
                    <th className="text-center">{t.common.method}</th>
                    <th>{t.portal.feesPage.claimRefCol}</th>
                    <th className="text-end">{t.common.amount}</th>
                    <th className="text-center">{t.common.status}</th>
                    {claimStatus === "PENDING" && <th className="text-end">{t.common.actions}</th>}
                  </tr>
                </thead>
                <tbody>
                  {(claims.data?.claims ?? []).map((c) => {
                    const ref = c.details?.txnId ?? c.details?.refNo ?? "—";
                    return (
                      <tr key={c.id}>
                        <td className="ems-fs-12 text-muted">{new Date(c.createdAt).toLocaleDateString()}</td>
                        <td className="f-w-600">
                          <span className="ems-roll-badge me-1">{c.student.roll}</span> {c.student.name}
                        </td>
                        <td className="ems-fs-13">
                          {c.feeTitle ?? t.portal.feesPage.claimGeneralItem}
                          {c.accountLabel && <span className="d-block ems-fs-11 text-muted">{c.accountLabel}</span>}
                          {c.note && <span className="d-block ems-fs-11 text-muted">&ldquo;{c.note}&rdquo;</span>}
                        </td>
                        <td className="text-center ems-fs-13">{t.methods[c.method as keyof typeof t.methods] ?? c.method}</td>
                        <td><code className="ems-invoice-badge">{ref}</code></td>
                        <td className="text-end f-w-700">{c.amount.toLocaleString()}</td>
                        <td className="text-center">
                          <span className={`badge ${c.status === "APPROVED" ? "badge-success" : c.status === "REJECTED" ? "badge-danger" : "badge-warning"}`}>
                            {c.status === "APPROVED" ? t.portal.feesPage.claimApproved : c.status === "REJECTED" ? t.portal.feesPage.claimRejected : t.portal.feesPage.claimPending}
                          </span>
                          {c.invoiceNo && (
                            <span className="d-block ems-fs-11 text-muted mt-1">{c.invoiceNo}</span>
                          )}
                          {c.decisionNote && c.status === "REJECTED" && (
                            <span className="d-block ems-fs-11 text-danger mt-1">{c.decisionNote}</span>
                          )}
                        </td>
                        {claimStatus === "PENDING" && (
                          <td className="text-end">
                            <div className="d-inline-flex gap-1">
                              <Button size="sm" variant="success" loading={busy} onClick={() => decideClaim(c.id, "approve")}>
                                <IconCheck className="svg-16" />
                                <span className="ms-1 d-none d-lg-inline">{t.central.requestsPage.approve}</span>
                              </Button>
                              <Button size="sm" variant="danger" loading={busy} onClick={() => decideClaim(c.id, "reject")}>
                                <IconX className="svg-16" />
                              </Button>
                            </div>
                          </td>
                        )}
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
            <p className="ems-fs-11 text-muted mt-2 mb-0">{t.portal.feesPage.claimsHint}</p>
          </Card>
          )}
          <Card title={t.portal.feesPage.historyTitle} icon={<IconCard className="svg-20" />}>
            {(collections.data?.collections ?? []).length === 0 ? (
              <EmptyState icon={<IconCard />} title={t.common.noData} />
            ) : (
              <>
              <div className="table-responsive">
                <table className="table table-hover align-middle mb-0">
                  <thead className="ems-table-head">
                    <tr>
                      <th>{t.central.paymentsPage.invoiceNo}</th>
                      <th>{t.portal.feesPage.studentLabel}</th>
                      <th>{t.portal.feesPage.feeTitle}</th>
                      <th>{t.common.amount}</th>
                      <th>{t.common.method}</th>
                      <th>{t.common.date}</th>
                    </tr>
                  </thead>
                  <tbody>
                    {collectionsPager.paged.map((c) => (
                      <tr key={c.id}>
                        <td><span className="ems-invoice-badge">{c.invoiceNo}</span></td>
                        <td className="f-w-600">
                          <span className="ems-roll-badge me-1">{c.student.roll}</span> {c.student.name}
                        </td>
                        <td className="ems-fs-12 text-muted">{c.feeSchedule?.title ?? t.portal.feesPage.unspecific}</td>
                        <td className="f-w-800 text-success">{formatBDT(c.amount, locale)}</td>
                        <td>
                        <span className="badge badge-secondary">{t.methods[c.method as keyof typeof t.methods]}</span>
                        {c.details && (
                          <div className="ems-fs-11 text-muted mt-1">
                            {[
                              c.details.txnId && `TrxID: ${c.details.txnId}`,
                              c.details.bankName,
                              c.details.branch,
                              c.details.refNo && `Ref: ${c.details.refNo}`,
                              c.details.cardType,
                              c.details.last4 && `•••• ${c.details.last4}`,
                              c.details.account && `${c.details.account}`,
                            ]
                              .filter(Boolean)
                              .join(" · ")}
                          </div>
                        )}
                      </td>
                        <td className="ems-fs-12 text-muted">{formatDate(c.paidAt, locale)}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
              <Pagination {...collectionsPager} onPageChange={collectionsPager.setPage} onSizeChange={collectionsPager.setSize} />
              </>
            )}
          </Card>
          </>
        )
      )}

      {/* ---------- Fee head modal ---------- */}
      <Modal open={headModal} onClose={() => setHeadModal(false)} title={editingHead ? t.common.edit : t.portal.feesPage.newHead}>
        <form className="theme-form" onSubmit={submitHead}>
          <Input label={t.portal.feesPage.headName} required value={headForm.name} onChange={(e) => setHeadForm({ ...headForm, name: e.target.value })} placeholder="e.g. Monthly Tuition" />
          <Input label={t.portal.feesPage.headNameBn} value={headForm.nameBn} onChange={(e) => setHeadForm({ ...headForm, nameBn: e.target.value })} placeholder="যেমন: মাসিক বেতন" />
          <Select label={t.portal.feesPage.headType} value={headForm.type} onChange={(e) => setHeadForm({ ...headForm, type: e.target.value })}>
            {(["ADMISSION", "TUITION", "EXAM", "OTHER"] as const).map((x) => (
              <option key={x} value={x}>{t.feeTypes[x]}</option>
            ))}
          </Select>
          <Input label={t.portal.feesPage.defaultAmount} type="number" min="0" value={headForm.defaultAmount} onChange={(e) => setHeadForm({ ...headForm, defaultAmount: e.target.value })} />
          <div className="d-flex justify-content-end gap-2 pt-2">
            <Button type="button" variant="secondary" onClick={() => setHeadModal(false)}>{t.common.cancel}</Button>
            <Button type="submit" loading={busy}>{editingHead ? t.common.saveChanges : t.common.create}</Button>
          </div>
        </form>
      </Modal>

      {/* ---------- Assign modal ---------- */}
      <Modal open={assignModal} onClose={() => setAssignModal(false)} title={t.portal.feesPage.assignFee} wide>
        <form className="theme-form" onSubmit={submitAssign}>
          <div className="row">
            <div className="col-md-6">
              <Select label={t.portal.feesPage.headType} required value={assignForm.feeHeadId} onChange={(e) => {
                const h = heads.data?.heads.find((x) => x.id === Number(e.target.value));
                setAssignForm({ ...assignForm, feeHeadId: e.target.value, amount: h ? String(h.defaultAmount) : assignForm.amount });
              }}>
                <option value="">—</option>
                {(heads.data?.heads ?? []).map((h) => (
                  <option key={h.id} value={h.id}>{locale === "bn" && h.nameBn ? h.nameBn : h.name}</option>
                ))}
              </Select>
            </div>
            <div className="col-md-6">
              <Select label={t.portal.feesPage.assignTo} value={assignForm.target} onChange={(e) => setAssignForm({ ...assignForm, target: e.target.value as "CLASS" | "STUDENT" })}>
                <option value="CLASS">{t.portal.feesPage.entireClass}</option>
                <option value="STUDENT">{t.portal.feesPage.individualStudent}</option>
              </Select>
            </div>
            {assignForm.target === "CLASS" ? (
              <div className="col-md-6">
                <Select label={t.portal.studentsPage.classLabel} required value={assignForm.classItemId} onChange={(e) => setAssignForm({ ...assignForm, classItemId: e.target.value })}>
                  <option value="">—</option>
                  {classItems.map((c) => (
                    <option key={c.id} value={c.id}>{locale === "bn" ? c.nameBn : c.name}</option>
                  ))}
                </Select>
              </div>
            ) : (
              <div className="col-md-6">
                <Select label={t.portal.feesPage.studentLabel} required value={assignForm.studentId} onChange={(e) => setAssignForm({ ...assignForm, studentId: e.target.value })}>
                  <option value="">—</option>
                  {(students.data?.students ?? []).map((s) => (
                    <option key={s.id} value={s.id}>{s.roll} — {s.name}{s.className ? ` (${s.className})` : ""}</option>
                  ))}
                </Select>
              </div>
            )}
            <div className="col-md-6">
              <Input label={t.portal.feesPage.feeTitle} required value={assignForm.title} onChange={(e) => setAssignForm({ ...assignForm, title: e.target.value })} placeholder={t.portal.feesPage.feeTitlePlaceholder} />
            </div>
            <div className="col-md-6">
              <Input label={t.portal.feesPage.amountLabel} type="number" min="1" required value={assignForm.amount} onChange={(e) => setAssignForm({ ...assignForm, amount: e.target.value })} />
            </div>
            <div className="col-md-6">
              <DatePicker label={t.portal.feesPage.dueDate} value={assignForm.dueDate} onChange={(e) => setAssignForm({ ...assignForm, dueDate: e.target.value })} />
            </div>
            <div className="col-md-6">
              <MonthPicker label={t.portal.feesPage.month} value={assignForm.month} onChange={(e) => setAssignForm({ ...assignForm, month: e.target.value })} />
            </div>
          </div>
          <div className="d-flex justify-content-end gap-2 pt-2">
            <Button type="button" variant="secondary" onClick={() => setAssignModal(false)}>{t.common.cancel}</Button>
            <Button type="submit" loading={busy}>{t.portal.feesPage.assign}</Button>
          </div>
        </form>
      </Modal>

      {/* ---------- Collect modal ---------- */}
      <Modal open={collectModal} onClose={() => setCollectModal(false)} title={t.portal.feesPage.recordCollection}>
        <form className="theme-form" onSubmit={submitCollect}>
          <Select label={t.portal.feesPage.studentLabel} required value={collectForm.studentId} onChange={(e) => setCollectForm({ ...collectForm, studentId: e.target.value, feeScheduleId: "" })}>
            <option value="">—</option>
            {(students.data?.students ?? []).map((s) => (
              <option key={s.id} value={s.id}>{s.roll} — {s.name}</option>
            ))}
          </Select>
          <Select
            label={t.portal.feesPage.scheduleLabel}
            value={collectForm.feeScheduleId}
            onChange={(e) => {
              const sch = (schedules.data?.schedules ?? []).find((x) => x.id === Number(e.target.value));
              setCollectForm({
                ...collectForm,
                feeScheduleId: e.target.value,
                amount: sch ? String(sch.due) : collectForm.amount,
              });
            }}
            disabled={!collectForm.studentId}
          >
            <option value="">{t.portal.feesPage.unspecific}</option>
            {(schedules.data?.schedules ?? [])
              .filter((x) => x.student?.id === Number(collectForm.studentId) && x.due > 0)
              .map((x) => (
                <option key={x.id} value={x.id}>
                  {x.title} — {t.common.due}: {x.due}
                </option>
              ))}
          </Select>
          <Input label={t.portal.feesPage.amountLabel} type="number" min="1" required value={collectForm.amount} onChange={(e) => setCollectForm({ ...collectForm, amount: e.target.value })} />
          <Select label={t.common.method} value={collectForm.method} onChange={(e) => setCollectForm({ ...collectForm, method: e.target.value })}>
            {PAYMENT_METHODS.map((m) => (
              <option key={m} value={m}>{t.methods[m]}</option>
            ))}
          </Select>

          {/* method-specific transaction details */}
          {collectForm.method === "CASH" ? (
            <p className="ems-fs-11 text-muted mb-3 px-1">{t.portal.feesPage.cfCashHint}</p>
          ) : (
            <div className="p-3 mb-3 rounded" style={{ background: "rgba(36,105,92,0.05)" }}>
              <p className="ems-fs-11 f-w-700 text-uppercase text-muted mb-2">{t.portal.feesPage.cfDetailsTitle}</p>
              {MOBILE_METHODS.includes(collectForm.method) && (
                <div className="row g-2">
                  <div className="col-sm-6">
                    <Input
                      label={`${t.portal.feesPage.cfTxnId} *`}
                      value={collectForm.details.txnId ?? ""}
                      placeholder="8N7A1B2C"
                      onChange={(e) => setDetail("txnId", e.target.value)}
                    />
                  </div>
                  <div className="col-sm-6">
                    <Input
                      label={t.portal.feesPage.cfAccount}
                      value={collectForm.details.account ?? ""}
                      placeholder="01XXXXXXXXX"
                      onChange={(e) => setDetail("account", e.target.value)}
                    />
                  </div>
                </div>
              )}
              {collectForm.method === "BANK_TRANSFER" && (
                <div className="row g-2">
                  <div className="col-sm-6">
                    <Input
                      label={`${t.portal.feesPage.cfBank} *`}
                      value={collectForm.details.bankName ?? ""}
                      onChange={(e) => setDetail("bankName", e.target.value)}
                    />
                  </div>
                  <div className="col-sm-6">
                    <Input
                      label={t.portal.feesPage.cfBranch}
                      value={collectForm.details.branch ?? ""}
                      onChange={(e) => setDetail("branch", e.target.value)}
                    />
                  </div>
                  <div className="col-sm-6">
                    <Input
                      label={`${t.portal.feesPage.cfRef} *`}
                      value={collectForm.details.refNo ?? ""}
                      onChange={(e) => setDetail("refNo", e.target.value)}
                    />
                  </div>
                  <div className="col-sm-6">
                    <Input
                      label={t.portal.feesPage.cfAcNo}
                      value={collectForm.details.account ?? ""}
                      onChange={(e) => setDetail("account", e.target.value)}
                    />
                  </div>
                </div>
              )}
              {collectForm.method === "CARD" && (
                <div className="row g-2">
                  <div className="col-sm-4">
                    <Select label={t.portal.feesPage.cfCardType} value={collectForm.details.cardType ?? ""} onChange={(e) => setDetail("cardType", e.target.value)}>
                      <option value="">—</option>
                      <option value="credit">{t.portal.feesPage.cfCredit}</option>
                      <option value="debit">{t.portal.feesPage.cfDebit}</option>
                      <option value="prepaid">{t.portal.feesPage.cfPrepaid}</option>
                    </Select>
                  </div>
                  <div className="col-sm-4">
                    <Input
                      label={t.portal.feesPage.cfLast4}
                      value={collectForm.details.last4 ?? ""}
                      placeholder="1234"
                      maxLength={4}
                      onChange={(e) => setDetail("last4", e.target.value.replace(/\D/g, ""))}
                    />
                  </div>
                  <div className="col-sm-4">
                    <Input
                      label={`${t.portal.feesPage.cfApproval} *`}
                      value={collectForm.details.refNo ?? ""}
                      onChange={(e) => setDetail("refNo", e.target.value)}
                    />
                  </div>
                </div>
              )}
            </div>
          )}
          <div className="d-flex justify-content-end gap-2 pt-2">
            <Button type="button" variant="secondary" onClick={() => setCollectModal(false)}>{t.common.cancel}</Button>
            <Button type="submit" loading={busy} disabled={collectMissing.length > 0}>
              <IconWallet className="svg-16" />
              <span className="ms-1">{t.central.paymentsPage.collect}</span>
            </Button>
          </div>
        </form>
      </Modal>

      {/* Subject-fee assignment modal — per student */}
      <Modal
        open={sfModal}
        onClose={() => setSfModal(false)}
        title={`${t.portal.feesPage.sfAssign} — ${sfStudent ? (locale === "bn" && sfStudent.nameBn ? sfStudent.nameBn : sfStudent.name) : ""}`}
      >
        {sfStudent && (
          <form onSubmit={saveSfAssign}>
            <p className="ems-fs-12 text-muted">
              <span className="ems-roll-badge me-1">{sfStudent.roll}</span>
              {t.portal.feesPage.sfAssignHint}
            </p>
            {sfBasis && (
              <div className="alert alert-info ems-fs-12 py-2">
                {sfBasis === "GROUP" ? t.portal.feesPage.sfGroupPre : t.portal.feesPage.sfDeptPre}
              </div>
            )}
            <div style={{ maxHeight: 320, overflowY: "auto" }} className="pe-1">
              {sfItems.length === 0 ? (
                <EmptyState icon={<IconBook />} title={t.portal.feesPage.sfNoSubjects} />
              ) : (
                sfItems.map((row, idx) => (
                  <div
                    key={row.subjectItemId}
                    className="d-flex align-items-center gap-2 py-2"
                    style={{ borderBottom: "1px solid #e6edef" }}
                  >
                    <input
                      className="form-check-input mt-0 cursor-pointer"
                      type="checkbox"
                      checked={row.on}
                      onChange={(e) => toggleSfItem(idx, e.target.checked)}
                    />
                    <span className={`ems-fs-13 flex-grow-1 ${row.on ? "f-w-600" : "text-muted"}`}>
                      {locale === "bn" && row.nameBn ? row.nameBn : row.name}
                    </span>
                    <input
                      type="number"
                      min="0"
                      className="form-control form-control-sm text-end"
                      style={{ maxWidth: 120 }}
                      placeholder={sfRates[row.subjectItemId] ?? "0"}
                      value={row.fee}
                      disabled={!row.on}
                      onChange={(e) =>
                        setSfItems((prev) => prev.map((r, i) => (i === idx ? { ...r, fee: e.target.value } : r)))
                      }
                    />
                  </div>
                ))
              )}
            </div>
            <div className="d-flex align-items-center justify-content-between pt-3">
              <span className="ems-fs-13 f-w-700">
                {t.portal.feesPage.sfMonthly}:{" "}
                <span className="text-success">
                  {formatBDT(
                    sfItems.filter((i) => i.on).reduce((sum, i) => sum + (Number(i.fee) || 0), 0),
                    locale
                  )}
                </span>
              </span>
              <div className="d-flex gap-2">
                <Button type="button" variant="secondary" onClick={() => setSfModal(false)}>{t.common.cancel}</Button>
                <Button type="submit" loading={sfSaving}>{t.common.save}</Button>
              </div>
            </div>
          </form>
        )}
      </Modal>
    </div>
  );
}
