import { useState, useEffect, useRef } from "react";
import { ESPLoader, Transport } from "esptool-js";
import cloudflashLogo from "../assets/logos/cloudflash_large.png";

// ─── constants ────────────────────────────────────────────────────────────────

const STEPS = [
  { id: 1, label: "Welcome"  },
  { id: 2, label: "Hardware" },
  { id: 3, label: "Options"  },
  { id: 4, label: "Flash"    },
  { id: 5, label: "Done"     },
];

const FLASH_BAUD       = 460800;
const NVS_ADDRESS      = 0x9000;
const FW_ADDRESS       = 0x10000;
const VERIFY_POLL_MS   = 6000;
const VERIFY_TIMEOUT_MS = 120_000;

const FLASH_TYPE_FULL     = "full_wipe";
const FLASH_TYPE_FW_ONLY  = "fw_only";

const NVS_SCHEMA_LEGACY = "legacy"; // deviceUID / hwType / hwVersion
const NVS_SCHEMA_NEW    = "new";    // serial / hwFamily / hwRevision

// Fixed card height so the page doesn't jump between steps
const CARD_MIN_HEIGHT = 420;

// ─── helpers ──────────────────────────────────────────────────────────────────

function arrayBufferToString(buf) {
  const bytes = new Uint8Array(buf);
  let str = "";
  for (let i = 0; i < bytes.length; i++) str += String.fromCharCode(bytes[i]);
  return str;
}

async function fetchBinary(url) {
  const resp = await fetch(url);
  if (!resp.ok) {
    const err = await resp.json().catch(() => ({}));
    throw new Error(err.detail || `Failed to fetch ${url}: ${resp.status}`);
  }
  return resp.arrayBuffer();
}

async function postForBinary(url, body) {
  const resp = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if (!resp.ok) {
    const err = await resp.json().catch(() => ({}));
    throw new Error(err.detail || `Request failed: ${resp.status}`);
  }
  return resp.arrayBuffer();
}

// ─── Shared sub-components ────────────────────────────────────────────────────

function StepIndicator({ current }) {
  return (
    <div className="flex items-center justify-center mb-8" style={{ gap: 0 }}>
      {STEPS.map((step, i) => {
        const done    = step.id < current;
        const active  = step.id === current;
        const pending = step.id > current;
        const isLast  = i === STEPS.length - 1;

        return (
          <div key={step.id} className="flex items-center">
            {/* Circle */}
            <div className="flex flex-col items-center" style={{ width: 64 }}>
              <div
                className="flex items-center justify-center rounded-full text-xs font-bold transition-all"
                style={{
                  width: 28,
                  height: 28,
                  backgroundColor: done
                    ? "var(--accent)"
                    : active
                      ? "var(--accent)"
                      : "var(--bg-card-hover)",
                  color: done || active ? "var(--bg-primary)" : "var(--text-muted)",
                  border: `2px solid ${done || active ? "var(--accent)" : "var(--border-primary)"}`,
                  boxShadow: active ? "0 0 10px 3px rgba(116,184,22,0.4)" : "none",
                }}
              >
                {done ? (
                  <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
                  </svg>
                ) : (
                  step.id
                )}
              </div>
              <span
                className="text-[10px] mt-1 font-medium text-center"
                style={{ color: active ? "var(--accent)" : done ? "var(--text-secondary)" : "var(--text-muted)" }}
              >
                {step.label}
              </span>
            </div>

            {/* Connector line */}
            {!isLast && (
              <div
                style={{
                  width: 40,
                  height: 2,
                  marginBottom: 18,
                  backgroundColor: done ? "var(--accent)" : "var(--border-primary)",
                  transition: "background-color 0.3s",
                }}
              />
            )}
          </div>
        );
      })}
    </div>
  );
}

function ProgressBar({ label, percent }) {
  return (
    <div className="space-y-1.5">
      <div className="flex justify-between text-xs" style={{ color: "var(--text-secondary)" }}>
        <span>{label}</span>
        <span>{Math.round(percent)}%</span>
      </div>
      <div className="h-3 rounded-full overflow-hidden" style={{ backgroundColor: "var(--bg-card-hover)" }}>
        <div
          className="h-full rounded-full transition-all duration-200"
          style={{ width: `${percent}%`, backgroundColor: "var(--accent)" }}
        />
      </div>
    </div>
  );
}

function CombinedProgressBar({ blPct, partPct, nvsPct, fwPct }) {
  // Weights: bl=10%, part=10%, nvs=10%, fw=70%
  const overall = blPct * 0.10 + partPct * 0.10 + nvsPct * 0.10 + fwPct * 0.70;

  const label =
    fwPct > 0    ? `Firmware (${Math.round(fwPct)}%)`       :
    nvsPct > 0   ? `Device Identity (${Math.round(nvsPct)}%)` :
    partPct > 0  ? `Partition Table (${Math.round(partPct)}%)` :
                   `Bootloader (${Math.round(blPct)}%)`;

  return (
    <div className="space-y-1.5">
      <div className="flex justify-between text-xs" style={{ color: "var(--text-secondary)" }}>
        <span>{label}</span>
        <span>{Math.round(overall)}%</span>
      </div>
      <div className="h-3 rounded-full overflow-hidden" style={{ backgroundColor: "var(--bg-card-hover)" }}>
        <div
          className="h-full rounded-full transition-all duration-200"
          style={{ width: `${overall}%`, backgroundColor: "var(--accent)" }}
        />
      </div>
    </div>
  );
}

