import { useState, useEffect, useRef, useCallback } from "react";
import { createPortal } from "react-dom";
import { useAuth } from "../auth/AuthContext";
import api from "../api/client";

// ── Constants ─────────────────────────────────────────────────────────────────

const KNOWN_BOARD_TYPES = [
  { value: "vesper",      label: "Vesper",       family: "vesper"  },
  { value: "vesper_plus", label: "Vesper+",      family: "vesper"  },
  { value: "vesper_pro",  label: "Vesper Pro",   family: "vesper"  },
  { value: "agnus",       label: "Agnus",        family: "agnus"   },
  { value: "agnus_mini",  label: "Agnus Mini",   family: "agnus"   },
  { value: "chronos",     label: "Chronos",      family: "chronos" },
  { value: "chronos_pro", label: "Chronos Pro",  family: "chronos" },
];

const BOARD_LABELS = Object.fromEntries(KNOWN_BOARD_TYPES.map((b) => [b.value, b.label]));

const FAMILY_ACCENT = {
  vesper:  "#3b82f6",
  agnus:   "#f59e0b",
  chronos: "#ef4444",
  bespoke: "#a855f7",
};

// ── Helpers ───────────────────────────────────────────────────────────────────

function formatBytes(bytes) {
  if (!bytes) return "—";
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
}

function formatDate(iso) {
  if (!iso) return null;
  try {
    return new Date(iso).toLocaleString("en-US", {
      year: "numeric", month: "short", day: "numeric",
      hour: "2-digit", minute: "2-digit",
    });
  } catch {
    return iso;
  }
}

function boardLabel(hw_type, is_bespoke) {
  if (is_bespoke) return hw_type;
  return BOARD_LABELS[hw_type] || hw_type;
}

function familyOf(hw_type, is_bespoke) {
  if (is_bespoke) return "bespoke";
  const bt = KNOWN_BOARD_TYPES.find((b) => b.value === hw_type);
  return bt?.family ?? "vesper";
}

// ── Icons ─────────────────────────────────────────────────────────────────────

function IconCheck() {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
      <polyline points="20 6 9 17 4 12" />
    </svg>
  );
}

function IconMissing() {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
      <circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="12" /><line x1="12" y1="16" x2="12.01" y2="16" />
    </svg>
  );
}

function IconTrash() {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <polyline points="3 6 5 6 21 6" /><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
      <path d="M10 11v6" /><path d="M14 11v6" /><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
    </svg>
  );
}

function IconUpload() {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="17 8 12 3 7 8" /><line x1="12" y1="3" x2="12" y2="15" />
    </svg>
  );
}

function IconNote() {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" />
      <line x1="16" y1="13" x2="8" y2="13" /><line x1="16" y1="17" x2="8" y2="17" />
    </svg>
  );
}

function IconRefresh() {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <polyline points="23 4 23 10 17 10" /><polyline points="1 20 1 14 7 14" />
      <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" />
    </svg>
  );
}

// ── Asset file pill (used inside each board card) ────────────────────────────

function AssetPill({ label, info, canDelete, onDelete, onUpload }) {
  const exists = info?.exists;
  return (
    <div
      style={{
        display: "flex", alignItems: "center", gap: "0.5rem",
        padding: "0.375rem 0.625rem",
        borderRadius: "0.375rem",
        border: `1px solid ${exists ? "var(--border-secondary)" : "var(--danger)"}`,
        backgroundColor: exists ? "var(--bg-secondary)" : "var(--danger-bg)",
        minWidth: 0,
      }}
    >
      <span style={{ color: exists ? "var(--success-text)" : "var(--danger-text)", flexShrink: 0 }}>
        {exists ? <IconCheck /> : <IconMissing />}
      </span>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div className="font-mono" style={{ fontSize: "0.7rem", color: exists ? "var(--text-secondary)" : "var(--danger-text)", fontWeight: 600 }}>
          {label}
        </div>
        {exists ? (
          <div style={{ fontSize: "0.65rem", color: "var(--text-muted)", marginTop: "1px" }}>
            {formatBytes(info.size_bytes)} · {formatDate(info.uploaded_at)}
          </div>
        ) : (
          <div style={{ fontSize: "0.65rem", color: "var(--danger-text)", marginTop: "1px", opacity: 0.8 }}>
            Not uploaded
          </div>
        )}
      </div>
      <div style={{ display: "flex", gap: "0.25rem", flexShrink: 0 }}>
        <button
          onClick={onUpload}
          title={exists ? "Re-upload" : "Upload"}
          className="flex items-center justify-center cursor-pointer hover:opacity-80 transition-opacity rounded"
          style={{
            width: "24px", height: "24px",
            backgroundColor: "var(--bg-card-hover)",
            border: "1px solid var(--border-primary)",
            color: "var(--text-muted)",
          }}
        >
          <IconUpload />
        </button>
        {exists && canDelete && (
          <button
            onClick={onDelete}
            title="Delete"
            className="flex items-center justify-center cursor-pointer hover:opacity-80 transition-opacity rounded"
            style={{
              width: "24px", height: "24px",
              backgroundColor: "var(--danger-bg)",
              border: "1px solid var(--danger)",
              color: "var(--danger-text)",
            }}
          >
            <IconTrash />
          </button>
        )}
      </div>
    </div>
  );
}

