"use client";

import { useState, useEffect, useRef, Fragment, Suspense, useMemo } from "react";
import { useSearchParams } from "next/navigation";
import { useQuery, useMutation } from "@tanstack/react-query";
import { Alert, AlertDialog, Button, Modal, Spinner } from "@heroui/react";
import { ThunderboltFill, CreditCard, Check, QrCode, Xmark } from "@gravity-ui/icons";
import jsQR from "jsqr";
import { api } from "@/lib/api";
import { useSession } from "@/lib/auth-client";
import dayjs from "@/lib/dayjs";
import { BanknoteArrowUp, EvCharger, Plug } from "lucide-react";
import Link from "next/link";
import { IdTagCard } from "@/components/id-tag-card";

// ── Status maps (same as charge-points page) ────────────────────────────────

const statusLabelMap: Record<string, string> = {
  Available: "空闲中",
  Charging: "充电中",
  Preparing: "准备中",
  Finishing: "结束中",
  SuspendedEV: "EV 暂停",
  SuspendedEVSE: "EVSE 暂停",
  Reserved: "已预约",
  Faulted: "故障",
  Unavailable: "不可用",
  Occupied: "占用",
};

// ── Step indicator ───────────────────────────────────────────────────────────

function StepBar({ step, onGoBack }: { step: number; onGoBack: (target: number) => void }) {
  const labels = ["选择充电桩", "选择充电口", "选择储值卡"];
  return (
    <div className="flex w-full items-center">
      {labels.map((label, i) => {
        const idx = i + 1;
        const isActive = step === idx;
        const isDone = idx < step;
        const isLast = i === labels.length - 1;
        return (
          <Fragment key={i}>
            <button
              type="button"
              onClick={() => isDone && onGoBack(idx)}
              disabled={!isDone}
              className={[
                "flex shrink-0 flex-col items-center gap-1.5 py-1 min-w-16",
                isDone ? "cursor-pointer active:opacity-70" : "cursor-default",
              ].join(" ")}
            >
              <span
                className={[
                  "flex size-8 items-center justify-center rounded-full text-sm font-bold ring-2 ring-offset-2 ring-offset-background transition-all",
                  isActive
                    ? "bg-accent text-accent-foreground ring-accent shadow-md shadow-accent/30"
                    : isDone
                      ? "bg-success text-white ring-success"
                      : "bg-surface-tertiary text-muted ring-transparent",
                ].join(" ")}
              >
                {isDone ? <Check className="size-4" /> : idx}
              </span>
              <span
                className={[
                  "text-[11px] font-semibold leading-none whitespace-nowrap tracking-tight mt-1",
                  isActive ? "text-accent" : isDone ? "text-success" : "text-muted",
                ].join(" ")}
              >
                {label}
              </span>
            </button>
            {!isLast && (
              <div className="flex-1 mb-3.5">
                <span
                  className={[
                    "block h-0.5 w-full rounded-full transition-colors duration-300",
                    isDone ? "bg-success" : "bg-border",
                  ].join(" ")}
                />
              </div>
            )}
          </Fragment>
        );
      })}
    </div>
  );
}

// ── QR Scanner ───────────────────────────────────────────────────────────────

type ScannerProps = {
  onResult: (raw: string) => void;
  onClose: () => void;
};