function InfoBox({ type = "info", children }) {
  const styles = {
    info:    { bg: "var(--badge-blue-bg)",  border: "#1e3a5f",  color: "var(--badge-blue-text)" },
    warning: { bg: "#2e1a00",               border: "#7c4a00",  color: "#fb923c"                },
    error:   { bg: "var(--danger-bg)",      border: "var(--danger)", color: "var(--danger-text)" },
    success: { bg: "var(--success-bg)",     border: "var(--success)", color: "var(--success-text)" },
  };
  const s = styles[type] || styles.info;
  return (
    <div
      className="text-sm rounded-md p-3 border"
      style={{ backgroundColor: s.bg, borderColor: s.border, color: s.color }}
    >
      {children}
    </div>
  );
}

function SectionTitle({ children }) {
  return (
    <h2 className="text-xl font-bold mb-2 text-center" style={{ color: "var(--text-heading)" }}>
      {children}
    </h2>
  );
}

function SubTitle({ children }) {
  return (
    <p className="text-sm text-center mb-6" style={{ color: "var(--text-secondary)", lineHeight: 1.6 }}>
      {children}
    </p>
  );
}

function PrimaryButton({ onClick, disabled, children, fullWidth = false }) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`px-6 py-3 rounded-md font-semibold text-sm transition-opacity hover:opacity-90 disabled:opacity-40 cursor-pointer disabled:cursor-not-allowed ${fullWidth ? "w-full" : ""}`}
      style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
    >
      {children}
    </button>
  );
}

function SecondaryButton({ onClick, disabled, children }) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className="px-5 py-2.5 rounded-md font-medium text-sm transition-colors hover:opacity-80 cursor-pointer disabled:opacity-40"
      style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-secondary)", border: "1px solid var(--border-primary)" }}
    >
      {children}
    </button>
  );
}

// ─── Step 1: Welcome ──────────────────────────────────────────────────────────

function StepWelcome({ onNext }) {
  return (
    <div className="flex flex-col items-center justify-center h-full text-center px-4">

      <h1 className="text-2xl font-bold mb-3" style={{ color: "var(--text-heading)" }}>
        Welcome to BellSystems, CloudFlash !
      </h1>
      <p className="text-sm  mb-5" style={{ color: "var(--text-secondary)", lineHeight: 1.7 }}>
        CloudFlash allows you to restore or update the firmware on your BellSystems device <br></br> using only
        a USB cable and your web browser — no special software required.
      </p>

      <div
        className="rounded-lg border p-4 mb-8 text-left w-full space-y-2"
        style={{ backgroundColor: "var(--bg-secondary)", borderColor: "var(--border-primary)" }}
      >
        <p className="text-xs font-semibold uppercase tracking-wide mb-2" style={{ color: "var(--text-muted)" }}>
          Before you begin, please make sure:
        </p>
        {[
          "You are using Google Chrome or Microsoft Edge on a desktop or laptop computer.",
          "Your BellSystems device is connected via USB cable.",
          "The device is powered on.",
          "You have the serial number from the sticker on the bottom of your device (needed for a full restore only).",
        ].map((item, i) => (
          <div key={i} className="flex items-start gap-2">
            <span className="text-xs font-bold mt-0.5 flex-shrink-0" style={{ color: "var(--accent)" }}>✓</span>
            <span className="text-xs" style={{ color: "var(--text-secondary)" }}>{item}</span>
          </div>
        ))}
      </div>

      <PrimaryButton onClick={onNext}>
        I'm ready — let's begin →
      </PrimaryButton>
    </div>
  );
}

// ─── Step 2: Select Hardware ──────────────────────────────────────────────────

function StepSelectHardware({ onNext }) {
  const [firmwares, setFirmwares]       = useState([]);
  const [loading, setLoading]           = useState(true);
  const [error, setError]               = useState("");
  const [selectedHwType, setHwType]     = useState("");

  useEffect(() => {
    fetch("/api/public/cloudflash/firmware")
      .then((r) => {
        if (!r.ok) return r.json().then((e) => { throw new Error(e.detail || "Failed to load firmware list."); });
        return r.json();
      })
      .then(setFirmwares)
      .catch((e) => setError(e.message))
      .finally(() => setLoading(false));
  }, []);

  const selectedFw = firmwares.find((f) => f.hw_type === selectedHwType);

  return (
    <div className="flex flex-col h-full">
      <SectionTitle>Select Your Device Type</SectionTitle>
      <SubTitle>
        Choose the model that matches your device.<br></br>You can find the model name on the white sticker
        on the bottom or back panel of your device, labelled <strong style={{ color: "var(--text-primary)" }}>"Model"</strong>.
      </SubTitle>

      {error && <div className="mb-4"><InfoBox type="error">{error}</InfoBox></div>}

      {loading ? (
        <div className="flex-1 flex items-center justify-center">
          <p className="text-sm" style={{ color: "var(--text-muted)" }}>Loading available devices…</p>
        </div>
      ) : firmwares.length === 0 ? (
        <div className="mb-4">
          <InfoBox type="warning">
            No firmware is currently available. Please contact BellSystems support.
          </InfoBox>
        </div>
      ) : (
        <div className="flex flex-wrap justify-center gap-3 mb-6">
          {firmwares.map((fw) => (
            <HardwareCard
              key={fw.hw_type}
              fw={fw}
              selected={selectedHwType === fw.hw_type}
              onClick={() => setHwType(fw.hw_type)}
            />
          ))}
        </div>
      )}

      <div
        className="rounded-lg border p-3 mb-6 flex items-start gap-2"
        style={{ backgroundColor: "var(--bg-secondary)", borderColor: "var(--border-secondary)" }}
      >
        <svg className="w-4 h-4 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: "var(--text-muted)" }}>
          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
        </svg>
        <p className="text-xs" style={{ color: "var(--text-muted)" }}>
          Not sure which model you have? The model name is printed on the label on the back of your device.
          If you still can't find it, contact BellSystems support before proceeding.
        </p>
      </div>

      <div className="flex justify-end mt-auto">
        <PrimaryButton onClick={() => onNext(selectedFw)} disabled={!selectedFw}>
          Continue with {selectedFw ? selectedFw.hw_type_label : "selected device"} →
        </PrimaryButton>
      </div>
    </div>
  );
}