// ── Inline Upload Modal ───────────────────────────────────────────────────────

function UploadModal({ hwType, assetName, onClose, onSaved }) {
  const [file, setFile] = useState(null);
  const [uploading, setUploading] = useState(false);
  const [error, setError] = useState("");
  const fileInputRef = useRef(null);

  const handleUpload = async () => {
    if (!file) return;
    setError(""); setUploading(true);
    try {
      const formData = new FormData();
      formData.append("file", file);
      const token = localStorage.getItem("access_token");
      const res = await fetch(`/api/manufacturing/flash-assets/${hwType}/${assetName}`, {
        method: "POST",
        headers: { Authorization: `Bearer ${token}` },
        body: formData,
      });
      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        throw new Error(err.detail || "Upload failed");
      }
      onSaved();
    } catch (err) {
      setError(err.message);
    } finally {
      setUploading(false);
    }
  };

  return (
    <div
      className="fixed inset-0 flex items-center justify-center z-50"
      style={{ backgroundColor: "rgba(0,0,0,0.6)" }}
      onClick={onClose}
    >
      <div
        className="rounded-lg border w-full mx-4 flex flex-col"
        style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)", maxWidth: "480px" }}
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-center justify-between px-5 pt-4 pb-3" style={{ borderBottom: "1px solid var(--border-secondary)" }}>
          <div>
            <h3 className="text-sm font-semibold" style={{ color: "var(--text-heading)" }}>
              Upload {assetName}
            </h3>
            <p className="text-xs mt-0.5" style={{ color: "var(--text-muted)" }}>
              Board: <span className="font-mono">{hwType}</span> — overwrites existing file
            </p>
          </div>
          <button onClick={onClose} className="text-lg leading-none hover:opacity-70 cursor-pointer" style={{ color: "var(--text-muted)" }}>✕</button>
        </div>

        <div className="px-5 py-4" style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
          {error && (
            <div className="text-xs rounded-md p-3 border" style={{ backgroundColor: "var(--danger-bg)", borderColor: "var(--danger)", color: "var(--danger-text)" }}>
              {error}
            </div>
          )}
          <div
            onClick={() => fileInputRef.current?.click()}
            onDragOver={(e) => e.preventDefault()}
            onDrop={(e) => {
              e.preventDefault();
              const f = e.dataTransfer.files[0];
              if (f && f.name.endsWith(".bin")) setFile(f);
              else if (f) setError("Only .bin files are accepted.");
            }}
            style={{
              display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
              gap: "0.5rem", padding: "2rem 1rem",
              border: `2px dashed ${file ? "var(--btn-primary)" : "var(--border-input)"}`,
              borderRadius: "0.625rem",
              backgroundColor: file ? "var(--badge-blue-bg)" : "var(--bg-input)",
              cursor: "pointer", transition: "all 0.15s ease",
            }}
          >
            <input
              ref={fileInputRef}
              type="file"
              accept=".bin"
              onChange={(e) => {
                const f = e.target.files[0];
                if (f && !f.name.endsWith(".bin")) { setError("Only .bin files are accepted."); return; }
                setFile(f || null); setError("");
              }}
              style={{ display: "none" }}
            />
            {file ? (
              <>
                <span style={{ color: "var(--btn-primary)" }}><IconCheck /></span>
                <span style={{ fontSize: "0.8rem", fontWeight: 600, color: "var(--badge-blue-text)", textAlign: "center", wordBreak: "break-all" }}>{file.name}</span>
                <span style={{ fontSize: "0.7rem", color: "var(--text-muted)" }}>{formatBytes(file.size)}</span>
              </>
            ) : (
              <>
                <span style={{ color: "var(--text-muted)" }}><IconUpload /></span>
                <span style={{ fontSize: "0.8rem", color: "var(--text-muted)", textAlign: "center" }}>
                  Click or drop <span className="font-mono">{assetName}</span>
                </span>
              </>
            )}
          </div>
        </div>

        <div className="flex items-center justify-end gap-3 px-5 py-3" style={{ borderTop: "1px solid var(--border-secondary)" }}>
          <button onClick={onClose} className="px-4 py-2 text-sm rounded-md hover:opacity-80 cursor-pointer" style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-secondary)" }}>
            Cancel
          </button>
          <button
            onClick={handleUpload}
            disabled={!file || uploading}
            className="px-5 py-2 text-sm rounded-md font-medium hover:opacity-90 transition-opacity cursor-pointer disabled:opacity-50"
            style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
          >
            {uploading ? "Uploading…" : "Upload"}
          </button>
        </div>
      </div>
    </div>
  );
}