function QrScanner({ onResult, onClose }: ScannerProps) {
  const videoRef = useRef<HTMLVideoElement>(null);
  const streamRef = useRef<MediaStream | null>(null);
  const scanningRef = useRef(true);
  const mountedRef = useRef(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    mountedRef.current = true;
    scanningRef.current = true;
    let detector: any = null;

    async function start() {
      let stream: MediaStream;
      try {
        stream = await navigator.mediaDevices.getUserMedia({
          video: { facingMode: "environment" },
        });
      } catch (err: any) {
        if (mountedRef.current) setError("无法访问摄像头：" + (err?.message ?? "未知错误"));
        return;
      }

      if (!mountedRef.current) {
        stream.getTracks().forEach((t) => t.stop());
        return;
      }

      streamRef.current = stream;

      if (videoRef.current) {
        videoRef.current.srcObject = stream;
        try {
          await videoRef.current.play();
        } catch (err: any) {
          // AbortError fires when the element is removed mid-play (e.g. Modal animation).
          // It is not a real error — just bail out silently.
          if (err?.name === "AbortError") return;
          if (mountedRef.current) setError("无法播放摄像头画面：" + (err?.message ?? "未知错误"));
          return;
        }
      }

      if (!mountedRef.current) return;

      const useNative = "BarcodeDetector" in window;
      if (useNative) {
        detector = new (window as any).BarcodeDetector({ formats: ["qr_code"] });
      }

      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");

      const scan = async () => {
        if (!scanningRef.current || !videoRef.current) return;
        try {
          if (useNative) {
            const codes: Array<{ rawValue: string }> = await detector.detect(videoRef.current);
            if (codes.length > 0) {
              onResult(codes[0].rawValue);
              return;
            }
          } else if (ctx) {
            const video = videoRef.current;
            canvas.width = video.videoWidth;
            canvas.height = video.videoHeight;
            ctx.drawImage(video, 0, 0);
            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            const code = jsQR(imageData.data, imageData.width, imageData.height);
            if (code) {
              onResult(code.data);
              return;
            }
          }
        } catch {}
        requestAnimationFrame(scan);
      };
      requestAnimationFrame(scan);
    }

    start();

    return () => {
      mountedRef.current = false;
      scanningRef.current = false;
      streamRef.current?.getTracks().forEach((t) => t.stop());
      streamRef.current = null;
    };
  }, [onResult]);

  return (
    <div className="relative h-full w-full overflow-hidden bg-black">
      {error ? (
        <div className="flex h-full flex-col items-center justify-center gap-3 p-6 text-center">
          <p className="text-sm text-danger">{error}</p>
        </div>
      ) : (
        <>
          {/* eslint-disable-next-line jsx-a11y/media-has-caption */}
          <video ref={videoRef} className="h-full w-full object-cover" playsInline muted />
          {/* Aim overlay */}
          <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
            <div className="size-56 rounded-2xl border-2 border-white/80 shadow-[0_0_0_9999px_rgba(0,0,0,0.45)]" />
          </div>
          <p className="absolute bottom-8 left-0 right-0 text-center text-sm text-white/80">
            将二维码对准框内
          </p>
        </>
      )}
      {/* Close button — overlaid top-right */}
      <button
        type="button"
        onClick={onClose}
        className="absolute right-4 top-4 flex size-9 items-center justify-center rounded-full bg-black/50 text-white backdrop-blur-sm hover:bg-black/70"
        aria-label="关闭扫描"
      >
        <Xmark className="size-5" />
      </button>
    </div>
  );
}

// ── Main Page ────────────────────────────────────────────────────────────────