function HardwareCard({ fw, selected, onClick }) {
  return (
    <button
      type="button"
      onClick={onClick}
      className="rounded-lg border p-4 text-center transition-all cursor-pointer"
      style={{
        width: 160,
        backgroundColor: selected ? "var(--bg-secondary)" : "var(--bg-card)",
        borderColor: selected ? "var(--accent)" : "var(--border-primary)",
        boxShadow: selected ? "0 0 12px 3px rgba(116,184,22,0.25)" : "none",
      }}
    >
      <p className="text-sm font-semibold mb-0.5" style={{ color: selected ? "var(--accent)" : "var(--text-heading)" }}>
        {fw.hw_type_label}
      </p>
      <p className="text-xs font-mono" style={{ color: "var(--text-muted)" }}>
        v{fw.version}
      </p>
    </button>
  );
}

// ─── Step 3: Flash Type ───────────────────────────────────────────────────────

function StepSelectFlashType({ firmware, onNext }) {
  const [flashType, setFlashType]     = useState("");
  const [serial, setSerial]           = useState("");
  const [serialError, setSerialError] = useState("");
  const [serialValid, setSerialValid] = useState(false);
  const [validating, setValidating]   = useState(false);
  const [nvsSchema, setNvsSchema]     = useState(NVS_SCHEMA_NEW);

  const handleSerialBlur = async () => {
    const trimmed = serial.trim().toUpperCase();
    if (!trimmed) return;
    setValidating(true);
    setSerialError("");
    setSerialValid(false);
    try {
      const resp = await fetch(`/api/public/cloudflash/validate-serial/${encodeURIComponent(trimmed)}`);
      const data = await resp.json();
      if (data.valid) {
        setSerialValid(true);
      } else {
        setSerialError(
          "We couldn't find this serial number in our database. " +
          "Please double-check the sticker on your device and try again. " +
          "If you're sure it's correct, please contact BellSystems support."
        );
      }
    } catch {
      setSerialError("Could not verify serial number. Please check your internet connection and try again.");
    } finally {
      setValidating(false);
    }
  };

  const handleContinue = () => {
    if (flashType === FLASH_TYPE_FULL) {
      const trimmed = serial.trim().toUpperCase();
      if (!trimmed) { setSerialError("Please enter the serial number from the sticker on your device."); return; }
      if (!serialValid) { setSerialError("Please wait for the serial number to be verified, or correct it first."); return; }
      onNext({ flashType, serial: trimmed, nvsSchema });
    } else {
      onNext({ flashType, serial: null, nvsSchema: null });
    }
  };

  const isFullWipe = flashType === FLASH_TYPE_FULL;
  const inputBorderColor = serialError ? "var(--danger)" : serialValid ? "var(--accent)" : "var(--border-input)";

  return (
    <div className="flex flex-col h-full">
      <SectionTitle>Choose Flash Type</SectionTitle>
      <SubTitle>
        Select how you'd like to restore your <strong style={{ color: "var(--text-primary)" }}>{firmware.hw_type_label}</strong>.
        If you're unsure, start with <strong style={{ color: "var(--text-primary)" }}>Firmware Only</strong>.
      </SubTitle>

      <div className="grid grid-cols-2 gap-3 mb-6">
        <FlashTypeCard
          title="Firmware Update Only"
          subtitle="Recommended for most users"
          description="Installs only the firmware. Your device's configuration, settings, and network credentials are preserved. This is the safest option and works for most situations."
          badge="Safe"
          badgeColor="var(--success-text)"
          badgeBg="var(--success-bg)"
          icon={
            <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
            </svg>
          }
          selected={flashType === FLASH_TYPE_FW_ONLY}
          onClick={() => setFlashType(FLASH_TYPE_FW_ONLY)}
        />
        <FlashTypeCard
          title="Full Wipe & Restore"
          subtitle="Complete factory restore"
          description="Reinstalls everything from scratch — bootloader, partition table, NVS identity data, and firmware. Use this only if your device is bricked or if support has specifically asked you to do so."
          badge="Advanced"
          badgeColor="#fb923c"
          badgeBg="#2e1a00"
          icon={
            <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
            </svg>
          }
          selected={flashType === FLASH_TYPE_FULL}
          onClick={() => setFlashType(FLASH_TYPE_FULL)}
        />
      </div>

      {/* Serial + NVS schema — only shown for full wipe */}
      {isFullWipe && (
        <div
          className="rounded-lg border mb-4 overflow-hidden"
          style={{ backgroundColor: "var(--bg-secondary)", borderColor: "var(--border-primary)" }}
        >
          {/* Two-column split: 70% serial input / 30% NVS schema selector */}
          <div className="flex" style={{ minHeight: 0 }}>

            {/* Left — serial input (70%) */}
            {/* Left — NVS schema selector (30%) */}
            <div className="p-4" style={{ flex: "0 0 35%" }}>
              <label className="block text-sm font-semibold mb-1" style={{ color: "var(--text-heading)" }}>
                NVS Identity Format
              </label>
              <p className="text-xs mb-3" style={{ color: "var(--text-muted)" }}>
                Choose the format that matches your device generation.
              </p>
              <div className="flex flex-col gap-2">
                <NvsSchemaButton
                  label="Current Generation"
                  description="serial · hwFamily · hwRevision"
                  selected={nvsSchema === NVS_SCHEMA_NEW}
                  isDefault
                  onClick={() => setNvsSchema(NVS_SCHEMA_NEW)}
                />
                <NvsSchemaButton
                  label="Legacy Generation"
                  description="deviceUID · hwType · hwVersion"
                  selected={nvsSchema === NVS_SCHEMA_LEGACY}
                  onClick={() => setNvsSchema(NVS_SCHEMA_LEGACY)}
                />
              </div>
            </div>

            {/* Divider */}
            <div style={{ width: 1, backgroundColor: "var(--border-primary)", flexShrink: 0, margin: "16px 0" }} />

            {/* Right — serial input (70%) */}
            <div className="p-4" style={{ flex: 1 }}>
              <label className="block text-sm font-semibold mb-1" style={{ color: "var(--text-heading)" }}>
                Device Serial Number
              </label>
              <p className="text-xs mb-3" style={{ color: "var(--text-muted)" }}>
                Found on the sticker on the bottom of your device.{" "}
                {nvsSchema === NVS_SCHEMA_NEW
                  ? <>Looks like: <span className="font-mono" style={{ color: "var(--text-secondary)" }}>BSVSPR-26C18X-STD10R-5837FG</span></>
                  : <>Looks like: <span className="font-mono" style={{ color: "var(--text-secondary)" }}>PV25L22BP01R01</span></>
                }. Enter it exactly as shown, then click outside to verify.
              </p>
              <div className="relative">
                <input
                  type="text"
                  value={serial}
                  onChange={(e) => { setSerial(e.target.value.toUpperCase()); setSerialError(""); setSerialValid(false); }}
                  onBlur={handleSerialBlur}
                  placeholder={nvsSchema === NVS_SCHEMA_NEW ? "BSXXXX-XXXXXX-XXXXXX-XXXXXX" : "PVXXXXXXXXXXXX"}
                  className="w-full px-3 py-2.5 rounded-md text-sm border font-mono pr-10"
                  style={{ backgroundColor: "var(--bg-input)", borderColor: inputBorderColor, color: "var(--text-primary)" }}
                  spellCheck={false}
                  disabled={validating}
                />
                <div className="absolute right-3 top-1/2 -translate-y-1/2">
                  {validating && (
                    <svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24" style={{ color: "var(--text-muted)" }}>
                      <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
                      <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z" />
                    </svg>
                  )}
                  {!validating && serialValid && (
                    <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: "var(--accent)" }}>
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
                    </svg>
                  )}
                  {!validating && serialError && (
                    <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: "var(--danger)" }}>
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
                    </svg>
                  )}
                </div>
              </div>
              {serialError && (
                <p className="text-xs mt-2 leading-relaxed" style={{ color: "var(--danger)" }}>{serialError}</p>
              )}
              {serialValid && (
                <p className="text-xs mt-2" style={{ color: "var(--accent)" }}>
                  ✓ Serial verified — device found in the BellSystems database.
                </p>
              )}
            </div>

          </div>
        </div>
      )}

      <div className="flex justify-end mt-auto">
        <PrimaryButton
          onClick={handleContinue}
          disabled={!flashType || validating || (isFullWipe && !serialValid)}
        >
          {validating ? "Verifying serial…" : "Continue →"}
        </PrimaryButton>
      </div>
    </div>
  );
}