// ── Note Editor Modal ─────────────────────────────────────────────────────────

function NoteModal({ hwType, currentNote, onClose, onSaved }) {
  const [note, setNote] = useState(currentNote || "");
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState("");

  const handleSave = async () => {
    setSaving(true); setError("");
    try {
      await api.put(`/manufacturing/flash-assets/${hwType}/note`, { note });
      onSaved(note);
    } catch (err) {
      setError(err.message || "Failed to save note");
    } finally {
      setSaving(false);
    }
  };

  return (
    <div
      className="fixed inset-0 flex items-center justify-center z-50"
      style={{ backgroundColor: "rgba(0,0,0,0.6)" }}
      onClick={onClose}
    >
      <div
        className="rounded-lg border w-full mx-4"
        style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)", maxWidth: "480px" }}
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-center justify-between px-5 pt-4 pb-3" style={{ borderBottom: "1px solid var(--border-secondary)" }}>
          <div>
            <h3 className="text-sm font-semibold" style={{ color: "var(--text-heading)" }}>Asset Note</h3>
            <p className="text-xs mt-0.5" style={{ color: "var(--text-muted)" }}>
              <span className="font-mono">{hwType}</span> — stored as <span className="font-mono">note.txt</span> alongside the binaries
            </p>
          </div>
          <button onClick={onClose} className="text-lg leading-none hover:opacity-70 cursor-pointer" style={{ color: "var(--text-muted)" }}>✕</button>
        </div>
        <div className="px-5 py-4">
          {error && (
            <div className="text-xs rounded-md p-3 mb-3 border" style={{ backgroundColor: "var(--danger-bg)", borderColor: "var(--danger)", color: "var(--danger-text)" }}>
              {error}
            </div>
          )}
          <textarea
            value={note}
            onChange={(e) => setNote(e.target.value)}
            placeholder="e.g. Built from PlatformIO env:vesper_plus_v2, commit abc1234. Partition layout changed — reflash required."
            rows={5}
            className="w-full px-3 py-2 rounded-md text-sm border"
            style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-input)", color: "var(--text-primary)", resize: "vertical" }}
          />
          <p className="text-xs mt-1.5" style={{ color: "var(--text-muted)" }}>Leave blank to clear the note.</p>
        </div>
        <div className="flex items-center justify-end gap-3 px-5 py-3" style={{ borderTop: "1px solid var(--border-secondary)" }}>
          <button onClick={onClose} className="px-4 py-2 text-sm rounded-md hover:opacity-80 cursor-pointer" style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-secondary)" }}>
            Cancel
          </button>
          <button
            onClick={handleSave}
            disabled={saving}
            className="px-5 py-2 text-sm rounded-md font-medium hover:opacity-90 transition-opacity cursor-pointer disabled:opacity-50"
            style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
          >
            {saving ? "Saving…" : "Save Note"}
          </button>
        </div>
      </div>
    </div>
  );
}

// ── Add Bespoke Board Modal ───────────────────────────────────────────────────

