"use client";

import { useCallback, useState } from "react";
import { useSearchParams } from "next/navigation";
import { Copy, RotateCcw, Wrench } from "lucide-react";
import { useTranslation } from "react-i18next";
import type { TFunction } from "i18next";
import { useAsyncEffect } from "@/hooks/use-async-effect";
import { useTranslationRef } from "@/hooks/use-translation-ref";
import { toast } from "sonner";

import {
  getAdminPlayerWallets,
  getAdminTransferOrders,
  getAdminWalletTransactions,
  reverseTransferOrder,
  manuallyProcessTransferOrder,
  completeTransferInCredit,
} from "@/api/admin-wallet";
import { AdminDateRangeField } from "@/components/admin/admin-date-range-field";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { AdminAgentIdentityCells, AdminAgentIdentityHeads } from "@/components/admin/admin-agent-columns";
import { AdminPlayerIdentityCells, AdminPlayerIdentityHeads } from "@/components/admin/admin-player-identity-columns";
import { AdminRowActionsMenu } from "@/components/admin/admin-row-actions-menu";
import { AdminTableExportButton } from "@/components/admin/admin-table-export-button";
import { AdminStatusBadge } from "@/components/admin/admin-status-badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { AdminTableNoResourceRow } from "@/components/admin/admin-no-resource-state";
import { AdminTableLoadingRow } from "@/components/admin/admin-loading-state";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { adminHasAnyPermission } from "@/lib/admin-permissions";
import { PRD_WALLET_WRITE_ANY } from "@/lib/admin-prd";
import { useAdminProfile } from "@/stores/admin-session";
import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog";
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
import { useConfirmAction } from "@/hooks/use-confirm-action";
import { useExportLabels } from "@/hooks/use-export-labels";
import { PlayerLedgerSourceBadge } from "@/components/admin/player-funding-badges";
import { formatAdminMinorUnits } from "@/lib/money";
import { creditLedgerReasonLabel } from "@/modules/settlement/settlement-status-label";
import { LotteryApiBizError } from "@/types/api/errors";
import type {
  AdminPlayerWalletsData,
  AdminTransferOrderItem,
  AdminTransferOrderListData,
  AdminWalletTxnListData,
} from "@/types/api/admin-wallet";

/** 长单号/流水号：单行截断；点击复制全文，悬停可看全文 */
function CellMonoId({
  value,
  empty = "—",
  copyHint,
}: {
  value: string | null | undefined;
  empty?: string;
  /** 用于 toast / 无障碍：如「流水号」「主站流水号」 */
  copyHint?: string;
}): React.ReactElement {
  const { t } = useTranslation("wallet");
  if (value == null || value === "") {
    return <span className="text-muted-foreground">{empty}</span>;
  }

  const copy = async (e: React.MouseEvent): Promise<void> => {
    e.stopPropagation();
    try {
      await navigator.clipboard.writeText(value);
      toast.success(
        copyHint
          ? t("copySuccess", { label: copyHint })
          : t("copySuccess", { label: "" }).trim(),
      );
    } catch {
      toast.error(t("copyFailed"));
    }
  };

  return (
    <button
      type="button"
      className="group inline-flex min-w-0 w-full max-w-full items-center gap-1 rounded-md border border-transparent px-0.5 py-0.5 text-left font-mono text-xs transition-colors hover:border-border hover:bg-muted/60"
      title={value}
      aria-label={copyHint ?? t("copyTxnNo")}
      onClick={(e) => void copy(e)}
    >
      <span className="min-w-0 flex-1 truncate">{value}</span>
      <Copy
        className="size-3.5 shrink-0 text-muted-foreground opacity-60 group-hover:opacity-100"
        aria-hidden
      />
    </button>
  );
}

function walletTxnBizTypeLabel(
  bizType: string,
  ledgerSource: string | null | undefined,
  t: (key: string) => string,
  tSettlement: TFunction<"settlementCenter">,
): string {
  if (ledgerSource === "credit_ledger") {
    return creditLedgerReasonLabel(bizType, tSettlement);
  }

  switch (bizType) {
    case "transfer_in":
      return t("transferIn");
    case "transfer_out":
      return t("transferOut");
    case "transfer_out_refund":
      return t("transferOutRefund");
    case "bet_deduct":
      return t("bizBetDeduct");
    case "bet_reverse":
      return t("bizBetReverse");
    case "settle_payout":
      return t("bizSettlePayout");
    case "jackpot_manual_payout":
      return t("bizJackpotPayout");
    case "settlement_adjustment":
      return t("bizSettlementAdjustment");
    default:
      return bizType;
  }
}

function statusLabelT(status: string, t: (key: string) => string): string {
  switch (status) {
    case "processing":
      return t("statusProcessing");
    case "success":
      return t("statusSuccess");
    case "failed":
      return t("statusFailed");
    case "pending_reconcile":
      return t("statusPendingReconcile");
    case "reversed":
      return t("statusReversed");
    case "manually_processed":
      return t("statusCaseClosed");
    case "posted":
      return t("statusPosted");
    default:
      return status;
  }
}