function NvsSchemaButton({ label, description, selected, isDefault = false, onClick }) {
  return (
    <button
      type="button"
      onClick={onClick}
      className="w-full rounded-md border p-2.5 text-left transition-all cursor-pointer"
      style={{
        backgroundColor: selected ? "var(--bg-card)" : "transparent",
        borderColor: selected ? "var(--accent)" : "var(--border-primary)",
        boxShadow: selected ? "0 0 8px 2px rgba(116,184,22,0.18)" : "none",
      }}
    >
      <div className="flex items-start gap-2">
        <div
          className="flex-shrink-0 w-3.5 h-3.5 rounded-full border-2 flex items-center justify-center mt-0.5"
          style={{
            borderColor: selected ? "var(--accent)" : "var(--border-primary)",
            backgroundColor: selected ? "var(--accent)" : "transparent",
          }}
        >
          {selected && <div style={{ width: 5, height: 5, borderRadius: "50%", backgroundColor: "var(--bg-primary)" }} />}
        </div>
        <div className="min-w-0">
          <div className="flex items-center gap-1.5 flex-wrap">
            <span className="text-xs font-semibold" style={{ color: selected ? "var(--accent)" : "var(--text-secondary)" }}>
              {label}
            </span>
            {isDefault && (
              <span
                className="text-[9px] font-semibold px-1 py-0.5 rounded"
                style={{ backgroundColor: "rgba(116,184,22,0.15)", color: "var(--accent)" }}
              >
                DEFAULT
              </span>
            )}
          </div>
          <p className="text-[10px] font-mono mt-0.5 leading-snug" style={{ color: "var(--text-muted)" }}>
            {description}
          </p>
        </div>
      </div>
    </button>
  );
}

