"use client";

import { useMemo, useState } from "react";
import { useLang } from "@/context/LanguageContext";
import {
  Alert,
  Button,
  EmptyState,
  Input,
  Modal,
  PageLoader,
  Pagination,
  Select,
  usePagination,
} from "@/components/ui";
import { api, useApi } from "@/lib/client";
import { downloadCsv, formatDate } from "@/lib/utils";
import {
  IconBan,
  IconCheck,
  IconClipboard,
  IconDownload,
  IconEdit,
  IconEye,
  IconEyeOff,
  IconKey,
  IconPlus,
  IconShield,
  IconTrash,
  IconUsers,
} from "@/components/icons";
import { emsConfirm } from "@/components/ConfirmDialog";
import { MenuAccessEditor } from "@/components/MenuAccessEditor";
import type { StaffSideRole } from "@/lib/constants";

export type CredentialRole = "STUDENT" | "PARENT" | StaffSideRole;

/** A person a credential can be linked to (student or staff). */
export interface PersonOption {
  id: number;
  label: string;
  sub: string;
  /** Default login username for this person (roll / employeeCode / roll-P). */
  defaultUsername: string;
  /** Display name pre-filled for the credential (student/guardian/staff name). */
  defaultName: string;
  /** For students: guardian name, used when creating the PARENT credential. */
  altName?: string;
}

interface Credential {
  id: number;
  name: string;
  username: string;
  role: string;
  status: "ACTIVE" | "INACTIVE";
  lastLoginAt: string | null;
  createdAt: string;
  studentId: number | null;
  linkedName: string | null;
  linkedDetail: string | null;
  linkedClass: string | null;
  linkedClassBn: string | null;
}

interface BulkResult {
  created: number;
  skipped: number;
  credentials: { username: string; password: string; name: string; roll: string; studentName: string }[];
}

/** 8-char readable password (ambiguous chars removed) for the generator. */
function generatePassword(): string {
  const chars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
  const pick = () => chars[Math.floor(Math.random() * chars.length)];
  let pwd = Array.from({ length: 8 }, pick).join("");
  if (!/[A-Z]/.test(pwd)) pwd = `A${pwd.slice(1)}`;
  if (!/[a-z]/.test(pwd)) pwd = `${pwd.slice(0, -1)}b`;
  if (!/\d/.test(pwd)) pwd = `${pwd.slice(0, -1)}5`;
  return pwd;
}

/**
 * Shared member-credential manager (Student / Teacher / Parent / Staff logins).
 * The Students page passes the student-linked roles, the HR page the
 * staff-linked ones — both hit the same /api/portal/credentials endpoints.
 * On the student side it also offers class-wise bulk creation with generated
 * passwords (handed out once via the result table / CSV).
 */