function AddBespokeModal({ existingUids, onClose, onSaved }) {
  const [uid,         setUid]         = useState("");
  const [bootFile,    setBootFile]    = useState(null);
  const [partFile,    setPartFile]    = useState(null);
  const [uploading,   setUploading]   = useState(false);
  const [error,       setError]       = useState("");
  const bootRef = useRef(null);
  const partRef = useRef(null);

  const uidSlug = uid.trim().toLowerCase().replace(/\s+/g, "-");
  const isValidUid = /^[a-z0-9][a-z0-9._-]{0,126}$/.test(uidSlug);
  const isKnown = KNOWN_BOARD_TYPES.some((b) => b.value === uidSlug);
  const isDuplicate = existingUids.includes(uidSlug);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!isValidUid)    { setError("UID must be lowercase alphanumeric with hyphens/dots only."); return; }
    if (isKnown)        { setError("That name matches a standard board type. Use a unique bespoke UID."); return; }
    if (isDuplicate)    { setError("A bespoke board with that UID already exists."); return; }
    if (!bootFile && !partFile) { setError("Upload at least one file."); return; }
    setError(""); setUploading(true);
    const token = localStorage.getItem("access_token");
    try {
      for (const [asset, file] of [["bootloader.bin", bootFile], ["partitions.bin", partFile]]) {
        if (!file) continue;
        const fd = new FormData();
        fd.append("file", file);
        const res = await fetch(`/api/manufacturing/flash-assets/${uidSlug}/${asset}`, {
          method: "POST",
          headers: { Authorization: `Bearer ${token}` },
          body: fd,
        });
        if (!res.ok) {
          const err = await res.json().catch(() => ({}));
          throw new Error(err.detail || `Failed to upload ${asset}`);
        }
      }
      onSaved();
    } catch (err) {
      setError(err.message);
    } finally {
      setUploading(false);
    }
  };

  const FilePicker = ({ label, file, setFile, inputRef }) => (
    <div>
      <label className="block text-xs font-medium mb-1" style={{ color: "var(--text-muted)" }}>{label}</label>
      <div
        onClick={() => inputRef.current?.click()}
        onDragOver={(e) => e.preventDefault()}
        onDrop={(e) => {
          e.preventDefault();
          const f = e.dataTransfer.files[0];
          if (f && f.name.endsWith(".bin")) setFile(f);
          else if (f) setError("Only .bin files are accepted.");
        }}
        style={{
          display: "flex", alignItems: "center", gap: "0.5rem",
          padding: "0.5rem 0.75rem",
          border: `2px dashed ${file ? "var(--btn-primary)" : "var(--border-input)"}`,
          borderRadius: "0.5rem",
          backgroundColor: file ? "var(--badge-blue-bg)" : "var(--bg-input)",
          cursor: "pointer", transition: "all 0.15s ease",
        }}
      >
        <input ref={inputRef} type="file" accept=".bin" style={{ display: "none" }}
          onChange={(e) => {
            const f = e.target.files[0];
            if (f && !f.name.endsWith(".bin")) { setError("Only .bin files are accepted."); return; }
            setFile(f || null); setError("");
          }}
        />
        <span style={{ color: file ? "var(--btn-primary)" : "var(--text-muted)", flexShrink: 0 }}>
          {file ? <IconCheck /> : <IconUpload />}
        </span>
        <span className="font-mono" style={{ fontSize: "0.72rem", color: file ? "var(--badge-blue-text)" : "var(--text-muted)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
          {file ? `${file.name} (${formatBytes(file.size)})` : `click or drop ${label}`}
        </span>
      </div>
    </div>
  );

  return (
    <div
      className="fixed inset-0 flex items-center justify-center z-50"
      style={{ backgroundColor: "rgba(0,0,0,0.6)" }}
      onClick={onClose}
    >
      <div
        className="rounded-lg border w-full mx-4 flex flex-col"
        style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)", maxWidth: "480px" }}
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-center justify-between px-5 pt-4 pb-3" style={{ borderBottom: "1px solid var(--border-secondary)" }}>
          <div>
            <h3 className="text-sm font-semibold" style={{ color: "var(--text-heading)" }}>Add Bespoke Board</h3>
            <p className="text-xs mt-0.5" style={{ color: "var(--text-muted)" }}>
              Create a new bespoke flash asset set with a unique UID.
            </p>
          </div>
          <button onClick={onClose} className="text-lg leading-none hover:opacity-70 cursor-pointer" style={{ color: "var(--text-muted)" }}>✕</button>
        </div>

        <form onSubmit={handleSubmit}>
          <div className="px-5 py-4" style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
            {error && (
              <div className="text-xs rounded-md p-3 border" style={{ backgroundColor: "var(--danger-bg)", borderColor: "var(--danger)", color: "var(--danger-text)" }}>
                {error}
              </div>
            )}
            <div>
              <label className="block text-xs font-medium mb-1" style={{ color: "var(--text-muted)" }}>
                Bespoke UID <span style={{ color: "var(--danger-text)" }}>*</span>
              </label>
              <input
                type="text"
                value={uid}
                onChange={(e) => { setUid(e.target.value); setError(""); }}
                placeholder="e.g. client-athens-v1"
                required
                className="w-full px-3 py-2 rounded-md text-sm border font-mono"
                style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-input)", color: "var(--text-primary)" }}
              />
              {uidSlug && (
                <p className="text-xs mt-1 font-mono" style={{ color: isValidUid && !isKnown && !isDuplicate ? "var(--text-muted)" : "var(--danger-text)" }}>
                  {isDuplicate ? "Already exists" : isKnown ? "Matches a standard board type" : !isValidUid ? "Invalid format" : `→ ${uidSlug}`}
                </p>
              )}
            </div>
            <FilePicker label="bootloader.bin" file={bootFile} setFile={setBootFile} inputRef={bootRef} />
            <FilePicker label="partitions.bin" file={partFile} setFile={setPartFile} inputRef={partRef} />
          </div>

          <div className="flex items-center justify-end gap-3 px-5 py-3" style={{ borderTop: "1px solid var(--border-secondary)" }}>
            <button type="button" onClick={onClose} className="px-4 py-2 text-sm rounded-md hover:opacity-80 cursor-pointer" style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-secondary)" }}>
              Cancel
            </button>
            <button
              type="submit"
              disabled={uploading || !uidSlug}
              className="px-5 py-2 text-sm rounded-md font-medium hover:opacity-90 transition-opacity cursor-pointer disabled:opacity-50"
              style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
            >
              {uploading ? "Uploading…" : "Add Board"}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