function FlashTypeCard({ title, subtitle, description, badge, badgeColor, badgeBg, icon, selected, onClick }) {
  return (
    <button
      type="button"
      onClick={onClick}
      className="w-full rounded-lg border p-4 text-left transition-all cursor-pointer flex flex-col justify-start"
      style={{
        backgroundColor: selected ? "var(--bg-secondary)" : "var(--bg-card)",
        borderColor: selected ? "var(--accent)" : "var(--border-primary)",
        boxShadow: selected ? "0 0 12px 3px rgba(116,184,22,0.2)" : "none",
      }}
    >
      <div className="flex items-start gap-3">
        <div
          className="flex-shrink-0 flex items-center justify-center rounded-md mt-0.5"
          style={{ width: 36, height: 36, backgroundColor: selected ? "rgba(116,184,22,0.15)" : "var(--bg-card-hover)", color: selected ? "var(--accent)" : "var(--text-muted)" }}
        >
          {icon}
        </div>
        <div className="flex-1 min-w-0">
          <div className="flex items-center gap-2 mb-0.5 flex-wrap">
            <span className="text-sm font-semibold" style={{ color: selected ? "var(--accent)" : "var(--text-heading)" }}>
              {title}
            </span>
            <span
              className="text-[10px] font-semibold px-1.5 py-0.5 rounded"
              style={{ backgroundColor: badgeBg, color: badgeColor }}
            >
              {badge}
            </span>
          </div>
          <p className="text-xs font-medium mb-1" style={{ color: "var(--text-secondary)" }}>{subtitle}</p>
          <p className="text-xs leading-relaxed" style={{ color: "var(--text-muted)" }}>{description}</p>
        </div>
        <div
          className="flex-shrink-0 w-5 h-5 rounded-full border-2 flex items-center justify-center mt-0.5"
          style={{ borderColor: selected ? "var(--accent)" : "var(--border-primary)", backgroundColor: selected ? "var(--accent)" : "transparent" }}
        >
          {selected && (
            <svg className="w-3 h-3" fill="none" stroke="var(--bg-primary)" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
            </svg>
          )}
        </div>
      </div>
    </button>
  );
}

// ─── Step 4: Connect & Flash ──────────────────────────────────────────────────