type TransferFilters = {
  playerId: string;
  playerAccount: string;
  transferNo: string;
  externalRefNo: string;
  createdFrom: string;
  createdTo: string;
  statusCsv: string;
  abnormalOnly: boolean;
};

const emptyTransferFilters: TransferFilters = {
  playerId: "",
  playerAccount: "",
  transferNo: "",
  externalRefNo: "",
  createdFrom: "",
  createdTo: "",
  statusCsv: "",
  abnormalOnly: false,
};

type TxnFilters = {
  playerId: string;
  playerAccount: string;
  txnNo: string;
  externalRefNo: string;
  bizType: string;
  statusCsv: string;
  createdFrom: string;
  createdTo: string;
  abnormalOnly: boolean;
};

const emptyTxnFilters: TxnFilters = {
  playerId: "",
  playerAccount: "",
  txnNo: "",
  externalRefNo: "",
  bizType: "",
  statusCsv: "",
  createdFrom: "",
  createdTo: "",
  abnormalOnly: false,
};

/** 下拉「不限」值；请求时转为空串不传参 */
const WALLET_FILTER_ALL = "__all__";

/** 与 {@see WalletTransactionListController}、{@see LotteryTransferService} 当前写入的 biz_type 一致 */
const WALLET_TXN_BIZ_OPTIONS: { value: string; label: string }[] = [
  { value: "transfer_in", label: "transferIn" },
  { value: "transfer_out", label: "transferOut" },
  { value: "transfer_out_refund", label: "transferOutRefund" },
];

/** 与 {@see WalletTransactionListController::ALLOWED_STATUS} 一致 */
const WALLET_TXN_STATUS_OPTIONS: { value: string; label: string }[] = [
  { value: "posted", label: "statusPosted" },
  { value: "pending_reconcile", label: "statusPendingReconcile" },
  { value: "reversed", label: "statusReversed" },
];

/** 与 {@see TransferOrderListController::ALLOWED_STATUS} 一致 */
const TRANSFER_ORDER_STATUS_OPTIONS: { value: string; label: string }[] = [
  { value: "processing", label: "statusProcessing" },
  { value: "success", label: "statusSuccess" },
  { value: "failed", label: "statusFailed" },
  { value: "pending_reconcile", label: "statusPendingReconcile" },
  { value: "reversed", label: "statusReversed" },
  { value: "manually_processed", label: "statusCaseClosed" },
];

/** Base UI 的 SelectValue 会直接显示 `value`，需把哨兵转成「不限」、其余转成选项文案 */
function walletAdminSelectDisplayedLabel(
  raw: unknown,
  options: readonly { value: string; label: string }[],
  t?: (key: string) => string,
): string {
  const v = raw == null ? "" : String(raw);
  if (v === "" || v === WALLET_FILTER_ALL) {
    return t ? t("filterAll") : "All";
  }
  const key = options.find((o) => o.value === v)?.label;
  return key ? (t ? t(key) : key) : v;
}

function canReverseTransferOrder(
  row: { status: string; can_reverse?: boolean },
  canWriteWallet: boolean,
): boolean {
  return canWriteWallet && (row.can_reverse ?? row.status === "pending_reconcile");
}

function canCompleteTransferInCredit(
  row: {
    direction: string;
    status: string;
    fail_reason?: string | null;
    external_ref_no?: string | null;
    can_complete_credit?: boolean;
  },
  canWriteWallet: boolean,
): boolean {
  return (
    canWriteWallet &&
    (row.can_complete_credit ??
      (row.direction === "in" &&
        row.status === "pending_reconcile" &&
        row.fail_reason === "lottery_credit_failed" &&
        Boolean(row.external_ref_no?.trim())))
  );
}

function canManuallyProcessTransferOrder(
  row: {
    direction?: string;
    status: string;
    fail_reason?: string | null;
    can_manually_process?: boolean;
  },
  canWriteWallet: boolean,
): boolean {
  return (
    canWriteWallet &&
    (row.can_manually_process ??
      (["processing", "failed", "pending_reconcile"].includes(row.status) &&
        !(row.direction === "out" && row.status === "pending_reconcile") &&
        row.fail_reason !== "lottery_credit_failed"))
  );
}

type TransferOrderRowActionsProps = {
  row: AdminTransferOrderItem;
  canWriteWallet: boolean;
  busy: boolean;
  onCompleteCredit: (transferNo: string) => void;
  onReverse: (transferNo: string) => void;
  onManualProcess: (transferNo: string) => void;
  t: (key: string) => string;
};