export function CredentialsPanel({
  roles,
  students,
  staff,
}: {
  roles: CredentialRole[];
  students?: PersonOption[];
  staff?: PersonOption[];
}) {
  const { t, locale } = useLang();
  const list = useApi<{ credentials: Credential[]; institutionCode: string }>("/api/portal/credentials");
  // classes for the bulk-create scope (student side only)
  const canBulk = !!students;
  const academic = useApi<{ items: { id: number; category: string; name: string; nameBn: string }[]; selectedIds: number[] }>(
    canBulk ? "/api/portal/academic" : null
  );

  const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
  const [open, setOpen] = useState(false);
  const [saving, setSaving] = useState(false);
  const [showPassword, setShowPassword] = useState(false);
  const [form, setForm] = useState({
    role: roles[0],
    personId: "",
    username: "",
    name: "",
    password: "",
  });
  const [resetId, setResetId] = useState<number | null>(null);
  const [newPassword, setNewPassword] = useState("");
  const [resetting, setResetting] = useState(false);
  const [showResetPassword, setShowResetPassword] = useState(false);
  const [editId, setEditId] = useState<number | null>(null);

  /* subscription menu access (shared editor, this institution) */
  const [menuOpen, setMenuOpen] = useState(false);
  const [editForm, setEditForm] = useState({ username: "", name: "" });
  const [editing, setEditing] = useState(false);

  // bulk-create state
  const [bulkOpen, setBulkOpen] = useState(false);
  const [bulkRole, setBulkRole] = useState<"STUDENT" | "PARENT">("STUDENT");
  const [bulkClassId, setBulkClassId] = useState("");
  const [bulkRunning, setBulkRunning] = useState(false);
  const [bulkResult, setBulkResult] = useState<BulkResult | null>(null);

  // list filters
  const [query, setQuery] = useState("");
  const [roleFilter, setRoleFilter] = useState("");

  const options = form.role === "STUDENT" || form.role === "PARENT" ? students ?? [] : staff ?? [];

  // students that already hold a login of each type (from the credential list)
  const linkedStudentIds = useMemo(() => {
    const map = new Map<string, Set<number>>();
    for (const c of list.data?.credentials ?? []) {
      if (c.studentId == null) continue;
      const set = map.get(c.role) ?? new Set<number>();
      set.add(c.studentId);
      map.set(c.role, set);
    }
    return map;
  }, [list.data]);

  function pickRole(role: CredentialRole) {
    setForm({ role, personId: "", username: "", name: "", password: "" });
  }

  function pickPerson(id: string) {
    const person = options.find((o) => String(o.id) === id);
    const role = form.role;
    // Parent default username appends "-P" to the student roll
    const suffix = role === "PARENT" ? "-P" : "";
    setForm({
      ...form,
      personId: id,
      username: person ? `${person.defaultUsername}${suffix}` : "",
      name: person ? (role === "PARENT" ? person.altName ?? person.defaultName : person.defaultName) : "",
    });
  }

  async function copyText(text: string) {
    try {
      await navigator.clipboard.writeText(text);
      setMessage({ type: "success", text: t.common.copied });
    } catch {
      setMessage({ type: "error", text: t.common.tryAgain });
    }
  }

  async function createCredential(e: React.FormEvent) {
    e.preventDefault();
    setSaving(true);
    try {
      await api("/api/portal/credentials", {
        method: "POST",
        body: JSON.stringify({
          role: form.role,
          username: form.username.trim(),
          password: form.password,
          name: form.name.trim(),
          studentId: form.role === "STUDENT" || form.role === "PARENT" ? Number(form.personId) || null : null,
          staffId: form.role === "TEACHER" || form.role === "STAFF" ? Number(form.personId) || null : null,
        }),
      });
      setOpen(false);
      setForm({ role: roles[0], personId: "", username: "", name: "", password: "" });
      setMessage({ type: "success", text: t.portal.credentials.created });
      list.reload();
    } catch (err) {
      const code = err instanceof Error ? err.message : "";
      setMessage({
        type: "error",
        text: code === "USERNAME_EXISTS"
          ? t.portal.credentials.usernameExists
          : code === "MISSING_FIELDS"
            ? t.common.minPassword
            : code === "STUDENT_ALREADY_LINKED"
              ? t.portal.credentials.alreadyLinked
              : code === "STAFF_ALREADY_LINKED"
                ? t.portal.credentials.alreadyLinked
                : t.common.tryAgain,
      });
      setOpen(false);
    } finally {
      setSaving(false);
    }
  }

  async function saveReset(e: React.FormEvent) {
    e.preventDefault();
    if (resetId == null) return;
    setResetting(true);
    try {
      await api(`/api/portal/credentials/${resetId}`, {
        method: "PATCH",
        body: JSON.stringify({ password: newPassword }),
      });
      setResetId(null);
      setNewPassword("");
      setMessage({ type: "success", text: t.portal.credentials.passwordReset });
      list.reload();
    } catch (err) {
      const code = err instanceof Error ? err.message : "";
      setMessage({
        type: "error",
        text: code === "PASSWORD_TOO_SHORT" ? t.common.minPassword : t.common.tryAgain,
      });
      setResetId(null);
    } finally {
      setResetting(false);
    }
  }

  function openEdit(c: Credential) {
    setEditId(c.id);
    setEditForm({ username: c.username, name: c.name });
  }

  async function saveEdit(e: React.FormEvent) {
    e.preventDefault();
    if (editId == null) return;
    setEditing(true);
    try {
      await api(`/api/portal/credentials/${editId}`, {
        method: "PATCH",
        body: JSON.stringify({ username: editForm.username.trim(), name: editForm.name.trim() }),
      });
      setEditId(null);
      setMessage({ type: "success", text: t.portal.credentials.updated });
      list.reload();
    } catch (err) {
      const code = err instanceof Error ? err.message : "";
      setMessage({
        type: "error",
        text: code === "USERNAME_EXISTS" ? t.portal.credentials.usernameExists : t.common.tryAgain,
      });
      setEditId(null);
    } finally {
      setEditing(false);
    }
  }

  async function toggleStatus(c: Credential) {
    const disabling = c.status === "ACTIVE";
    if (
      disabling &&
      !(await emsConfirm({ message: t.portal.credentials.disableConfirm.replace("{name}", c.username), danger: true }))
    )
      return;
    try {
      await api(`/api/portal/credentials/${c.id}`, {
        method: "PATCH",
        body: JSON.stringify({ status: disabling ? "INACTIVE" : "ACTIVE" }),
      });
      setMessage({ type: "success", text: t.portal.credentials.updated });
      list.reload();
    } catch {
      setMessage({ type: "error", text: t.common.tryAgain });
    }
  }

  async function removeCredential(c: Credential) {
    if (!(await emsConfirm({ message: t.portal.credentials.deleteConfirm.replace("{name}", c.username), danger: true }))) return;
    try {
      await api(`/api/portal/credentials/${c.id}`, { method: "DELETE" });
      setMessage({ type: "success", text: t.portal.credentials.deleted });
      list.reload();
    } catch {
      setMessage({ type: "error", text: t.common.tryAgain });
    }
  }

  async function runBulk(e: React.FormEvent) {
    e.preventDefault();
    setBulkRunning(true);
    setBulkResult(null);
    try {
      const res = await api<BulkResult>("/api/portal/credentials/bulk", {
        method: "POST",
        body: JSON.stringify({
          role: bulkRole,
          classItemId: bulkClassId ? Number(bulkClassId) : null,
        }),
      });
      setBulkResult(res);
      list.reload();
    } catch (err) {
      const code = err instanceof Error ? err.message : "";
      setMessage({
        type: "error",
        text: code === "NO_STUDENTS_IN_SCOPE" ? t.portal.credentials.noStudentsInScope : t.common.tryAgain,
      });
    } finally {
      setBulkRunning(false);
    }
  }

  function exportListCsv() {
    const header = [
      t.portal.credentials.username, t.common.name,
      t.portal.credentials.role, t.portal.credentials.linkedTo,
      t.portal.studentsPage.classLabel, t.common.status,
      t.portal.credentials.lastLogin, t.common.createdAt,
    ];
    const rows = (list.data?.credentials ?? []).map((c) => [
      c.username, c.name,
      t.status[c.role as keyof typeof t.status] ?? c.role,
      `${c.linkedName ?? ""} ${c.linkedDetail ?? ""}`.trim(),
      locale === "bn" && c.linkedClassBn ? c.linkedClassBn : c.linkedClass ?? "",
      c.status === "ACTIVE" ? t.common.active : t.common.inactive,
      c.lastLoginAt ? formatDate(c.lastLoginAt, locale) : "",
      formatDate(c.createdAt, locale),
    ]);
    downloadCsv("credentials", [header, ...rows]);
  }

  function exportBulkCsv() {
    if (!bulkResult) return;
    const header = [t.portal.credentials.roll, t.common.name, t.portal.credentials.username, t.common.password];
    const rows = bulkResult.credentials.map((c) => [c.roll, c.studentName, c.username, c.password]);
    downloadCsv(`new-${bulkRole.toLowerCase()}-credentials`, [header, ...rows]);
  }

  const allCredentials = list.data?.credentials ?? [];
  const credentials = useMemo(() => {
    const q = query.trim().toLowerCase();
    return allCredentials.filter((c) => {
      if (roleFilter && c.role !== roleFilter) return false;
      if (!q) return true;
      return (
        c.username.toLowerCase().includes(q) ||
        c.name.toLowerCase().includes(q) ||
        (c.linkedName ?? "").toLowerCase().includes(q) ||
        (c.linkedDetail ?? "").toLowerCase().includes(q)
      );
    });
  }, [allCredentials, query, roleFilter]);
  const pager = usePagination(credentials, 10);

  const roleBadgeVariant = useMemo(() => {
    const map: Record<string, string> = {
      STUDENT: "badge-primary",
      TEACHER: "badge-success",
      PARENT: "badge-warning",
      STAFF: "badge-secondary",
    };
    return map;
  }, []);

  const classes = useMemo(() => {
    const selected = new Set(academic.data?.selectedIds ?? []);
    return (academic.data?.items ?? []).filter((i) => i.category === "CLASS" && selected.has(i.id));
  }, [academic.data]);

  const resetTarget = allCredentials.find((c) => c.id === resetId);
  const editTarget = allCredentials.find((c) => c.id === editId);

  return (
    <div className="card">
      <div className="card-header d-flex flex-wrap align-items-center justify-content-between gap-2">
        <h5 className="mb-0 f-w-700">{t.portal.credentials.title}</h5>
        <div className="d-flex flex-wrap gap-2">
          <Button size="sm" variant="secondary" onClick={() => setMenuOpen(true)}>
            <IconShield className="svg-16" />
            <span className="ms-1">{t.portal.credentials.menuAccess}</span>
          </Button>
          {allCredentials.length > 0 && (
            <Button size="sm" variant="secondary" onClick={exportListCsv}>
              <IconDownload className="svg-16" />
              <span className="ms-1">CSV</span>
            </Button>
          )}
          {canBulk && (
            <Button size="sm" variant="secondary" onClick={() => { setBulkOpen(true); setBulkResult(null); }}>
              <IconUsers className="svg-16" />
              <span className="ms-1">{t.portal.credentials.bulk}</span>
            </Button>
          )}
          <Button size="sm" onClick={() => setOpen(true)}>
            <IconPlus className="svg-16" />
            <span className="ms-1">{t.portal.credentials.create}</span>
          </Button>
        </div>
      </div>

      {list.data?.institutionCode && (
        <div className="card-body py-2 border-bottom">
          <p className="mb-0 ems-fs-12">
            <IconKey className="svg-14 me-1" />
            {t.portal.credentials.infoCode}{" "}
            <code className="ems-invoice-badge ms-1">{list.data.institutionCode}</code>
          </p>
        </div>
      )}

      {message && (
        <div className="card-body py-2">
          <Alert type={message.type} onClose={() => setMessage(null)}>{message.text}</Alert>
        </div>
      )}

      {list.loading && !list.data ? (
        <div className="card-body">
          <PageLoader text={t.common.loading} />
        </div>
      ) : allCredentials.length === 0 ? (
        <div className="card-body">
          <EmptyState icon={<IconKey />} title={t.portal.credentials.noCredentials} />
        </div>
      ) : (
        <>
          <div className="card-body py-2 border-bottom">
            <div className="row g-2 align-items-end">
              <div className="col-md-7">
                <div className="form-group mb-0">
                  <label className="col-form-label pt-0 f-w-600">{t.common.search}</label>
                  <input
                    className="form-control"
                    value={query}
                    onChange={(e) => setQuery(e.target.value)}
                    placeholder={t.portal.credentials.searchPlaceholder}
                  />
                </div>
              </div>
              <div className="col-md-5">
                <Select
                  label={t.portal.credentials.role}
                  value={roleFilter}
                  onChange={(e) => setRoleFilter(e.target.value)}
                  wrapperClassName="form-group mb-0"
                >
                  <option value="">{t.portal.credentials.filterAllRoles}</option>
                  {roles.map((r) => (
                    <option key={r} value={r}>
                      {t.status[r as keyof typeof t.status]}
                    </option>
                  ))}
                </Select>
              </div>
            </div>
          </div>
          <div className="table-responsive">
            <table className="table table-hover align-middle mb-0">
              <thead className="ems-table-head">
                <tr>
                  <th>{t.portal.credentials.username}</th>
                  <th>{t.common.name}</th>
                  <th>{t.portal.credentials.linkedTo}</th>
                  <th>{t.portal.credentials.lastLogin}</th>
                  <th className="text-center">{t.common.status}</th>
                  <th className="text-end">{t.common.actions}</th>
                </tr>
              </thead>
              <tbody>
                {pager.paged.map((c) => (
                  <tr key={c.id} className={c.status === "INACTIVE" ? "opacity-75" : ""}>
                    <td>
                      <code className="ems-invoice-badge">{c.username}</code>
                    </td>
                    <td className="f-w-600">
                      {c.name}
                      <div>
                        <span className={`badge ${roleBadgeVariant[c.role] ?? "badge-secondary"} mt-1`}>
                          {t.status[c.role as keyof typeof t.status]}
                        </span>
                      </div>
                    </td>
                    <td className="ems-fs-12 text-muted">
                      {c.linkedName ?? "—"}
                      {c.linkedDetail && <p className="mb-0 ems-fs-11">{c.linkedDetail}</p>}
                      {(locale === "bn" && c.linkedClassBn ? c.linkedClassBn : c.linkedClass) && (
                        <p className="mb-0 ems-fs-11">
                          {locale === "bn" && c.linkedClassBn ? c.linkedClassBn : c.linkedClass}
                        </p>
                      )}
                    </td>
                    <td className="ems-fs-12 text-muted">
                      {c.lastLoginAt ? formatDate(c.lastLoginAt, locale) : "—"}
                    </td>
                    <td className="text-center">
                      {c.status === "ACTIVE" ? (
                        <span className="badge badge-success">{t.common.active}</span>
                      ) : (
                        <span className="badge badge-danger">{t.common.inactive}</span>
                      )}
                    </td>
                    <td className="text-end">
                      <div className="d-inline-flex gap-1">
                        <Button size="sm" variant="secondary" onClick={() => openEdit(c)} title={t.common.edit}>
                          <IconEdit className="svg-16" />
                        </Button>
                        <Button
                          size="sm"
                          variant={c.status === "ACTIVE" ? "warning" : "success"}
                          onClick={() => toggleStatus(c)}
                          title={c.status === "ACTIVE" ? t.portal.credentials.disable : t.portal.credentials.enable}
                        >
                          {c.status === "ACTIVE" ? <IconBan className="svg-16" /> : <IconCheck className="svg-16" />}
                        </Button>
                        <Button size="sm" variant="secondary" onClick={() => { setResetId(c.id); setNewPassword(""); setShowResetPassword(false); }}>
                          {t.portal.credentials.resetPassword}
                        </Button>
                        <Button size="sm" variant="danger" onClick={() => removeCredential(c)} title={t.common.delete}>
                          <IconTrash className="svg-16" />
                        </Button>
                      </div>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          <Pagination {...pager} onPageChange={pager.setPage} onSizeChange={pager.setSize} />
        </>
      )}

      {/* Create credential modal */}
      <Modal open={open} onClose={() => setOpen(false)} title={t.portal.credentials.create}>
        <form className="theme-form" onSubmit={createCredential}>
          <div className="row">
            <div className="col-md-6">
              <Select
                label={t.portal.credentials.role}
                value={form.role}
                onChange={(e) => pickRole(e.target.value as CredentialRole)}
              >
                {roles.map((r) => (
                  <option key={r} value={r}>
                    {t.status[r as keyof typeof t.status]}
                  </option>
                ))}
              </Select>
            </div>
            <div className="col-md-6">
              <Select
                label={form.role === "STUDENT" || form.role === "PARENT" ? t.portal.credentials.student : t.portal.credentials.staffMember}
                value={form.personId}
                required
                onChange={(e) => pickPerson(e.target.value)}
              >
                <option value="">— {t.common.filter} —</option>
                {options.map((o) => {
                  const linked = linkedStudentIds.get(form.role)?.has(o.id);
                  return (
                    <option key={o.id} value={o.id}>
                      {o.label} ({o.sub}){linked ? ` · ${t.portal.credentials.hasLogin}` : ""}
                    </option>
                  );
                })}
              </Select>
            </div>
            <div className="col-md-6">
              <Input
                label={t.portal.credentials.username}
                required
                value={form.username}
                onChange={(e) => setForm({ ...form, username: e.target.value })}
              />
            </div>
            <div className="col-md-6">
              <div className="form-group mb-3">
                <label className="col-form-label pt-0 f-w-600">{t.common.password}</label>
                <div className="input-group">
                  <input
                    className="form-control"
                    type={showPassword ? "text" : "password"}
                    required
                    value={form.password}
                    onChange={(e) => setForm({ ...form, password: e.target.value })}
                  />
                  <button
                    type="button"
                    className="btn btn-outline-secondary"
                    onClick={() => setShowPassword((s) => !s)}
                    title={showPassword ? t.portal.credentials.hide : t.portal.credentials.show}
                  >
                    {showPassword ? <IconEyeOff className="svg-16" /> : <IconEye className="svg-16" />}
                  </button>
                  <button
                    type="button"
                    className="btn btn-outline-secondary"
                    onClick={() => setForm((f) => ({ ...f, password: generatePassword() }))}
                    title={t.portal.credentials.generate}
                  >
                    <IconKey className="svg-16" />
                  </button>
                </div>
              </div>
            </div>
            <div className="col-md-12">
              <Input
                label={`${t.common.name} (${t.common.optional})`}
                value={form.name}
                onChange={(e) => setForm({ ...form, name: e.target.value })}
              />
            </div>
          </div>
          <div className="d-flex justify-content-end gap-2">
            <Button type="button" variant="secondary" onClick={() => setOpen(false)}>
              {t.common.cancel}
            </Button>
            <Button type="submit" loading={saving} disabled={!form.personId || !form.username.trim() || form.password.length < 6}>
              {t.common.save}
            </Button>
          </div>
        </form>
      </Modal>

      {/* Edit credential modal (username / display name) */}
      <Modal
        open={editId !== null}
        onClose={() => setEditId(null)}
        title={`${t.portal.credentials.editTitle} — ${editTarget?.username ?? ""}`}
      >
        <form className="theme-form" onSubmit={saveEdit}>
          <Input
            label={t.portal.credentials.username}
            required
            value={editForm.username}
            onChange={(e) => setEditForm({ ...editForm, username: e.target.value })}
          />
          <Input
            label={t.common.name}
            required
            value={editForm.name}
            onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
          />
          <div className="d-flex justify-content-end gap-2">
            <Button type="button" variant="secondary" onClick={() => setEditId(null)}>
              {t.common.cancel}
            </Button>
            <Button type="submit" loading={editing} disabled={!editForm.username.trim() || !editForm.name.trim()}>
              {t.common.save}
            </Button>
          </div>
        </form>
      </Modal>

      {/* Reset password modal */}
      <Modal
        open={resetId !== null}
        onClose={() => setResetId(null)}
        title={`${t.portal.credentials.resetPassword} — ${resetTarget?.username ?? ""}`}
      >
        <form className="theme-form" onSubmit={saveReset}>
          <div className="form-group mb-3">
            <label className="col-form-label pt-0 f-w-600">{t.portal.credentials.newPassword}</label>
            <div className="input-group">
              <input
                className="form-control"
                type={showResetPassword ? "text" : "password"}
                required
                value={newPassword}
                onChange={(e) => setNewPassword(e.target.value)}
              />
              <button
                type="button"
                className="btn btn-outline-secondary"
                onClick={() => setShowResetPassword((s) => !s)}
                title={showResetPassword ? t.portal.credentials.hide : t.portal.credentials.show}
              >
                {showResetPassword ? <IconEyeOff className="svg-16" /> : <IconEye className="svg-16" />}
              </button>
              <button
                type="button"
                className="btn btn-outline-secondary"
                onClick={() => setNewPassword(generatePassword())}
                title={t.portal.credentials.generate}
              >
                <IconKey className="svg-16" />
              </button>
              <button
                type="button"
                className="btn btn-outline-secondary"
                onClick={() => newPassword && copyText(newPassword)}
                title={t.portal.credentials.copy}
                disabled={newPassword.length < 6}
              >
                <IconClipboard className="svg-16" />
              </button>
            </div>
          </div>
          <div className="d-flex justify-content-end gap-2">
            <Button type="button" variant="secondary" onClick={() => setResetId(null)}>
              {t.common.cancel}
            </Button>
            <Button type="submit" loading={resetting} disabled={newPassword.length < 6}>
              {t.common.save}
            </Button>
          </div>
        </form>
      </Modal>

      {/* Bulk create modal (student side) */}
      <Modal open={bulkOpen} onClose={() => setBulkOpen(false)} title={t.portal.credentials.bulkTitle}>
        {!bulkResult ? (
          <form className="theme-form" onSubmit={runBulk}>
            <p className="ems-fs-12 text-muted">{t.portal.credentials.bulkHint}</p>
            <div className="row">
              <div className="col-md-6">
                <Select
                  label={t.portal.credentials.role}
                  value={bulkRole}
                  onChange={(e) => setBulkRole(e.target.value as "STUDENT" | "PARENT")}
                >
                  <option value="STUDENT">{t.status.STUDENT}</option>
                  <option value="PARENT">{t.status.PARENT}</option>
                </Select>
              </div>
              <div className="col-md-6">
                <Select
                  label={t.portal.credentials.bulkClass}
                  value={bulkClassId}
                  onChange={(e) => setBulkClassId(e.target.value)}
                >
                  <option value="">{t.portal.studentsPage.allClasses}</option>
                  {classes.map((c) => (
                    <option key={c.id} value={c.id}>
                      {locale === "bn" && c.nameBn ? c.nameBn : c.name}
                    </option>
                  ))}
                </Select>
              </div>
            </div>
            <div className="d-flex justify-content-end gap-2">
              <Button type="button" variant="secondary" onClick={() => setBulkOpen(false)}>
                {t.common.cancel}
              </Button>
              <Button type="submit" loading={bulkRunning} disabled={academic.loading}>
                <IconUsers className="svg-16" />
                <span className="ms-1">{t.portal.credentials.bulkRun}</span>
              </Button>
            </div>
          </form>
        ) : (
          <div>
            <Alert type={bulkResult.created > 0 ? "success" : "warning"}>
              {t.portal.credentials.bulkResult
                .replace("{n}", String(bulkResult.created))
                .replace("{m}", String(bulkResult.skipped))}
            </Alert>
            {bulkResult.credentials.length > 0 && (
              <>
                <p className="ems-fs-12 text-danger">{t.portal.credentials.bulkResultHint}</p>
                <div className="table-responsive mb-3" style={{ maxHeight: 320 }}>
                  <table className="table table-hover align-middle mb-0">
                    <thead className="ems-table-head">
                      <tr>
                        <th>{t.portal.credentials.roll}</th>
                        <th>{t.common.name}</th>
                        <th>{t.portal.credentials.username}</th>
                        <th>{t.common.password}</th>
                      </tr>
                    </thead>
                    <tbody>
                      {bulkResult.credentials.map((c) => (
                        <tr key={c.username}>
                          <td>
                            <span className="ems-roll-badge">{c.roll}</span>
                          </td>
                          <td className="ems-fs-13">{c.studentName}</td>
                          <td>
                            <code className="ems-invoice-badge">{c.username}</code>
                          </td>
                          <td className="font-monospace ems-fs-13">{c.password}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
                <div className="d-flex justify-content-end gap-2">
                  <Button variant="secondary" onClick={exportBulkCsv}>
                    <IconDownload className="svg-16" />
                    <span className="ms-1">CSV</span>
                  </Button>
                  <Button onClick={() => setBulkOpen(false)}>{t.common.close}</Button>
                </div>
              </>
            )}
          </div>
        )}
      </Modal>

      {/* subscription menu access — shared editor for this institution */}
      <MenuAccessEditor
        open={menuOpen}
        onClose={() => setMenuOpen(false)}
        endpoint="/api/portal/menu-access"
      />
    </div>
  );
}