function StepFlash({ firmware, flashType, serial, nvsSchema, onDone }) {
  const [phase, setPhase]               = useState("connect"); // connect | flashing | done
  const [connecting, setConnecting]     = useState(false);
  const [portName, setPortName]         = useState("");
  const [portConnected, setPortConnected] = useState(false);
  const [blPct, setBlPct]               = useState(0);
  const [partPct, setPartPct]           = useState(0);
  const [nvsPct, setNvsPct]             = useState(0);
  const [fwPct, setFwPct]               = useState(0);
  const [error, setError]               = useState("");
  const [log, setLog]                   = useState([]);

  const portRef   = useRef(null);
  const loaderRef = useRef(null);
  const logEndRef = useRef(null);

  const appendLog = (msg) => {
    setLog((prev) => [...prev, String(msg)]);
    setTimeout(() => logEndRef.current?.scrollIntoView({ behavior: "smooth" }), 50);
  };

  const webSerialAvailable = "serial" in navigator;

  const handleConnect = async () => {
    setError("");
    setConnecting(true);
    try {
      const port = await navigator.serial.requestPort();
      const info = port.getInfo?.() || {};
      const label = info.usbVendorId
        ? `USB ${info.usbVendorId.toString(16).toUpperCase()}:${(info.usbProductId || 0).toString(16).toUpperCase()}`
        : "Serial Port";
      portRef.current = port;
      setPortName(label);
      setPortConnected(true);
    } catch (err) {
      if (err.name !== "NotFoundError") {
        setError(err.message || "Port selection failed.");
      }
    } finally {
      setConnecting(false);
    }
  };

  const handleFlash = async () => {
    if (!portRef.current) return;
    setError("");
    setLog([]);
    setBlPct(0); setPartPct(0); setNvsPct(0); setFwPct(0);
    setPhase("flashing");

    try {
      appendLog("Fetching firmware files from BellSystems servers…");

      let blBuffer = null, partBuffer = null, nvsBuffer = null;

      if (flashType === FLASH_TYPE_FULL) {
        appendLog("Downloading bootloader…");
        blBuffer = await fetchBinary(`/api/public/cloudflash/${firmware.hw_type}/bootloader.bin`);
        appendLog(`Bootloader: ${blBuffer.byteLength} bytes`);

        appendLog("Downloading partition table…");
        partBuffer = await fetchBinary(`/api/public/cloudflash/${firmware.hw_type}/partitions.bin`);
        appendLog(`Partition table: ${partBuffer.byteLength} bytes`);

        appendLog("Generating NVS identity data…");
        nvsBuffer = await postForBinary("/api/public/cloudflash/nvs.bin", {
          serial_number: serial,
          hw_type: firmware.hw_type,
          hw_revision: "1.0",
          nvs_schema: nvsSchema,
        });
        appendLog(`NVS: ${nvsBuffer.byteLength} bytes`);
      }

      appendLog("Downloading firmware…");
      const fwBuffer = await fetchBinary(firmware.download_url);
      appendLog(`Firmware: ${fwBuffer.byteLength} bytes`);

      appendLog("Connecting to your device…");
      const transport = new Transport(portRef.current, true);
      loaderRef.current = new ESPLoader({
        transport,
        baudrate: FLASH_BAUD,
        terminal: {
          clean() {},
          writeLine: (line) => { appendLog(line); },
          write: (msg) => { appendLog(msg); },
        },
      });

      await loaderRef.current.main();
      appendLog("Device connected. Starting flash…");

      const fileArray = flashType === FLASH_TYPE_FULL
        ? [
            { data: arrayBufferToString(blBuffer),   address: 0x1000        },
            { data: arrayBufferToString(partBuffer), address: 0x8000        },
            { data: arrayBufferToString(nvsBuffer),  address: NVS_ADDRESS   },
            { data: arrayBufferToString(fwBuffer),   address: FW_ADDRESS    },
          ]
        : [
            { data: arrayBufferToString(fwBuffer),   address: FW_ADDRESS    },
          ];

      await loaderRef.current.writeFlash({
        fileArray,
        flashSize: "keep", flashMode: "keep", flashFreq: "keep",
        eraseAll: flashType === FLASH_TYPE_FULL, compress: true,
        reportProgress(fileIndex, written, total) {
          const pct = (written / total) * 100;
          if (flashType === FLASH_TYPE_FULL) {
            if      (fileIndex === 0) { setBlPct(pct); }
            else if (fileIndex === 1) { setBlPct(100); setPartPct(pct); }
            else if (fileIndex === 2) { setPartPct(100); setNvsPct(pct); }
            else                      { setNvsPct(100); setFwPct(pct); }
          } else {
            setFwPct(pct);
          }
        },
        calculateMD5Hash: () => "",
      });

      if (flashType === FLASH_TYPE_FULL) {
        setBlPct(100); setPartPct(100); setNvsPct(100);
      }
      setFwPct(100);
      appendLog("Flash complete! Rebooting device…");

      // Hard reset via RTS
      try {
        const t = loaderRef.current.transport;
        await t.setRTS(true);
        await new Promise((r) => setTimeout(r, 100));
        await t.setRTS(false);
      } catch (_) {}

      try { await loaderRef.current.transport.disconnect(); } catch (_) {}

      setPhase("done");
      onDone({ serial });
    } catch (err) {
      setError(err.message || String(err));
      setPhase("connect");
    }
  };

  const isFullWipe = flashType === FLASH_TYPE_FULL;
  const flashing   = phase === "flashing";

  return (
    <div className="flex flex-col h-full">
      <SectionTitle>Connect &amp; Flash</SectionTitle>
      <SubTitle>
        Connect your <strong style={{ color: "var(--text-primary)" }}>{firmware.hw_type_label}</strong> via USB,
        then click "Select Port" to choose it.
        {isFullWipe && serial && (
          <> Your serial number <span className="font-mono" style={{ color: "var(--text-primary)" }}>{serial}</span> will be written to the device.</>
        )}
      </SubTitle>

      {!webSerialAvailable && (
        <div className="mb-4">
          <InfoBox type="warning">
            <strong>Browser not supported.</strong> CloudFlash requires Google Chrome or Microsoft Edge on a desktop
            computer. Safari, Firefox, and mobile browsers are not supported.
          </InfoBox>
        </div>
      )}

      {error && (
        <div className="mb-4">
          <InfoBox type="error">
            <strong>Error:</strong> {error}
          </InfoBox>
        </div>
      )}

      {/* Port status */}
      <div
        className="rounded-lg border p-4 mb-4 flex items-center justify-between gap-4"
        style={{ backgroundColor: "var(--bg-secondary)", borderColor: portConnected ? "var(--accent)" : "var(--border-primary)" }}
      >
        <div className="flex items-center gap-2.5">
          <span
            className="inline-block w-2.5 h-2.5 rounded-full flex-shrink-0"
            style={{ backgroundColor: portConnected ? "#22c55e" : "var(--border-primary)" }}
          />
          <span className="text-sm" style={{ color: portConnected ? "var(--text-heading)" : "var(--text-muted)" }}>
            {portConnected ? portName || "Device connected" : "No device selected"}
          </span>
        </div>
        {!portConnected && (
          <button
            onClick={handleConnect}
            disabled={!webSerialAvailable || connecting || flashing}
            className="px-4 py-2 rounded-md text-sm font-medium cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed hover:opacity-90 transition-opacity"
            style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
          >
            {connecting ? "Waiting…" : "Select Port"}
          </button>
        )}
      </div>

      {/* How-to hint */}
      {!portConnected && !flashing && (
        <div
          className="rounded-lg border p-3 mb-4 space-y-1.5"
          style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-secondary)" }}
        >
          <p className="text-xs font-semibold" style={{ color: "var(--text-secondary)" }}>How to connect your device:</p>
          {[
            "Plug your BellSystems device into your computer using a USB cable.",
            "Click the \"Select Port\" button above and a browser popup will appear.",
            "Look for an entry that says \"CP210x\", \"CH340\" or \"USB Serial (COM#)\" and click it.",
            "If you don't see your device, try a different USB cable or port.",
          ].map((step, i) => (
            <div key={i} className="flex items-start gap-2">
              <span className="text-xs font-bold flex-shrink-0 mt-0.5" style={{ color: "var(--accent)" }}>{i + 1}.</span>
              <span className="text-xs" style={{ color: "var(--text-muted)" }}>{step}</span>
            </div>
          ))}
        </div>
      )}

      {/* Progress bar */}
      {(flashing || fwPct > 0) && (
        <div className="mb-4">
          {isFullWipe
            ? <CombinedProgressBar blPct={blPct} partPct={partPct} nvsPct={nvsPct} fwPct={fwPct} />
            : <ProgressBar label="Firmware" percent={fwPct} />
          }
        </div>
      )}

      {flashing && (
        <div className="mb-2">
          <InfoBox type="warning">
            <strong>Please do not disconnect your device or close this tab</strong> while flashing is in progress.
            This could permanently damage your device.
          </InfoBox>
        </div>
      )}

      {/* Flash log */}
      {log.length > 0 && (
        <div
          className="rounded-md border p-3 mb-4 overflow-y-auto text-xs font-mono"
          style={{ backgroundColor: "#0a0f0a", borderColor: "var(--border-secondary)", color: "#6db36d", maxHeight: 100 }}
        >
          {log.map((line, i) => <div key={i}>{line}</div>)}
          <div ref={logEndRef} />
        </div>
      )}

      <div className="flex justify-end mt-auto">
        <PrimaryButton
          onClick={handleFlash}
          disabled={!portConnected || !webSerialAvailable || flashing}
        >
          {flashing ? "Flashing… please wait" : "Start Flash →"}
        </PrimaryButton>
      </div>
    </div>
  );
}