function TransferOrderRowActions({
  row,
  canWriteWallet,
  busy,
  onCompleteCredit,
  onReverse,
  onManualProcess,
  t,
}: TransferOrderRowActionsProps): React.ReactElement {
  return (
    <AdminRowActionsMenu
      busy={busy}
      ariaLabel={t("actionsMenuAriaLabel")}
      actions={[
        {
          key: "complete",
          label: t("completeCredit"),
          hidden: !canCompleteTransferInCredit(row, canWriteWallet),
          onClick: () => onCompleteCredit(row.transfer_no),
        },
        {
          key: "manual",
          label: t("markCaseClosed"),
          icon: Wrench,
          hidden: !canManuallyProcessTransferOrder(row, canWriteWallet),
          onClick: () => onManualProcess(row.transfer_no),
        },
        {
          key: "reverse",
          label: t("reverse"),
          icon: RotateCcw,
          destructive: true,
          hidden: !canReverseTransferOrder(row, canWriteWallet),
          onClick: () => onReverse(row.transfer_no),
        },
      ]}
    />
  );
}

export function TransferOrdersPanel(): React.ReactElement {
  const { t } = useTranslation(["wallet", "common"]);
  const tRef = useTranslationRef(["wallet", "common"]);
  const { request: requestConfirm, ConfirmDialog } = useConfirmAction();
  const profile = useAdminProfile();
  const canWriteWallet = adminHasAnyPermission(profile?.permissions, [...PRD_WALLET_WRITE_ANY]);
  const exportLabels = useExportLabels("walletTransferOrders");
  useAdminCurrencyCatalog();
  const formatTs = useAdminDateTimeFormatter();
  const searchParams = useSearchParams();
  const playerIdFromUrl = (searchParams.get("player_id") ?? "").trim();
  const transferNoFromUrl = (searchParams.get("transfer_no") ?? "").trim();
  const externalRefNoFromUrl = (searchParams.get("external_ref_no") ?? "").trim();
  const [data, setData] = useState<AdminTransferOrderListData | null>(null);
  const [loading, setLoading] = useState(true);
  const [err, setErr] = useState<string | null>(null);
  const [page, setPage] = useState(1);
  const [perPage, setPerPage] = useState(10);
  const initialTransferFilters: TransferFilters = {
    ...emptyTransferFilters,
    playerId: playerIdFromUrl,
    transferNo: transferNoFromUrl,
    externalRefNo: externalRefNoFromUrl,
  };
  const [draft, setDraft] = useState<TransferFilters>(initialTransferFilters);
  const [applied, setApplied] = useState<TransferFilters>(initialTransferFilters);
  const [actionLoading, setActionLoading] = useState<Set<string>>(new Set());

  const doAction = async (
    transferNo: string,
    fn: () => Promise<unknown>,
    successMsg: string,
  ) => {
    setActionLoading((prev) => new Set(prev).add(transferNo));
    try {
      await fn();
      toast.success(successMsg);
      void load();
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("actionFailed"));
    } finally {
      setActionLoading((prev) => {
        const next = new Set(prev);
        next.delete(transferNo);
        return next;
      });
    }
  };

  const handleReverse = (transferNo: string) =>
    requestConfirm({
      title: t("confirm.reverseTitle"),
      description: t("confirm.reverseDescription", { transferNo }),
      confirmVariant: "destructive",
      onConfirm: () => doAction(transferNo, () => reverseTransferOrder(transferNo), t("reverseSuccess")),
    });

  const handleManuallyProcess = (transferNo: string) =>
    requestConfirm({
      title: t("confirm.markCaseClosedTitle"),
      description: t("confirm.markCaseClosedDescription", { transferNo }),
      onConfirm: () =>
        doAction(transferNo, () => manuallyProcessTransferOrder(transferNo), t("markCaseClosedSuccess")),
    });

  const handleCompleteCredit = (transferNo: string) =>
    requestConfirm({
      title: t("confirm.completeCreditTitle"),
      description: t("confirm.completeCreditDescription", { transferNo }),
      onConfirm: () =>
        doAction(transferNo, () => completeTransferInCredit(transferNo), t("completeCreditSuccess")),
    });

  const load = useCallback(async () => {
    setLoading(true);
    setErr(null);
    try {
      const player_id =
        applied.playerId.trim() === "" ? undefined : Number(applied.playerId);
      const d = await getAdminTransferOrders({
        page,
        per_page: perPage,
        abnormal: applied.abnormalOnly || undefined,
        player_id:
          player_id !== undefined && !Number.isNaN(player_id) && player_id > 0
            ? player_id
            : undefined,
        player_account: applied.playerAccount.trim() || undefined,
        transfer_no: applied.transferNo.trim() || undefined,
        external_ref_no: applied.externalRefNo.trim() || undefined,
        created_from: applied.createdFrom.trim() || undefined,
        created_to: applied.createdTo.trim() || undefined,
        status: applied.statusCsv.trim() || undefined,
      });
      setData(d);
    } catch (e) {
      setErr(e instanceof LotteryApiBizError ? e.message : tRef.current("loadFailed"));
      setData(null);
    } finally {
      setLoading(false);
    }
  }, [page, perPage, applied, tRef]);

  useAsyncEffect(() => {
    void load();
  }, [page, perPage, applied, tRef]);

  const runSearch = () => {
    setApplied({ ...draft });
    setPage(1);
  };

  const resetFilters = () => {
    setDraft(emptyTransferFilters);
    setApplied(emptyTransferFilters);
    setPage(1);
  };

  return (
    <>
    <Card>
      <CardHeader>
        <CardTitle>{t("transferOrders")}</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
          <div className="grid gap-1.5">
            <Label htmlFor="to-transfer-no">{t("localTransferNo")}</Label>
            <Input
              id="to-transfer-no"
              placeholder={t("search")}
              value={draft.transferNo}
              onChange={(e) => setDraft((d) => ({ ...d, transferNo: e.target.value }))}
            />
          </div>
          <div className="grid gap-1.5">
            <Label htmlFor="to-ext">{t("externalRefNo")}</Label>
            <Input
              id="to-ext"
              placeholder={t("search")}
              value={draft.externalRefNo}
              onChange={(e) => setDraft((d) => ({ ...d, externalRefNo: e.target.value }))}
            />
          </div>
          <div className="grid gap-1.5">
            <Label htmlFor="to-account">{t("playerAccount")}</Label>
            <Input
              id="to-account"
              placeholder={t("playerAccountPlaceholder")}
              value={draft.playerAccount}
              onChange={(e) => setDraft((d) => ({ ...d, playerAccount: e.target.value }))}
            />
          </div>
          <div className="grid gap-1.5">
            <Label htmlFor="to-player">{t("playerId")}</Label>
            <Input
              id="to-player"
              inputMode="numeric"
              placeholder={t("playerIdOptional")}
              value={draft.playerId}
              onChange={(e) => setDraft((d) => ({ ...d, playerId: e.target.value }))}
            />
          </div>
          <div className="sm:col-span-2 lg:col-span-2 xl:col-span-2">
            <AdminDateRangeField
              id="to-created-range"
              label={t("requestDateRange")}
              from={draft.createdFrom}
              to={draft.createdTo}
              onRangeChange={(r) =>
                setDraft((d) => ({ ...d, createdFrom: r.from, createdTo: r.to }))
              }
            />
          </div>
          <div className="grid gap-1.5">
            <Label htmlFor="to-status">{t("status")}</Label>
            <Select
              modal={false}
              value={
                draft.statusCsv === "" || !TRANSFER_ORDER_STATUS_OPTIONS.some((o) => o.value === draft.statusCsv)
                  ? WALLET_FILTER_ALL
                  : draft.statusCsv
              }
              onValueChange={(v) =>
                setDraft((d) => ({
                  ...d,
                  statusCsv: v == null || v === WALLET_FILTER_ALL ? "" : String(v),
                }))
              }
            >
              <SelectTrigger id="to-status" className="h-8 w-full">
                <SelectValue>
                  {(v) => walletAdminSelectDisplayedLabel(v, TRANSFER_ORDER_STATUS_OPTIONS, t)}
                </SelectValue>
              </SelectTrigger>
              <SelectContent align="start" sideOffset={6}>
                <SelectItem value={WALLET_FILTER_ALL}>{t("filterAll")}</SelectItem>
                {TRANSFER_ORDER_STATUS_OPTIONS.map((o) => (
                  <SelectItem key={o.value} value={o.value}>
                    {t(o.label)}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          <div className="flex flex-col justify-end gap-2 sm:col-span-2 lg:col-span-1">
            <span className="text-sm font-medium leading-none">{t("options")}</span>
            <label className="flex min-h-9 cursor-pointer items-center gap-2 text-sm">
              <Checkbox
                checked={draft.abnormalOnly}
                onCheckedChange={(v) =>
                  setDraft((d) => ({ ...d, abnormalOnly: v === true }))
                }
              />
              {t("abnormalOnly")}
            </label>
          </div>
        </div>
        <div className="flex flex-wrap gap-2">
          <AdminTableExportButton
            tableId="wallet-transfer-orders-table"
            filename={exportLabels.filename}
            sheetName={exportLabels.sheetName}
          />
          <Button type="button" size="sm" onClick={() => runSearch()}>
            {t("search")}
          </Button>
          <Button type="button" size="sm" variant="outline" onClick={() => resetFilters()}>
            {t("resetFilters")}
          </Button>
          <Button type="button" size="sm" variant="secondary" onClick={() => void load()}>
            {t("refreshCurrentPage")}
          </Button>
        </div>

        {err ? <p className="text-sm text-red-600 dark:text-red-400">{err}</p> : null}
        {(loading && !data) || data ? (
          <>
            <div className="admin-table-shell overflow-x-auto rounded-md border">
              <Table id="wallet-transfer-orders-table" className="min-w-[1180px]">
                <TableHeader>
                  <TableRow>
                    <TableHead className="min-w-0 max-w-[14rem]">{t("localTransferNo")}</TableHead>
                    <TableHead className="min-w-0 max-w-[12rem]">{t("externalRefNo")}</TableHead>
                    <AdminAgentIdentityHeads />
                    <AdminPlayerIdentityHeads />
                    <TableHead className="w-14">{t("direction")}</TableHead>
                    <TableHead className="whitespace-nowrap">{t("amount")}</TableHead>
                    <TableHead className="whitespace-nowrap">{t("status")}</TableHead>
                    <TableHead className="min-w-0 max-w-[14rem]">{t("failReason")}</TableHead>
                    <TableHead className="min-w-0 whitespace-normal leading-tight">{t("requestTime")}</TableHead>
                    <TableHead className="min-w-0 whitespace-normal leading-tight">{t("finishedTime")}</TableHead>
                    <TableHead className="sticky right-0 z-20 bg-muted w-12 text-center shadow-[-1px_0_0_rgba(203,213,225,0.7)]">{t("table.actions", { ns: "common" })}</TableHead>
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {loading && !data ? (
                    <AdminTableLoadingRow colSpan={13} />
                  ) : !data || data.items.length === 0 ? (
                    <AdminTableNoResourceRow colSpan={13} />
                  ) : (
                    data.items.map((row) => (
                      <TableRow key={row.id}>
                        <TableCell className="min-w-0 max-w-[14rem] align-top whitespace-normal">
                          <CellMonoId value={row.transfer_no} copyHint={t("copyTransferNo")} />
                        </TableCell>
                        <TableCell className="min-w-0 max-w-[12rem] align-top whitespace-normal">
                          <CellMonoId value={row.external_ref_no} copyHint={t("copyExternalRefNo")} />
                        </TableCell>
                        <AdminAgentIdentityCells row={row} />
                        <AdminPlayerIdentityCells row={row} />
                        <TableCell>{row.direction}</TableCell>
                        <TableCell className="tabular-nums">
                          {formatAdminMinorUnits(row.amount, row.currency_code)}
                        </TableCell>
                        <TableCell>
                          <AdminStatusBadge status={row.status}>{statusLabelT(row.status, t)}</AdminStatusBadge>
                        </TableCell>
                        <TableCell className="max-w-[14rem] whitespace-normal break-words text-xs text-muted-foreground">
                          {row.fail_reason?.trim() ? row.fail_reason : "—"}
                        </TableCell>
                        <TableCell className="min-w-0 whitespace-normal font-mono text-[11px] leading-snug text-muted-foreground">
                          {formatTs(row.created_at)}
                        </TableCell>
                        <TableCell className="min-w-0 whitespace-normal font-mono text-[11px] leading-snug text-muted-foreground">
                          {formatTs(row.finished_at)}
                        </TableCell>
                        <TableCell className="sticky right-0 z-10 bg-card text-center align-middle shadow-[-1px_0_0_rgba(203,213,225,0.7)]">
                          <div className="flex justify-center">
                            <TransferOrderRowActions
                              row={row}
                              canWriteWallet={canWriteWallet}
                              busy={actionLoading.has(row.transfer_no)}
                              onCompleteCredit={handleCompleteCredit}
                              onReverse={handleReverse}
                              onManualProcess={handleManuallyProcess}
                              t={t}
                            />
                          </div>
                        </TableCell>
                      </TableRow>
                    ))
                  )}
                </TableBody>
              </Table>
            </div>
            {data ? (
              <AdminListPaginationFooter
                selectId="wallet-transfer-orders-per-page"
                total={data.total}
                page={page}
                lastPage={Math.max(1, Math.ceil(data.total / Math.max(1, data.per_page)))}
                perPage={perPage}
                loading={loading}
                onPerPageChange={(next) => {
                  setPerPage(next);
                  setPage(1);
                }}
                onPageChange={setPage}
              />
            ) : null}
          </>
        ) : null}
      </CardContent>
    </Card>
    <ConfirmDialog />
    </>
  );
}

export function WalletTxnsPanel(): React.ReactElement {
  const { t } = useTranslation(["wallet", "common"]);
  const { t: tSettlement } = useTranslation("settlementCenter");
  const tRef = useTranslationRef(["wallet", "common"]);
  const exportLabels = useExportLabels("walletTransactions");
  const formatTs = useAdminDateTimeFormatter();
  const [data, setData] = useState<AdminWalletTxnListData | null>(null);
  const [loading, setLoading] = useState(true);
  const [err, setErr] = useState<string | null>(null);
  const [page, setPage] = useState(1);
  const [perPage, setPerPage] = useState(10);
  const searchParams = useSearchParams();
  const playerIdFromUrl = (searchParams.get("player_id") ?? "").trim();
  const txnNoFromUrl = (searchParams.get("txn_no") ?? "").trim();
  const externalRefNoFromUrl = (searchParams.get("external_ref_no") ?? "").trim();
  const initialTxnFilters: TxnFilters = {
    ...emptyTxnFilters,
    playerId: playerIdFromUrl,
    txnNo: txnNoFromUrl,
    externalRefNo: externalRefNoFromUrl,
  };
  const [draft, setDraft] = useState<TxnFilters>(initialTxnFilters);
  const [applied, setApplied] = useState<TxnFilters>(initialTxnFilters);

  const load = useCallback(async () => {
    setLoading(true);
    setErr(null);
    try {
      const player_id =
        applied.playerId.trim() === "" ? undefined : Number(applied.playerId);
      const d = await getAdminWalletTransactions({
        page,
        per_page: perPage,
        abnormal: applied.abnormalOnly || undefined,
        player_id:
          player_id !== undefined && !Number.isNaN(player_id) && player_id > 0
            ? player_id
            : undefined,
        player_account: applied.playerAccount.trim() || undefined,
        txn_no: applied.txnNo.trim() || undefined,
        external_ref_no: applied.externalRefNo.trim() || undefined,
        created_from: applied.createdFrom.trim() || undefined,
        created_to: applied.createdTo.trim() || undefined,
        biz_type: applied.bizType.trim() || undefined,
        status: applied.statusCsv.trim() || undefined,
      });
      setData(d);
    } catch (e) {
      setErr(e instanceof LotteryApiBizError ? e.message : tRef.current("loadFailed"));
      setData(null);
    } finally {
      setLoading(false);
    }
  }, [page, perPage, applied, tRef]);

  useAsyncEffect(() => {
    void load();
  }, [page, perPage, applied]);

  const runSearch = () => {
    setApplied({ ...draft });
    setPage(1);
  };

  const resetFilters = () => {
    setDraft(emptyTxnFilters);
    setApplied(emptyTxnFilters);
    setPage(1);
  };

  const showLedgerColumn =
    data?.items.some((row) => row.ledger_source === "credit_ledger") ?? false;
  const txnTableColSpan = showLedgerColumn ? 12 : 11;

  return (
    <Card>
      <CardHeader>
        <CardTitle>{t("walletTransactions")}</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
          <div className="grid gap-1.5">
            <Label htmlFor="tx-no">{t("txnNo")}</Label>
            <Input
              id="tx-no"
              placeholder={t("search")}
              value={draft.txnNo}
              onChange={(e) => setDraft((d) => ({ ...d, txnNo: e.target.value }))}
            />
          </div>
          <div className="grid gap-1.5">
            <Label htmlFor="tx-ext">{t("externalRefNo")}</Label>
            <Input
              id="tx-ext"
              placeholder={t("search")}
              value={draft.externalRefNo}
              onChange={(e) => setDraft((d) => ({ ...d, externalRefNo: e.target.value }))}
            />
          </div>
          <div className="grid gap-1.5">
            <Label htmlFor="tx-account">{t("playerAccount")}</Label>
            <Input
              id="tx-account"
              placeholder={t("playerAccountPlaceholder")}
              value={draft.playerAccount}
              onChange={(e) => setDraft((d) => ({ ...d, playerAccount: e.target.value }))}
            />
          </div>
          <div className="grid gap-1.5">
            <Label htmlFor="tx-player">{t("playerId")}</Label>
            <Input
              id="tx-player"
              inputMode="numeric"
              placeholder={t("playerIdOptional")}
              value={draft.playerId}
              onChange={(e) => setDraft((d) => ({ ...d, playerId: e.target.value }))}
            />
          </div>
          <div className="grid gap-1.5">
            <Label htmlFor="tx-biz">{t("bizType")}</Label>
            <Select
              modal={false}
              value={
                draft.bizType === "" || !WALLET_TXN_BIZ_OPTIONS.some((o) => o.value === draft.bizType)
                  ? WALLET_FILTER_ALL
                  : draft.bizType
              }
              onValueChange={(v) =>
                setDraft((d) => ({
                  ...d,
                  bizType: v == null || v === WALLET_FILTER_ALL ? "" : String(v),
                }))
              }
            >
              <SelectTrigger id="tx-biz" className="h-8 w-full">
                <SelectValue>
                  {(v) => walletAdminSelectDisplayedLabel(v, WALLET_TXN_BIZ_OPTIONS, t)}
                </SelectValue>
              </SelectTrigger>
              <SelectContent align="start" sideOffset={6}>
                <SelectItem value={WALLET_FILTER_ALL}>{t("filterAll")}</SelectItem>
                {WALLET_TXN_BIZ_OPTIONS.map((o) => (
                  <SelectItem key={o.value} value={o.value}>
                    {t(o.label)}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          <div className="grid gap-1.5">
            <Label htmlFor="tx-status">{t("status")}</Label>
            <Select
              modal={false}
              value={
                draft.statusCsv === "" || !WALLET_TXN_STATUS_OPTIONS.some((o) => o.value === draft.statusCsv)
                  ? WALLET_FILTER_ALL
                  : draft.statusCsv
              }
              onValueChange={(v) =>
                setDraft((d) => ({
                  ...d,
                  statusCsv: v == null || v === WALLET_FILTER_ALL ? "" : String(v),
                }))
              }
            >
              <SelectTrigger id="tx-status" className="h-8 w-full">
                <SelectValue>
                  {(v) => walletAdminSelectDisplayedLabel(v, WALLET_TXN_STATUS_OPTIONS, t)}
                </SelectValue>
              </SelectTrigger>
              <SelectContent align="start" sideOffset={6}>
                <SelectItem value={WALLET_FILTER_ALL}>{t("filterAll")}</SelectItem>
                {WALLET_TXN_STATUS_OPTIONS.map((o) => (
                  <SelectItem key={o.value} value={o.value}>
                    {t(o.label)}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          <div className="sm:col-span-2 lg:col-span-2 xl:col-span-2">
            <AdminDateRangeField
              id="tx-created-range"
              label={t("requestDateRange")}
              from={draft.createdFrom}
              to={draft.createdTo}
              onRangeChange={(r) =>
                setDraft((d) => ({ ...d, createdFrom: r.from, createdTo: r.to }))
              }
            />
          </div>
          <div className="flex flex-col justify-end gap-2 sm:col-span-2 lg:col-span-1">
            <span className="text-sm font-medium leading-none">{t("options")}</span>
            <label className="flex min-h-9 cursor-pointer items-center gap-2 text-sm">
              <Checkbox
                checked={draft.abnormalOnly}
                onCheckedChange={(v) =>
                  setDraft((d) => ({ ...d, abnormalOnly: v === true }))
                }
              />
              {t("abnormalOnlyPending")}
            </label>
          </div>
        </div>
        <div className="flex flex-wrap gap-2">
          <AdminTableExportButton
            tableId="wallet-transactions-table"
            filename={exportLabels.filename}
            sheetName={exportLabels.sheetName}
          />
          <Button type="button" size="sm" onClick={() => runSearch()}>
            {t("search")}
          </Button>
          <Button type="button" size="sm" variant="outline" onClick={() => resetFilters()}>
            {t("resetFilters")}
          </Button>
          <Button type="button" size="sm" variant="secondary" onClick={() => void load()}>
            {t("refreshCurrentPage")}
          </Button>
        </div>

        {err ? <p className="text-sm text-red-600 dark:text-red-400">{err}</p> : null}
        {(loading && !data) || data ? (
          <>
            <div className="admin-table-shell overflow-x-auto rounded-md border">
              <Table id="wallet-transactions-table" className="min-w-[1180px]">
                <TableHeader>
                  <TableRow>
                    <TableHead className="min-w-[10rem] whitespace-nowrap">{t("txnNo")}</TableHead>
                    <TableHead className="min-w-[8rem] whitespace-nowrap">{t("externalRefNo")}</TableHead>
                    <AdminAgentIdentityHeads />
                    <AdminPlayerIdentityHeads />
                    {showLedgerColumn ? (
                      <TableHead className="whitespace-nowrap">{t("ledgerChannel", { defaultValue: "账本" })}</TableHead>
                    ) : null}
                    <TableHead className="min-w-[6.5rem] whitespace-nowrap">{t("type")}</TableHead>
                    <TableHead className="min-w-[6.5rem] whitespace-nowrap">{t("amount")}</TableHead>
                    <TableHead className="whitespace-nowrap">{t("status")}</TableHead>
                    <TableHead className="min-w-[8.5rem] whitespace-nowrap">{t("requestTime")}</TableHead>
                    <TableHead className="min-w-[8.5rem] whitespace-nowrap">{t("finishedTime")}</TableHead>
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {loading && !data ? (
                    <AdminTableLoadingRow colSpan={txnTableColSpan} />
                  ) : !data || data.items.length === 0 ? (
                    <AdminTableNoResourceRow colSpan={txnTableColSpan} />
                  ) : (
                    data.items.map((row) => (
                      <TableRow key={row.id}>
                        <TableCell className="min-w-[10rem] max-w-[12rem] align-top">
                          <CellMonoId value={row.txn_no} copyHint={t("copyTxnNo")} />
                        </TableCell>
                        <TableCell className="min-w-[8rem] max-w-[10rem] align-top">
                          <CellMonoId value={row.external_ref_no} copyHint={t("copyExternalTxnRefNo")} />
                        </TableCell>
                        <AdminAgentIdentityCells row={row} />
                        <AdminPlayerIdentityCells row={row} />
                        {showLedgerColumn ? (
                          <TableCell className="align-top whitespace-nowrap">
                            <PlayerLedgerSourceBadge ledgerSource={row.ledger_source} />
                          </TableCell>
                        ) : null}
                        <TableCell className="min-w-[6.5rem] max-w-[9rem] align-top text-xs">
                          <span
                            className="block truncate"
                            title={walletTxnBizTypeLabel(row.biz_type, row.ledger_source, t, tSettlement)}
                          >
                            {walletTxnBizTypeLabel(row.biz_type, row.ledger_source, t, tSettlement)}
                          </span>
                        </TableCell>
                        <TableCell className="min-w-[6.5rem] align-top whitespace-nowrap tabular-nums text-xs">
                          {row.amount_formatted ?? formatAdminMinorUnits(row.amount)}
                          <span className="ml-1 text-muted-foreground">
                            ({row.direction === 1 ? t("in") : t("out")})
                          </span>
                        </TableCell>
                        <TableCell className="align-top whitespace-nowrap">
                          <AdminStatusBadge status={row.status}>{statusLabelT(row.status, t)}</AdminStatusBadge>
                        </TableCell>
                        <TableCell className="min-w-[8.5rem] align-top whitespace-nowrap font-mono text-[11px] leading-snug text-muted-foreground">
                          {formatTs(row.created_at)}
                        </TableCell>
                        <TableCell className="min-w-[8.5rem] align-top whitespace-nowrap font-mono text-[11px] leading-snug text-muted-foreground">
                          {formatTs(row.updated_at)}
                        </TableCell>
                      </TableRow>
                    ))
                  )}
                </TableBody>
              </Table>
            </div>
            {data ? (
              <AdminListPaginationFooter
                selectId="wallet-transactions-per-page"
                total={data.total}
                page={page}
                lastPage={Math.max(1, Math.ceil(data.total / Math.max(1, data.per_page)))}
                perPage={perPage}
                loading={loading}
                onPerPageChange={(next) => {
                  setPerPage(next);
                  setPage(1);
                }}
                onPageChange={setPage}
              />
            ) : null}
          </>
        ) : null}
      </CardContent>
    </Card>
  );
}

export function PlayerWalletPanel(): React.ReactElement {
  const { t } = useTranslation(["wallet", "common"]);
  const tRef = useTranslationRef(["wallet", "common"]);
  const exportLabels = useExportLabels("playerWallets");
  useAdminCurrencyCatalog();
  const [playerId, setPlayerId] = useState("");
  const [result, setResult] = useState<AdminPlayerWalletsData | null>(null);
  const [err, setErr] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  const query = useCallback(async () => {
    const id = Number(playerId.trim());
    if (Number.isNaN(id) || id < 1) {
      setErr(tRef.current("invalidPlayerId"));
      setResult(null);
      return;
    }
    setLoading(true);
    setErr(null);
    try {
      const d = await getAdminPlayerWallets(id);
      setResult(d);
    } catch (e) {
      setErr(e instanceof LotteryApiBizError ? e.message : tRef.current("queryFailed"));
      setResult(null);
    } finally {
      setLoading(false);
    }
  }, [playerId, tRef]);

  return (
    <Card>
      <CardHeader>
        <CardTitle>{t("playerWalletQuery")}</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        <div className="flex flex-wrap items-end gap-2">
          <div className="grid gap-1.5">
            <Label htmlFor="pw-id">{t("playerId")}</Label>
            <Input
              id="pw-id"
              inputMode="numeric"
              placeholder="1"
              value={playerId}
              onChange={(e) => setPlayerId(e.target.value)}
              className="w-40"
            />
          </div>
          <AdminTableExportButton
            tableId="player-wallet-table"
            filename={exportLabels.filename}
            sheetName={exportLabels.sheetName}
          />
          <Button type="button" onClick={() => void query()} disabled={loading}>
            {loading ? t("querying") : t("query")}
          </Button>
        </div>
        {err ? <p className="text-sm text-red-600 dark:text-red-400">{err}</p> : null}
        {result ? (
          <div className="space-y-3 rounded-lg border p-4 text-sm">
            <p>
              <span className="text-muted-foreground">{t("sitePlayer")}</span>{" "}
              {result.player.site_code}:{result.player.site_player_id}
            </p>
            <Table id="player-wallet-table">
              <TableHeader>
                <TableRow>
                  <TableHead>{t("walletType")}</TableHead>
                  <TableHead>{t("currency")}</TableHead>
                  <TableHead>{t("balanceMinor")}</TableHead>
                  <TableHead>{t("availableBalance")}</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {result.wallets.length === 0 ? (
                  <AdminTableNoResourceRow colSpan={4} />
                ) : (
                  result.wallets.map((w) => (
                    <TableRow key={w.id}>
                      <TableCell>{w.wallet_type}</TableCell>
                      <TableCell>{w.currency_code}</TableCell>
                      <TableCell className="font-mono tabular-nums">{w.balance}</TableCell>
                      <TableCell className="tabular-nums">
                        {formatAdminMinorUnits(w.available_balance, w.currency_code)}
                      </TableCell>
                    </TableRow>
                  ))
                )}
              </TableBody>
            </Table>
          </div>
        ) : null}
      </CardContent>
    </Card>
  );
}