// ── Delete Confirm Modal ──────────────────────────────────────────────────────

function DeleteConfirmModal({ hwType, assetName, onClose, onConfirmed }) {
  const [deleting, setDeleting] = useState(false);
  const [error, setError] = useState("");

  const handleDelete = async () => {
    setDeleting(true); setError("");
    try {
      const token = localStorage.getItem("access_token");
      const res = await fetch(`/api/manufacturing/flash-assets/${hwType}/${assetName}`, {
        method: "DELETE",
        headers: { Authorization: `Bearer ${token}` },
      });
      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        throw new Error(err.detail || "Delete failed");
      }
      onConfirmed();
    } catch (err) {
      setError(err.message);
      setDeleting(false);
    }
  };

  return (
    <div
      className="fixed inset-0 flex items-center justify-center z-50"
      style={{ backgroundColor: "rgba(0,0,0,0.6)" }}
      onClick={onClose}
    >
      <div
        className="rounded-lg border p-6 max-w-sm w-full mx-4"
        style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
        onClick={(e) => e.stopPropagation()}
      >
        <h3 className="text-sm font-semibold mb-1" style={{ color: "var(--text-heading)" }}>Delete Flash Asset</h3>
        <p className="text-sm mb-1" style={{ color: "var(--text-secondary)" }}>
          Delete <span className="font-mono" style={{ color: "var(--text-primary)" }}>{assetName}</span> for{" "}
          <span className="font-mono" style={{ color: "var(--text-primary)" }}>{hwType}</span>?
        </p>
        <p className="text-xs mb-5" style={{ color: "var(--text-muted)" }}>
          This cannot be undone. Flashing will fail until you re-upload this file.
        </p>
        {error && (
          <div className="text-xs rounded-md p-2 mb-3 border" style={{ backgroundColor: "var(--danger-bg)", borderColor: "var(--danger)", color: "var(--danger-text)" }}>
            {error}
          </div>
        )}
        <div className="flex gap-3">
          <button
            onClick={handleDelete}
            disabled={deleting}
            className="px-4 py-2 text-sm rounded-md hover:opacity-90 transition-opacity cursor-pointer disabled:opacity-50"
            style={{ backgroundColor: "var(--danger-btn)", color: "var(--text-white)" }}
          >
            {deleting ? "Deleting…" : "Delete"}
          </button>
          <button onClick={onClose} className="px-4 py-2 text-sm rounded-md hover:opacity-80 cursor-pointer" style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-secondary)" }}>
            Cancel
          </button>
        </div>
      </div>
    </div>
  );
}

// ── Board Card ────────────────────────────────────────────────────────────────