// ─── Step 5: Done ─────────────────────────────────────────────────────────────

function StepDone({ firmware, flashType, serial, onReset }) {
  const [verifyStatus, setVerifyStatus] = useState("waiting"); // waiting | online | timeout
  const isFullWipe = flashType === FLASH_TYPE_FULL;

  useEffect(() => {
    if (!isFullWipe || !serial) { setVerifyStatus("skipped"); return; }

    let elapsed = 0;
    const interval = setInterval(async () => {
      elapsed += VERIFY_POLL_MS;
      if (elapsed > VERIFY_TIMEOUT_MS) {
        clearInterval(interval);
        setVerifyStatus("timeout");
        return;
      }
      try {
        // We poll the public status endpoint — the device heartbeat will show if it's online
        const resp = await fetch(`/api/public/cloudflash/status`);
        // We can't directly check MQTT status without auth, so we just wait for the
        // device to appear via a public heartbeat check. For now we show a friendly
        // "waiting" message and let it time out gracefully.
        // If you have a public device-online endpoint, swap it in here.
      } catch (_) {}
    }, VERIFY_POLL_MS);

    return () => clearInterval(interval);
  }, [isFullWipe, serial]);

  return (
    <div className="flex flex-col items-center justify-center h-full text-center px-4">
      {/* Success icon */}
      <div
        className="flex items-center justify-center rounded-full mb-5"
        style={{ width: 72, height: 72, backgroundColor: "var(--success-bg)", border: "2px solid var(--success)" }}
      >
        <svg className="w-9 h-9" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: "var(--success)" }}>
          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
        </svg>
      </div>

      <h2 className="text-2xl font-bold mb-2" style={{ color: "var(--text-heading)" }}>
        Flash Complete!
      </h2>
      <p className="text-sm max-w-sm mb-6" style={{ color: "var(--text-secondary)", lineHeight: 1.7 }}>
        Your <strong style={{ color: "var(--text-primary)" }}>{firmware.hw_type_label}</strong> has been
        successfully {flashType === FLASH_TYPE_FULL ? "restored to factory settings" : "updated"}.
        The device is now rebooting.
      </p>

      {isFullWipe && (
        <div
          className="rounded-lg border p-4 mb-6 text-left w-full max-w-sm space-y-2"
          style={{ backgroundColor: "var(--bg-secondary)", borderColor: "var(--border-primary)" }}
        >
          {verifyStatus === "waiting" && (
            <div className="flex items-center gap-2">
              <span className="inline-block w-2 h-2 rounded-full animate-pulse" style={{ backgroundColor: "#fb923c" }} />
              <span className="text-sm" style={{ color: "var(--text-secondary)" }}>
                Waiting for device to connect to BellCloud…
              </span>
            </div>
          )}
          {verifyStatus === "timeout" && (
            <InfoBox type="warning">
              We couldn't confirm your device connected automatically.
              This is normal — it may take a few minutes for the device to join the network.
              You can close this page.
            </InfoBox>
          )}
          {verifyStatus === "skipped" && null}
        </div>
      )}

      <div
        className="rounded-lg border p-4 mb-6 text-left w-full max-w-sm space-y-2"
        style={{ backgroundColor: "var(--bg-secondary)", borderColor: "var(--border-secondary)" }}
      >
        <p className="text-xs font-semibold uppercase tracking-wide mb-2" style={{ color: "var(--text-muted)" }}>
          What to do next
        </p>
        {[
          "You can safely disconnect the USB cable.",
          "Power cycle the device if it doesn't restart automatically.",
          "The device will reconnect to your network and BellCloud automatically.",
          "If you experience any issues, please contact BellSystems support.",
        ].map((item, i) => (
          <div key={i} className="flex items-start gap-2">
            <span className="text-xs font-bold mt-0.5 flex-shrink-0" style={{ color: "var(--accent)" }}>{i + 1}.</span>
            <span className="text-xs" style={{ color: "var(--text-muted)" }}>{item}</span>
          </div>
        ))}
      </div>

      <SecondaryButton onClick={onReset}>
        Flash another device
      </SecondaryButton>
    </div>
  );
}