function ChargePageContent() {
  const searchParams = useSearchParams();
  const { data: sessionData } = useSession();

  const [step, setStep] = useState(1);
  const [selectedCpId, setSelectedCpId] = useState<string | null>(null);
  const [selectedConnectorId, setSelectedConnectorId] = useState<number | null>(null);
  const [selectedIdTag, setSelectedIdTag] = useState<string | null>(null);
  const [showScanner, setShowScanner] = useState(false);
  const [scanError, setScanError] = useState<string | null>(null);
  const [startResult, setStartResult] = useState<"success" | "error" | null>(null);
  const [startError, setStartError] = useState<string | null>(null);
  const [startSnapshot, setStartSnapshot] = useState<{
    cpId: string;
    chargePointIdentifier: string;
    connectorId: number;
    idTag: string;
  } | null>(null);

  // Detect mobile
  const [isMobile, setIsMobile] = useState(false);
  useEffect(() => {
    setIsMobile(/Mobi|Android|iPhone|iPad/i.test(navigator.userAgent));
  }, []);

  // Pre-fill from URL params (QR code redirect)
  useEffect(() => {
    const cpId = searchParams.get("cpId");
    const connector = searchParams.get("connector");
    if (cpId) {
      setSelectedCpId(cpId);
      if (connector && !Number.isNaN(Number(connector))) {
        setSelectedConnectorId(Number(connector));
        setStep(3);
      } else {
        setStep(2);
      }
    }
  }, [searchParams]);

  const { data: chargePoints = [], isLoading: cpLoading } = useQuery({
    queryKey: ["chargePoints"],
    queryFn: () => api.chargePoints.list().catch(() => []),
    refetchInterval: 5_000,
  });

  const { data: idTags = [], isLoading: tagsLoading } = useQuery({
    queryKey: ["idTags", "list"],
    queryFn: () => api.idTags.list().catch(() => []),
  });

  const { data: activeTransactions = [] } = useQuery({
    queryKey: ["transactions", "active", "idTagLock"],
    queryFn: async () => {
      const res = await api.transactions.list({ page: 1, limit: 200, status: "active" });
      return res.data;
    },
    refetchInterval: 3_000,
  });

  const selectedCp = chargePoints.find((cp) => cp.id === selectedCpId) ?? null;
  const myTags = idTags?.filter((t) => t.status === "Accepted") ?? [];
  const activeTransactionsForCurrentUser = useMemo(() => {
    const currentUserId = sessionData?.user?.id;
    if (!currentUserId) return activeTransactions;
    return activeTransactions.filter((tx) => tx.idTagUserId === currentUserId);
  }, [activeTransactions, sessionData?.user?.id]);

  const activeCount = activeTransactionsForCurrentUser.length;
  const activeDetailHref =
    activeCount === 1
      ? `/dashboard/transactions/${activeTransactionsForCurrentUser[0].id}`
      : "/dashboard/transactions?status=active";
  const activeIdTagSet = useMemo(
    () => new Set(activeTransactionsForCurrentUser.map((tx) => tx.idTag)),
    [activeTransactionsForCurrentUser],
  );

  useEffect(() => {
    if (startResult !== "success" && selectedIdTag && activeIdTagSet.has(selectedIdTag)) {
      setSelectedIdTag(null);
    }
  }, [selectedIdTag, activeIdTagSet, startResult]);

  const { data: startedTransactionId } = useQuery({
    queryKey: [
      "latestStartedTx",
      startSnapshot?.cpId,
      startSnapshot?.connectorId,
      startSnapshot?.idTag,
      startResult,
    ],
    enabled: startResult === "success" && !!startSnapshot?.cpId && !!startSnapshot?.idTag,
    queryFn: async () => {
      if (!startSnapshot?.cpId || !startSnapshot?.idTag) return null;
      const res = await api.transactions.list({
        page: 1,
        limit: 30,
        status: "active",
        chargePointId: startSnapshot.cpId,
      });

      const matched = res.data.find(
        (tx) =>
          tx.idTag === startSnapshot.idTag && tx.connectorNumber === startSnapshot.connectorId,
      );

      return matched?.id ?? null;
    },
    refetchInterval: (query) => (query.state.data ? false : 1_500),
    retry: false,
  });

  const startMutation = useMutation({
    mutationFn: async () => {
      if (!selectedCp || selectedConnectorId === null || !selectedIdTag) {
        throw new Error("请先完成所有选择");
      }
      return api.transactions.remoteStart({
        chargePointIdentifier: selectedCp.chargePointIdentifier,
        connectorId: selectedConnectorId,
        idTag: selectedIdTag,
      });
    },
    onMutate: () => {
      if (!selectedCp || selectedConnectorId === null || !selectedIdTag) return;
      setStartSnapshot({
        cpId: selectedCp.id,
        chargePointIdentifier: selectedCp.chargePointIdentifier,
        connectorId: selectedConnectorId,
        idTag: selectedIdTag,
      });
    },
    onSuccess: () => {
      setStartResult("success");
    },
    onError: (err: Error) => {
      setStartResult("error");
      const msg = err.message ?? "";
      const lowerMsg = msg.toLowerCase();

      if (lowerMsg.includes("offline")) setStartError("充电桩当前不在线，请稍后再试");
      else if (
        lowerMsg.includes("chargepoint is not accepted") ||
        lowerMsg.includes("not accepted")
      ) {
        setStartError("充电桩未启用，请联系管理员");
      } else if (msg.includes("ConcurrentTx") || lowerMsg.includes("concurrent")) {
        setStartError("该储值卡已有进行中的充电订单，请先结束后再发起");
      } else if (lowerMsg.includes("rejected: blocked")) {
        setStartError("储值卡不可用或余额不足，请更换储值卡或先充值");
      } else if (lowerMsg.includes("rejected: expired")) {
        setStartError("储值卡已过期，请联系管理员处理");
      } else if (lowerMsg.includes("rejected: invalid")) {
        setStartError("储值卡无效，请确认卡号后重试");
      } else if (lowerMsg.includes("not found or not authorized")) {
        setStartError("该储值卡无权使用或不存在");
      } else if (msg.includes("idTag") || lowerMsg.includes("idtag")) {
        setStartError("储值卡状态不可用，无法启动充电");
      } else setStartError("启动失败：" + msg);
    },
  });

  function handleScanResult(raw: string) {
    setShowScanner(false);
    setScanError(null);
    try {
      // Support both helios:// scheme and https:// web URLs
      const url = new URL(raw);
      const cpId = url.searchParams.get("cpId");
      const connector = url.searchParams.get("connector");
      if (!cpId) {
        setScanError("二维码内容无效，缺少充电桩信息");
        return;
      }
      setSelectedCpId(cpId);
      if (connector && !Number.isNaN(Number(connector))) {
        setSelectedConnectorId(Number(connector));
        setStep(3);
      } else {
        setSelectedConnectorId(null);
        setStep(2);
      }
    } catch {
      setScanError("二维码内容格式错误");
    }
  }

  // ── Success screen ─────────────────────────────────────────────────────────
  if (startResult === "success") {
    return (
      <div className="flex flex-col items-center justify-center gap-8 py-16 text-center">
        <div className="relative">
          <div className="flex size-24 items-center justify-center rounded-full bg-success-soft ring-8 ring-success/10">
            <Check className="size-12 text-success" />
          </div>
        </div>
        <div className="space-y-2">
          <h2 className="text-2xl font-bold text-foreground">已发起充电</h2>
          <p className="text-sm text-muted leading-relaxed">充电桩正在响应，稍候将自动开始</p>
        </div>
        <div className="w-full max-w-xs rounded-2xl border border-border bg-surface p-4 text-left space-y-2">
          <div className="flex justify-between text-sm">
            <span className="text-muted">充电桩</span>
            <span className="font-medium text-foreground">
              {startSnapshot?.chargePointIdentifier ?? selectedCp?.chargePointIdentifier}
            </span>
          </div>
          <div className="flex justify-between text-sm">
            <span className="text-muted">接口</span>
            <span className="font-medium text-foreground">
              #{startSnapshot?.connectorId ?? selectedConnectorId}
            </span>
          </div>
          <div className="flex justify-between text-sm">
            <span className="text-muted">储值卡</span>
            <span className="font-mono font-medium text-foreground">
              {startSnapshot?.idTag ?? selectedIdTag}
            </span>
          </div>
        </div>
        {startedTransactionId ? (
          <Link
            href={`/dashboard/transactions/${startedTransactionId}`}
            className="w-full max-w-xs"
          >
            <Button size="lg" className="w-full">
              <BanknoteArrowUp className="size-4" />
              查看订单
            </Button>
          </Link>
        ) : (
          <div className="w-full max-w-xs space-y-2">
            <Link href="/dashboard/transactions?status=active" className="block">
              <Button size="lg" variant="secondary" className="w-full">
                <BanknoteArrowUp className="size-4" />
                查看进行中订单
              </Button>
            </Link>
            <p className="text-xs text-muted text-center inline-flex w-full items-center justify-center gap-1.5">
              正在生成订单，可先前往列表查看
            </p>
          </div>
        )}
      </div>
    );
  }

  // ── Main UI ────────────────────────────────────────────────────────────────
  return (
    <div className="space-y-5 pb-4">
      {/* Header */}
      <div className="flex items-center justify-between gap-3">
        <div>
          <h1 className="text-xl font-bold text-foreground">立即充电</h1>
          <p className="mt-0.5 text-sm text-muted">选择充电桩和储值卡，远程启动充电</p>
        </div>

        {/* QR scan button — mobile only */}
        {isMobile && (
          <button
            type="button"
            onClick={() => {
              setScanError(null);
              setShowScanner(true);
            }}
            disabled={showScanner}
            className="flex shrink-0 flex-col items-center gap-1 rounded-2xl border border-border bg-surface px-3.5 py-2.5 text-foreground shadow-sm active:opacity-70 disabled:opacity-40"
          >
            <QrCode className="size-5" />
            <span className="text-[10px] font-medium leading-none">扫码</span>
          </button>
        )}
      </div>

      {/* QR Scanner Modal */}
      <Modal
        isOpen={showScanner}
        onOpenChange={(open) => {
          if (!open) setShowScanner(false);
        }}
      >
        <Modal.Backdrop>
          <Modal.Container scroll="outside" size="full">
            <Modal.Dialog className="h-full overflow-hidden p-0">
              <QrScanner onResult={handleScanResult} onClose={() => setShowScanner(false)} />
            </Modal.Dialog>
          </Modal.Container>
        </Modal.Backdrop>
      </Modal>

      <AlertDialog
        isOpen={startResult === "error" && !!startError}
        onOpenChange={(open) => {
          if (!open) {
            setStartResult(null);
            setStartError(null);
            setStartSnapshot(null);
          }
        }}
      >
        <AlertDialog.Backdrop variant="blur">
          <AlertDialog.Container size="sm">
            <AlertDialog.Dialog>
              <AlertDialog.Header>
                <AlertDialog.CloseTrigger />
                <AlertDialog.Icon status="danger" />
                <AlertDialog.Heading>启动失败</AlertDialog.Heading>
              </AlertDialog.Header>
              <AlertDialog.Body className="overflow-hidden">
                <p>{startError ?? "启动失败，请稍后重试"}</p>
              </AlertDialog.Body>
              <AlertDialog.Footer>
                <Button
                  slot="close"
                  variant="secondary"
                  onPress={() => {
                    setStartResult(null);
                    setStartError(null);
                  }}
                >
                  我知道了
                </Button>
              </AlertDialog.Footer>
            </AlertDialog.Dialog>
          </AlertDialog.Container>
        </AlertDialog.Backdrop>
      </AlertDialog>

      {scanError && (
        <div className="rounded-xl border border-danger/30 bg-danger/5 px-4 py-3 text-sm text-danger">
          {scanError}
        </div>
      )}

      {activeCount > 0 && (
        <Alert status="accent">
          <Alert.Indicator />
          <Alert.Content>
            <Alert.Title>当前有 {activeCount} 笔进行中的充电</Alert.Title>
            <Alert.Description>
              同一张储值卡无法发起多笔充电订单，若要结束进行中的订单，请{" "}
              <Link href={activeDetailHref} className="underline">
                <Button variant="secondary" size="sm" className="px-1.5 h-6 rounded-xl">
                  点击查看
                </Button>
              </Link>
            </Alert.Description>
          </Alert.Content>
        </Alert>
      )}

      {/* Step bar */}
      <StepBar step={step} onGoBack={(t) => setStep(t)} />

      {/* ── Step 1: Select charge point ──────────────────────────────── */}
      {step === 1 && (
        <div className="space-y-3">
          {cpLoading ? (
            <div className="flex justify-center py-16">
              <Spinner />
            </div>
          ) : chargePoints.filter((cp) => cp.registrationStatus === "Accepted").length === 0 ? (
            <div className="rounded-2xl border border-border px-6 py-14 text-center">
              <Plug className="mx-auto mb-3 size-10 text-muted" />
              <p className="font-medium text-foreground">暂无可用充电桩</p>
              <p className="mt-1 text-sm text-muted">请稍后刷新查看</p>
            </div>
          ) : (
            <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
              {chargePoints
                .filter((cp) => cp.registrationStatus === "Accepted")
                .map((cp) => {
                  const online =
                    !!cp.lastHeartbeatAt && dayjs().diff(dayjs(cp.lastHeartbeatAt), "second") < 120;
                  const availableCount = cp.connectors.filter(
                    (c) => c.status === "Available",
                  ).length;
                  const disabled = !online || availableCount === 0;
                  return (
                    <button
                      key={cp.id}
                      type="button"
                      disabled={disabled}
                      onClick={() => {
                        setSelectedCpId(cp.id);
                        setSelectedConnectorId(null);
                        setStep(2);
                      }}
                      className={[
                        "flex flex-col gap-3 rounded-2xl border p-4 text-left transition-all",
                        disabled
                          ? "cursor-not-allowed opacity-40 border-border bg-surface-secondary"
                          : selectedCpId === cp.id
                            ? "border-accent bg-accent/8 shadow-sm shadow-accent-soft-hover active:scale-[0.98]"
                            : "cursor-pointer border-border bg-surface hover:border-accent/60 hover:bg-accent/5 active:scale-[0.98]",
                      ].join(" ")}
                    >
                      {/* Top row: identifier + status */}
                      <div className="flex items-start justify-between gap-2">
                        <div className="flex min-w-0 flex-1 flex-col gap-0.5">
                          <span className="font-semibold text-foreground truncate leading-tight">
                            {cp.chargePointIdentifier}
                          </span>
                          {(cp.chargePointVendor || cp.chargePointModel) && (
                            <span className="text-xs text-muted truncate">
                              {[cp.chargePointVendor, cp.chargePointModel]
                                .filter(Boolean)
                                .join(" · ")}
                            </span>
                          )}
                        </div>
                        <span
                          className={[
                            "shrink-0 inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-semibold",
                            online
                              ? "bg-success/12 text-success"
                              : "bg-surface-tertiary text-muted",
                          ].join(" ")}
                        >
                          <span
                            className={`size-1.5 rounded-full ${online ? "bg-success" : "bg-muted"}`}
                          />
                          {online ? "在线" : "离线"}
                        </span>
                      </div>
                      {/* Bottom row: connectors + fee */}
                      <div className="flex items-center justify-between">
                        <span className="flex items-center gap-1.5 text-sm text-muted">
                          <Plug className="size-3.5 shrink-0" />
                          <span>
                            <span
                              className={availableCount > 0 ? "font-semibold text-foreground" : ""}
                            >
                              {availableCount}
                            </span>
                            /{cp.connectors.length} 空闲
                          </span>
                        </span>
                        {cp.pricingMode === "tou" ? (
                          <span className="text-sm font-medium text-accent">峰谷电价</span>
                        ) : cp.feePerKwh > 0 ? (
                          <span className="text-sm font-medium text-foreground">
                            ¥{(cp.feePerKwh / 100).toFixed(2)}
                            <span className="text-xs text-muted font-normal">/kWh</span>
                          </span>
                        ) : (
                          <span className="text-sm font-semibold text-success">免费</span>
                        )}
                      </div>
                    </button>
                  );
                })}
            </div>
          )}
        </div>
      )}

      {/* ── Step 2: Select connector ──────────────────────────────────── */}
      {step === 2 && (
        <div className="space-y-4">
          {/* Context pill */}
          {selectedCp && (
            <div className="inline-flex items-center gap-1.5 rounded-full border border-border bg-surface px-3 py-1.5 text-xs">
              <EvCharger className="size-3.5 text-muted" />
              <span className="text-muted">充电桩</span>
              <span className="font-semibold text-foreground">
                {selectedCp.chargePointIdentifier}
              </span>
            </div>
          )}

          {selectedCp && selectedCp.connectors.filter((c) => c.connectorId > 0).length === 0 ? (
            <div className="rounded-2xl border border-border px-6 py-14 text-center">
              <Plug className="mx-auto mb-3 size-10 text-muted" />
              <p className="font-medium text-foreground">该桩暂无可用接口</p>
            </div>
          ) : (
            <div className="grid gap-3 grid-cols-2 sm:grid-cols-3 lg:grid-cols-4">
              {selectedCp?.connectors
                .filter((c) => c.connectorId > 0)
                .sort((a, b) => a.connectorId - b.connectorId)
                .map((conn) => {
                  const available = conn.status === "Available";
                  return (
                    <button
                      key={conn.id}
                      type="button"
                      disabled={!available}
                      onClick={() => {
                        if (available) {
                          setSelectedConnectorId(conn.connectorId);
                          setStep(3);
                        }
                      }}
                      className={[
                        "relative flex flex-col items-center gap-3 rounded-2xl border py-5 px-3 text-center transition-all",
                        !available
                          ? "cursor-not-allowed opacity-40 border-border bg-surface-secondary"
                          : selectedConnectorId === conn.connectorId
                            ? "border-accent bg-accent/8 shadow-sm shadow-accent-soft-hover active:scale-[0.97]"
                            : "cursor-pointer border-border bg-surface hover:border-accent/60 hover:bg-accent/5 active:scale-[0.97]",
                      ].join(" ")}
                    >
                      <span
                        className={[
                          "flex size-12 items-center justify-center rounded-full text-xl font-bold",
                          available
                            ? "bg-success/12 text-success"
                            : "bg-surface-tertiary text-muted",
                        ].join(" ")}
                      >
                        {conn.connectorId}
                      </span>
                      <div className="space-y-0.5">
                        <p className="text-sm font-semibold text-foreground">
                          接口 #{conn.connectorId}
                        </p>
                        <p
                          className={[
                            "text-xs font-medium",
                            conn.status === "Available" ? "text-success" : "text-muted",
                          ].join(" ")}
                        >
                          {statusLabelMap[conn.status] ?? conn.status}
                        </p>
                      </div>
                      {selectedConnectorId === conn.connectorId && (
                        <span className="absolute right-2 top-2 flex size-5 items-center justify-center rounded-full bg-accent">
                          <Check className="size-3 text-white" />
                        </span>
                      )}
                    </button>
                  );
                })}
            </div>
          )}

          <Button variant="secondary" size="sm" onPress={() => setStep(1)}>
            上一步
          </Button>
        </div>
      )}

      {/* ── Step 3: Select ID tag + start ────────────────────────────── */}
      {step === 3 && (
        <div className="space-y-4">
          {/* Context pills */}
          <div className="flex flex-wrap gap-2">
            {selectedCp && (
              <div className="inline-flex items-center gap-1.5 rounded-full border border-border bg-surface px-3 py-1.5 text-xs">
                <EvCharger className="size-3.5 text-muted" />
                <span className="text-muted">充电桩</span>
                <span className="font-semibold text-foreground">
                  {selectedCp.chargePointIdentifier}
                </span>
              </div>
            )}
            {selectedConnectorId !== null && (
              <div className="inline-flex items-center gap-1.5 rounded-full border border-border bg-surface px-3 py-1.5 text-xs">
                <Plug className="size-3.5 text-muted" />
                <span className="text-muted">接口</span>
                <span className="font-semibold text-foreground">#{selectedConnectorId}</span>
              </div>
            )}
          </div>

          <p className="text-sm font-semibold text-foreground">选择储值卡充电</p>

          {tagsLoading ? (
            <div className="flex justify-center py-16">
              <Spinner />
            </div>
          ) : myTags.length === 0 ? (
            <div className="rounded-2xl border border-border px-6 py-14 text-center">
              <CreditCard className="mx-auto mb-3 size-10 text-muted" />
              <p className="font-medium text-foreground">你还没有储值卡</p>
              <p className="mt-1 text-sm text-muted">
                请前往
                <Link href="/dashboard/id-tags" className="text-accent hover:underline">
                  储值卡
                </Link>
                页面申领或前往服务中心办理
              </p>
            </div>
          ) : (
            <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
              {myTags.map((tag) => {
                const locked = activeIdTagSet.has(tag.idTag);
                return (
                  <div key={tag.idTag} className="space-y-2">
                    <IdTagCard
                      idTag={tag.idTag}
                      balance={tag.balance}
                      layout={tag.cardLayout ?? undefined}
                      skin={tag.cardSkin ?? undefined}
                      isSelected={selectedIdTag === tag.idTag}
                      isDisabled={locked}
                      onClick={() => {
                        if (!locked) setSelectedIdTag(tag.idTag);
                      }}
                    />
                    {locked && (
                      <p className="px-1 text-xs font-medium text-warning">
                        该卡有进行中的订单，暂不可再次启动
                      </p>
                    )}
                  </div>
                );
              })}
            </div>
          )}

          {/* Action bar */}
          <div className="flex gap-3 pt-1">
            <Button
              variant="secondary"
              onPress={() => {
                setStep(2);
                setStartResult(null);
                setStartError(null);
                setStartSnapshot(null);
              }}
            >
              上一步
            </Button>
            <Button
              className="flex-1"
              isDisabled={!selectedIdTag || startMutation.isPending}
              onPress={() => startMutation.mutate()}
            >
              {startMutation.isPending ? (
                <Spinner size="sm" />
              ) : (
                <ThunderboltFill className="size-4" />
              )}
              {startMutation.isPending ? "发送中…" : "启动充电"}
            </Button>
          </div>
        </div>
      )}
    </div>
  );
}

export default function ChargePage() {
  return (
    <Suspense>
      <ChargePageContent />
    </Suspense>
  );
}