function BoardCard({ entry, canDelete, canEdit, onUpload, onDelete, onNote, onRefresh }) {
  const { hw_type, bootloader, partitions, note, is_bespoke } = entry;
  const family = familyOf(hw_type, is_bespoke);
  const accent = FAMILY_ACCENT[family] || FAMILY_ACCENT.vesper;
  const label = boardLabel(hw_type, is_bespoke);

  const hasBootloader = bootloader?.exists;
  const hasPartitions = partitions?.exists;
  const hasAll = hasBootloader && hasPartitions;
  const hasNone = !hasBootloader && !hasPartitions;

  const statusColor = hasAll ? "var(--success-text)" : hasNone ? "var(--danger-text)" : "#f59e0b";
  const statusBg = hasAll ? "var(--success-bg)" : hasNone ? "var(--danger-bg)" : "#2a1a00";
  const statusLabel = hasAll ? "Ready" : hasNone ? "Missing" : "Partial";

  return (
    <div
      className="rounded-lg border"
      style={{
        backgroundColor: "var(--bg-card)",
        borderColor: "var(--border-primary)",
        borderLeft: `3px solid ${accent}`,
        display: "flex", flexDirection: "column",
        gap: "0",
      }}
    >
      {/* Header */}
      <div className="px-4 pt-3 pb-2" style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: "0.5rem" }}>
        <div style={{ display: "flex", alignItems: "center", gap: "0.5rem", minWidth: 0, flexWrap: "wrap" }}>
          <span className="text-sm font-semibold" style={{ color: "var(--text-heading)", lineHeight: 1 }}>{label}</span>
          {is_bespoke && (
            <span className="text-xs px-1.5 py-0.5 rounded font-medium" style={{ backgroundColor: "#2a0a3a", color: "#a855f7", border: "1px solid #a855f7", lineHeight: 1 }}>
              bespoke
            </span>
          )}
          <span style={{ color: "var(--text-muted)", opacity: 0.4, lineHeight: 1, fontSize: "0.75rem" }}>·</span>
          <span className="font-mono text-xs" style={{ color: "var(--text-muted)", opacity: 0.7, lineHeight: 1 }}>{hw_type}</span>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: "0.5rem", flexShrink: 0 }}>
          <span
            className="text-xs px-2 py-0.5 rounded-full font-medium"
            style={{ backgroundColor: statusBg, color: statusColor }}
          >
            {statusLabel}
          </span>
          {canEdit && (
            <button
              onClick={() => onNote(entry)}
              title={note ? "Edit note" : "Add note"}
              className="flex items-center gap-1 px-2 py-1 text-xs rounded cursor-pointer hover:opacity-80 transition-opacity"
              style={{
                backgroundColor: note ? "var(--bg-secondary)" : "transparent",
                border: `1px solid ${note ? "var(--border-secondary)" : "transparent"}`,
                color: note ? "var(--text-secondary)" : "var(--text-muted)",
              }}
            >
              <IconNote />
              {note ? "Note" : "Add note"}
            </button>
          )}
        </div>
      </div>

      {/* Note preview */}
      {note && (
        <div className="px-4 pb-2">
          <p className="text-xs" style={{ color: "var(--text-muted)", fontStyle: "italic", lineHeight: 1.5 }}>
            {note}
          </p>
        </div>
      )}

      {/* Asset pills */}
      <div className="px-4 pb-3" style={{ display: "flex", flexDirection: "column", gap: "0.4rem" }}>
        <AssetPill
          label="bootloader.bin"
          info={bootloader}
          canDelete={canDelete}
          onUpload={() => onUpload(hw_type, "bootloader.bin")}
          onDelete={() => onDelete(hw_type, "bootloader.bin")}
        />
        <AssetPill
          label="partitions.bin"
          info={partitions}
          canDelete={canDelete}
          onUpload={() => onUpload(hw_type, "partitions.bin")}
          onDelete={() => onDelete(hw_type, "partitions.bin")}
        />
      </div>
    </div>
  );
}

// ── Main Component ────────────────────────────────────────────────────────────