// ─── Disabled / Feature Gate Screen ──────────────────────────────────────────

function DisabledScreen() {
  return (
    <div className="min-h-screen flex items-center justify-center px-4" style={{ backgroundColor: "var(--bg-primary)" }}>
      <div
        className="rounded-xl border p-10 text-center max-w-md w-full"
        style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
      >
        <div
          className="flex items-center justify-center rounded-full mx-auto mb-5"
          style={{ width: 64, height: 64, backgroundColor: "var(--bg-card-hover)", border: "2px solid var(--border-primary)" }}
        >
          <svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: "var(--text-muted)" }}>
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
          </svg>
        </div>
        <h1 className="text-xl font-bold mb-2" style={{ color: "var(--text-heading)" }}>
          CloudFlash is Currently Unavailable
        </h1>
        <p className="text-sm" style={{ color: "var(--text-secondary)", lineHeight: 1.7 }}>
          The CloudFlash service is temporarily disabled. Please check back later or contact
          BellSystems support if you need urgent assistance with your device.
        </p>
      </div>
    </div>
  );
}

// ─── Scroll Unlocker ─────────────────────────────────────────────────────────
// The app shell sets html/body/#root to overflow:hidden.
// This wrapper temporarily lifts that so CloudFlash can scroll freely.

function ScrollUnlocker({ children }) {
  useEffect(() => {
    const targets = [document.documentElement, document.body, document.getElementById("root")];
    const prev = targets.map((el) => el && el.style.overflow);
    targets.forEach((el) => el && (el.style.overflow = "auto"));
    return () => targets.forEach((el, i) => el && (el.style.overflow = prev[i] || ""));
  }, []);
  return children;
}

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

export default function CloudFlashPage() {
  const [gateLoading, setGateLoading] = useState(true);
  const [enabled, setEnabled]         = useState(false);

  // Wizard state
  const [step, setStep]           = useState(1);
  const [firmware, setFirmware]   = useState(null);   // { hw_type, hw_type_label, channel, version, download_url }
  const [flashType, setFlashType] = useState(null);   // FLASH_TYPE_FULL | FLASH_TYPE_FW_ONLY
  const [serial, setSerial]       = useState(null);   // user-provided serial (full wipe only)
  const [nvsSchema, setNvsSchema] = useState(null);   // NVS_SCHEMA_NEW | NVS_SCHEMA_LEGACY

  // Check feature gate
  useEffect(() => {
    fetch("/api/public/cloudflash/status")
      .then((r) => r.json())
      .then((data) => setEnabled(data.enabled ?? false))
      .catch(() => setEnabled(false))
      .finally(() => setGateLoading(false));
  }, []);

  const reset = () => {
    setStep(1);
    setFirmware(null);
    setFlashType(null);
    setSerial(null);
    setNvsSchema(null);
  };

  if (gateLoading) {
    return (
      <div className="min-h-screen flex items-center justify-center" style={{ backgroundColor: "var(--bg-primary)" }}>
        <p className="text-sm" style={{ color: "var(--text-muted)" }}>Loading…</p>
      </div>
    );
  }

  if (!enabled) return <DisabledScreen />;

  return (
    <ScrollUnlocker>
    <div
      className="flex flex-col items-center justify-center py-10 px-4"
      style={{ minHeight: "100vh", backgroundColor: "var(--bg-primary)" }}
    >
      {/* Brand header */}
      <div className="mb-6">
        <img src={cloudflashLogo} alt="CloudFlash" style={{ height: 100, objectFit: "contain" }} />
      </div>

      {/* Step indicator */}
      <div className="w-full max-w-4xl">
        <StepIndicator current={step} />
      </div>

      {/* Wizard card */}
      <div
        className="w-full max-w-4xl rounded-xl border p-8"
        style={{
          backgroundColor: "var(--bg-card)",
          borderColor: "var(--border-primary)",
          minHeight: CARD_MIN_HEIGHT,
        }}
      >
        {step === 1 && (
          <StepWelcome onNext={() => setStep(2)} />
        )}
        {step === 2 && (
          <StepSelectHardware
            onNext={(fw) => { setFirmware(fw); setStep(3); }}
          />
        )}
        {step === 3 && firmware && (
          <StepSelectFlashType
            firmware={firmware}
            onNext={({ flashType: ft, serial: sn, nvsSchema: ns }) => {
              setFlashType(ft);
              setSerial(sn);
              setNvsSchema(ns);
              setStep(4);
            }}
          />
        )}
        {step === 4 && firmware && flashType && (
          <StepFlash
            firmware={firmware}
            flashType={flashType}
            serial={serial}
            nvsSchema={nvsSchema}
            onDone={() => setStep(5)}
          />
        )}
        {step === 5 && firmware && flashType && (
          <StepDone
            firmware={firmware}
            flashType={flashType}
            serial={serial}
            onReset={reset}
          />
        )}
      </div>

      {/* Footer */}
      <p className="text-xs mt-6" style={{ color: "var(--text-muted)" }}>
        © {new Date().getFullYear()} BellSystems · CloudFlash v1.0
      </p>
    </div>
    </ScrollUnlocker>
  );
}