export default function FlashAssetManager({ onClose }) {
  const { hasPermission } = useAuth();
  const canDelete = hasPermission("manufacturing", "delete");
  const canEdit   = hasPermission("manufacturing", "edit");

  const [assets,   setAssets]   = useState([]);
  const [loading,  setLoading]  = useState(true);
  const [error,    setError]    = useState("");
  const [filter,   setFilter]   = useState("all"); // "all" | "ready" | "partial" | "missing"

  // Modal state
  const [uploadTarget,    setUploadTarget]    = useState(null); // { hwType, assetName }
  const [deleteTarget,    setDeleteTarget]    = useState(null); // { hwType, assetName }
  const [noteTarget,      setNoteTarget]      = useState(null); // entry
  const [showAddBespoke,  setShowAddBespoke]  = useState(false);
  const canAdd = hasPermission("manufacturing", "add");

  const fetchAssets = useCallback(async () => {
    setLoading(true); setError("");
    try {
      const data = await api.get("/manufacturing/flash-assets");
      setAssets(data.assets || []);
    } catch (err) {
      setError(err.message || "Failed to load flash assets");
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => { fetchAssets(); }, [fetchAssets]);

  // Filter
  const filteredAssets = assets.filter((e) => {
    const hasAll  = e.bootloader?.exists && e.partitions?.exists;
    const hasNone = !e.bootloader?.exists && !e.partitions?.exists;
    if (filter === "ready")   return hasAll;
    if (filter === "missing") return hasNone;
    if (filter === "partial") return !hasAll && !hasNone;
    return true;
  });

  // Stats
  const readyCount   = assets.filter((e) => e.bootloader?.exists && e.partitions?.exists).length;
  const missingCount = assets.filter((e) => !e.bootloader?.exists && !e.partitions?.exists).length;
  const partialCount = assets.length - readyCount - missingCount;

  return (
    <div
      className="fixed inset-0 flex items-center justify-center z-50"
      style={{ backgroundColor: "rgba(0,0,0,0.7)", padding: "0 1rem" }}
      onClick={onClose}
    >
      <div
        className="rounded-lg border w-full flex flex-col"
        style={{
          backgroundColor: "var(--bg-card)",
          borderColor: "var(--border-primary)",
          maxWidth: "960px",
          height: "70vh",
        }}
        onClick={(e) => e.stopPropagation()}
      >
        {/* ── Header ── */}
        <div
          className="flex items-start justify-between px-6 pt-5 pb-4"
          style={{ borderBottom: "1px solid var(--border-secondary)", flexShrink: 0 }}
        >
          <div>
            <h2 className="text-base font-semibold" style={{ color: "var(--text-heading)" }}>
              Flash Asset Manager
            </h2>
            <p className="text-xs mt-0.5" style={{ color: "var(--text-muted)" }}>
              Bootloader and partition table binaries per board type — PlatformIO build artifacts flashed at 0x1000 and 0x8000.
            </p>
            {/* Stats */}
            {!loading && (
              <div className="flex gap-3 mt-2">
                {[
                  { label: "Ready",   count: readyCount,   color: "var(--success-text)",  bg: "var(--success-bg)", key: "ready"   },
                  { label: "Partial", count: partialCount, color: "#f59e0b",              bg: "#2a1a00",            key: "partial" },
                  { label: "Missing", count: missingCount, color: "var(--danger-text)",   bg: "var(--danger-bg)",  key: "missing" },
                ].map(({ label, count, color, bg, key }) => count > 0 && (
                  <button
                    key={key}
                    onClick={() => setFilter(filter === key ? "all" : key)}
                    className="text-xs px-2 py-0.5 rounded-full font-medium cursor-pointer hover:opacity-80 transition-opacity"
                    style={{
                      backgroundColor: bg, color,
                      outline: filter === key ? `2px solid ${color}` : "none",
                      outlineOffset: "1px",
                    }}
                  >
                    {count} {label}
                  </button>
                ))}
                {filter !== "all" && (
                  <button
                    onClick={() => setFilter("all")}
                    className="text-xs px-2 py-0.5 cursor-pointer hover:opacity-80 transition-opacity"
                    style={{ color: "var(--text-muted)" }}
                  >
                    Show all
                  </button>
                )}
              </div>
            )}
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: "0.5rem", flexShrink: 0 }}>
            <button
              onClick={fetchAssets}
              disabled={loading}
              title="Refresh"
              className="flex items-center gap-1.5 px-3 py-2 text-sm rounded-md cursor-pointer hover:opacity-80 transition-opacity disabled:opacity-50"
              style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-muted)", border: "1px solid var(--border-primary)" }}
            >
              <IconRefresh />
              Refresh
            </button>
            {canAdd && (
              <button
                onClick={() => setShowAddBespoke(true)}
                className="flex items-center gap-1.5 px-3 py-2 text-sm rounded-md font-medium cursor-pointer hover:opacity-90 transition-opacity"
                style={{ backgroundColor: "#2a0a3a", color: "#a855f7", border: "1px solid #a855f7" }}
              >
                + Bespoke Board
              </button>
            )}
            <button
              onClick={onClose}
              className="text-lg leading-none hover:opacity-70 cursor-pointer"
              style={{ color: "var(--text-muted)", marginLeft: "0.25rem" }}
            >
              ✕
            </button>
          </div>
        </div>

        {/* ── Body ── */}
        <div className="flex-1 overflow-y-auto p-6">
          {error && (
            <div className="text-sm rounded-md p-3 mb-4 border" style={{ backgroundColor: "var(--danger-bg)", borderColor: "var(--danger)", color: "var(--danger-text)" }}>
              {error}
            </div>
          )}

          {loading ? (
            <div className="flex items-center justify-center py-16" style={{ color: "var(--text-muted)", fontSize: "0.875rem" }}>
              Loading…
            </div>
          ) : filteredAssets.length === 0 ? (
            <div className="flex items-center justify-center py-16" style={{ color: "var(--text-muted)", fontSize: "0.875rem" }}>
              No assets match the current filter.
            </div>
          ) : (
            <>
              {/* Standard boards section */}
              {(() => {
                const standard = filteredAssets.filter((e) => !e.is_bespoke);
                if (standard.length === 0) return null;
                return (
                  <div style={{ marginBottom: "1.5rem" }}>
                    <p className="text-xs font-semibold uppercase mb-3" style={{ color: "var(--text-muted)", letterSpacing: "0.06em" }}>
                      Standard Boards
                    </p>
                    <div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
                      {standard.map((entry) => (
                        <BoardCard
                          key={entry.hw_type}
                          entry={entry}
                          canDelete={canDelete}
                          canEdit={canEdit}
                          onUpload={(hwType, asset) => setUploadTarget({ hwType, assetName: asset })}
                          onDelete={(hwType, asset) => setDeleteTarget({ hwType, assetName: asset })}
                          onNote={(e) => setNoteTarget(e)}
                          onRefresh={fetchAssets}
                        />
                      ))}
                    </div>
                  </div>
                );
              })()}

              {/* Bespoke boards section */}
              {(() => {
                const bespoke = filteredAssets.filter((e) => e.is_bespoke);
                if (bespoke.length === 0) return null;
                return (
                  <div>
                    <p className="text-xs font-semibold uppercase mb-3" style={{ color: "var(--text-muted)", letterSpacing: "0.06em" }}>
                      Bespoke Devices
                    </p>
                    <div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
                      {bespoke.map((entry) => (
                        <BoardCard
                          key={entry.hw_type}
                          entry={entry}
                          canDelete={canDelete}
                          canEdit={canEdit}
                          onUpload={(hwType, asset) => setUploadTarget({ hwType, assetName: asset })}
                          onDelete={(hwType, asset) => setDeleteTarget({ hwType, assetName: asset })}
                          onNote={(e) => setNoteTarget(e)}
                          onRefresh={fetchAssets}
                        />
                      ))}
                    </div>
                  </div>
                );
              })()}
            </>
          )}
        </div>

        {/* ── Footer ── */}
        <div
          className="flex items-center justify-between px-6 py-3"
          style={{ borderTop: "1px solid var(--border-secondary)", flexShrink: 0 }}
        >
          <p className="text-xs" style={{ color: "var(--text-muted)" }}>
            {assets.length} board type{assets.length !== 1 ? "s" : ""} tracked
            {filteredAssets.length !== assets.length ? ` · ${filteredAssets.length} shown` : ""}
          </p>
          <button
            onClick={onClose}
            className="px-4 py-2 text-sm rounded-md hover:opacity-80 cursor-pointer"
            style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-secondary)" }}
          >
            Close
          </button>
        </div>
      </div>

      {/* ── Modals ── */}
      {uploadTarget && (
        <UploadModal
          hwType={uploadTarget.hwType}
          assetName={uploadTarget.assetName}
          onClose={() => setUploadTarget(null)}
          onSaved={() => { setUploadTarget(null); fetchAssets(); }}
        />
      )}

      {deleteTarget && (
        <DeleteConfirmModal
          hwType={deleteTarget.hwType}
          assetName={deleteTarget.assetName}
          onClose={() => setDeleteTarget(null)}
          onConfirmed={() => { setDeleteTarget(null); fetchAssets(); }}
        />
      )}

      {noteTarget && (
        <NoteModal
          hwType={noteTarget.hw_type}
          currentNote={noteTarget.note}
          onClose={() => setNoteTarget(null)}
          onSaved={(newNote) => {
            // Optimistically update local state
            setAssets((prev) => prev.map((e) => e.hw_type === noteTarget.hw_type ? { ...e, note: newNote } : e));
            setNoteTarget(null);
          }}
        />
      )}

      {showAddBespoke && (
        <AddBespokeModal
          existingUids={assets.filter((e) => e.is_bespoke).map((e) => e.hw_type)}
          onClose={() => setShowAddBespoke(false)}
          onSaved={() => { setShowAddBespoke(false); fetchAssets(); }}
        />
      )}
    </div>
  );
}
