import { useState, useEffect, useCallback, useRef } from "react";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import api from "../../api/client";
import { useAuth } from "../../auth/AuthContext";
import ComposeEmailModal from "../components/ComposeEmailModal";
import MailViewModal from "../components/MailViewModal";
import QuotationList from "../quotations/QuotationList";
import { CommTypeIconBadge, CommDirectionIcon } from "../components/CommIcons";
import OverviewTab from "./CustomerDetail/OverviewTab";
import SupportTab from "./CustomerDetail/SupportTab";
import FinancialsTab from "./CustomerDetail/FinancialsTab";
import OrdersTab from "./CustomerDetail/OrdersTab";
import { InitNegotiationsModal, RecordIssueModal, RecordPaymentModal } from "./CustomerDetail/QuickEntryModals";

// Inline SVG icons — all use currentColor
const IconExpand  = ({ size = 13 }) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 8L21 3M21 3H16M21 3V8M8 8L3 3M3 3L3 8M3 3L8 3M8 16L3 21M3 21H8M3 21L3 16M16 16L21 21M21 21V16M21 21H16"/></svg>;
const IconReply   = () => <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 17 4 12 9 7"/><path d="M20 18v-2a4 4 0 0 0-4-4H4"/></svg>;
const IconEdit    = () => <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M20,16v4a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4H8"/><polygon points="12.5 15.8 22 6.2 17.8 2 8.3 11.5 8 16 12.5 15.8"/></svg>;
const IconDelete  = ({ size = 13 }) => <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor"><path d="M5.755,20.283,4,8H20L18.245,20.283A2,2,0,0,1,16.265,22H7.735A2,2,0,0,1,5.755,20.283ZM21,4H16V3a1,1,0,0,0-1-1H9A1,1,0,0,0,8,3V4H3A1,1,0,0,0,3,6H21a1,1,0,0,0,0-2Z"/></svg>;

// Media-specific SVG icons (currentColor)
const IconRefresh = () => (
  <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
    <path fillRule="evenodd" clipRule="evenodd" d="M3.46447 3.46447C2 4.92893 2 7.28595 2 12C2 16.714 2 19.0711 3.46447 20.5355C4.92893 22 7.28595 22 12 22C16.714 22 19.0711 22 20.5355 20.5355C22 19.0711 22 16.714 22 12C22 7.28595 22 4.92893 20.5355 3.46447C19.0711 2 16.714 2 12 2C7.28595 2 4.92893 2 3.46447 3.46447ZM5.46058 11.0833C5.83333 7.79988 8.62406 5.25 12.0096 5.25C13.9916 5.25 15.7702 6.12471 16.9775 7.50653C17.25 7.81846 17.2181 8.29226 16.9061 8.56479C16.5942 8.83733 16.1204 8.80539 15.8479 8.49347C14.9136 7.42409 13.541 6.75 12.0096 6.75C9.45215 6.75 7.33642 8.63219 6.97332 11.0833H7.33654C7.63998 11.0833 7.91353 11.2662 8.02955 11.5466C8.14558 11.8269 8.08122 12.1496 7.86651 12.364L6.69825 13.5307C6.40544 13.8231 5.93113 13.8231 5.63832 13.5307L4.47005 12.364C4.25534 12.1496 4.19099 11.8269 4.30701 11.5466C4.42304 11.2662 4.69658 11.0833 5.00002 11.0833H5.46058ZM17.3018 10.4693C17.5947 10.1769 18.069 10.1769 18.3618 10.4693L19.53 11.636C19.7448 11.8504 19.8091 12.1731 19.6931 12.4534C19.5771 12.7338 19.3035 12.9167 19.0001 12.9167H18.5395C18.1668 16.2001 15.376 18.75 11.9905 18.75C10.0085 18.75 8.22995 17.8753 7.02263 16.4935C6.7501 16.1815 6.78203 15.7077 7.09396 15.4352C7.40589 15.1627 7.87968 15.1946 8.15222 15.5065C9.08654 16.5759 10.4591 17.25 11.9905 17.25C14.548 17.25 16.6637 15.3678 17.0268 12.9167H16.6636C16.3601 12.9167 16.0866 12.7338 15.9705 12.4534C15.8545 12.1731 15.9189 11.8504 16.1336 11.636L17.3018 10.4693Z"/>
  </svg>
);
const IconVideo = ({ style }) => (
  <svg width="36" height="36" viewBox="0 0 512 512" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style={style}>
    <path d="M482.703,98.813C456.469,77.625,363.953,61,256,61S55.531,77.625,29.297,98.813C5.188,118.25,0,206.125,0,256s5.188,137.75,29.297,157.188C55.531,434.375,148.047,451,256,451s200.469-16.625,226.703-37.813C506.813,393.75,512,305.875,512,256S506.813,118.25,482.703,98.813z M332.813,258.406l-118.844,70.938c-0.875,0.5-1.938,0.531-2.813,0.031s-1.422-1.438-1.422-2.438V256v-70.938c0-1.016,0.547-1.938,1.422-2.438s1.938-0.469,2.813,0.031l118.844,70.938c0.844,0.5,1.359,1.406,1.359,2.406C334.172,256.969,333.656,257.906,332.813,258.406z"/>
  </svg>
);
const IconWaveform = () => (
  <svg width="56" height="56" viewBox="0 0 56 56" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style={{ color: "var(--accent)" }}>
    <path d="M 27.9999 51.9063 C 41.0546 51.9063 51.9063 41.0781 51.9063 28 C 51.9063 14.9453 41.0312 4.0937 27.9765 4.0937 C 14.8983 4.0937 4.0937 14.9453 4.0937 28 C 4.0937 41.0781 14.9218 51.9063 27.9999 51.9063 Z M 27.9999 47.9219 C 16.9374 47.9219 8.1014 39.0625 8.1014 28 C 8.1014 16.9609 16.9140 8.0781 27.9765 8.0781 C 39.0155 8.0781 47.8983 16.9609 47.9219 28 C 47.9454 39.0625 39.0390 47.9219 27.9999 47.9219 Z M 25.6327 43.5391 C 26.3593 43.5391 26.8983 42.9766 26.8983 42.2500 L 26.8983 13.75 C 26.8983 13.0234 26.3358 12.4375 25.6327 12.4375 C 24.8827 12.4375 24.3202 13.0234 24.3202 13.75 L 24.3202 42.2500 C 24.3202 43.0000 24.8593 43.5391 25.6327 43.5391 Z M 35.1014 40.1406 C 35.8514 40.1406 36.4140 39.5547 36.4140 38.8516 L 36.4140 17.1250 C 36.4140 16.3984 35.8514 15.8359 35.1014 15.8359 C 34.3983 15.8359 33.8358 16.4219 33.8358 17.1250 L 33.8358 38.8516 C 33.8358 39.5547 34.3983 40.1406 35.1014 40.1406 Z M 20.8749 37.1641 C 21.5780 37.1641 22.1405 36.6016 22.1405 35.8750 L 22.1405 20.0781 C 22.1405 19.3750 21.5780 18.8125 20.8749 18.8125 C 20.1483 18.8125 19.5624 19.3750 19.5624 20.0781 L 19.5624 35.8750 C 19.5624 36.6016 20.1483 37.1641 20.8749 37.1641 Z M 30.3671 35.2890 C 31.0936 35.2890 31.6562 34.75 31.6562 34.0234 L 31.6562 21.9531 C 31.6562 21.2266 31.0936 20.6875 30.3671 20.6875 C 29.6405 20.6875 29.0780 21.25 29.0780 21.9531 L 29.0780 34.0234 C 29.0780 34.7266 29.6405 35.2890 30.3671 35.2890 Z M 39.8827 32.5 C 40.6093 32.5 41.1718 31.9609 41.1718 31.2344 L 41.1718 24.7422 C 41.1718 24.0156 40.6093 23.4766 39.8827 23.4766 C 39.1562 23.4766 38.5936 24.0156 38.5936 24.7422 L 38.5936 31.2344 C 38.5936 31.9609 39.1562 32.5 39.8827 32.5 Z M 16.0936 31.3281 C 16.8202 31.3281 17.4062 30.7656 17.4062 30.0625 L 17.4062 25.9141 C 17.4062 25.2109 16.8202 24.6484 16.0936 24.6484 C 15.3905 24.6484 14.8046 25.2109 14.8046 25.9141 L 14.8046 30.0625 C 14.8046 30.7656 15.3905 31.3281 16.0936 31.3281 Z"/>
  </svg>
);

const IconNextcloud = ({ size = 16 }) => (
  <svg width={size} height={size} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
    <path d="M16.027 8.713c-3.333 0-6.136 2.287-6.991 5.355-0.744-1.641-2.391-2.808-4.301-2.808-2.609 0.016-4.724 2.131-4.735 4.74 0.011 2.609 2.125 4.724 4.735 4.74 1.911 0 3.552-1.167 4.301-2.813 0.855 3.073 3.657 5.36 6.991 5.36 3.312 0 6.099-2.26 6.973-5.308 0.755 1.615 2.375 2.761 4.26 2.761 2.615-0.016 4.729-2.131 4.74-4.74-0.011-2.609-2.125-4.724-4.74-4.74-1.885 0-3.505 1.147-4.265 2.761-0.869-3.048-3.656-5.308-6.968-5.308zM16.027 11.495c2.5 0 4.5 2 4.5 4.505s-2 4.505-4.5 4.505c-2.496 0.011-4.516-2.016-4.505-4.505 0-2.505 2-4.505 4.505-4.505zM4.735 14.041c1.099 0 1.959 0.86 1.959 1.959s-0.86 1.959-1.959 1.959c-1.084 0.011-1.969-0.876-1.953-1.959 0-1.099 0.859-1.959 1.953-1.959zM27.26 14.041c1.1 0 1.959 0.86 1.959 1.959s-0.859 1.959-1.959 1.959c-1.083 0.011-1.963-0.876-1.953-1.959 0-1.099 0.86-1.959 1.953-1.959z"/>
  </svg>
);


const IconDownload = ({ size = 16 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <path d="M12 7L12 14M12 14L15 11M12 14L9 11"/>
    <path d="M16 17H12H8"/>
    <path d="M22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C21.5093 4.43821 21.8356 5.80655 21.9449 8"/>
  </svg>
);

const CONTACT_TYPE_ICONS = {
  email: "📧",
  phone: "📞",
  whatsapp: "💬",
  other: "🔗",
};

const COMMS_TYPES = ["email", "whatsapp", "call", "sms", "note", "in_person"];
const COMMS_DIRECTIONS = ["inbound", "outbound", "internal"];
const MEDIA_DIRECTIONS = ["received", "sent", "internal"];

// Display labels for transport types — always lowercase
const COMM_TYPE_LABELS = {
  email:     "e-mail",
  whatsapp:  "whatsapp",
  call:      "phonecall",
  sms:       "sms",
  note:      "note",
  in_person: "in person",
};
const COMM_DATE_FMT = new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "short", year: "numeric" });
const COMM_FULL_DATE_FMT = new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "long", year: "numeric" });
const COMM_TIME_FMT = new Intl.DateTimeFormat("en-US", { hour: "numeric", minute: "2-digit", hour12: true });

function formatCommDate(value) {
  if (!value) return "";
  const d = new Date(value);
  if (Number.isNaN(d.getTime())) return "";
  return COMM_DATE_FMT.format(d);
}

function formatRelativeTime(value) {
  if (!value) return "";
  const d = new Date(value);
  if (Number.isNaN(d.getTime())) return "";
  const diffMs = Date.now() - d.getTime();
  const diffSec = Math.floor(diffMs / 1000);
  if (diffSec < 60) return "just now";
  const diffMin = Math.floor(diffSec / 60);
  if (diffMin < 60) return `${diffMin}m ago`;
  const diffHr = Math.floor(diffMin / 60);
  if (diffHr < 24) return `${diffHr}h ago`;
  const diffDay = Math.floor(diffHr / 24);
  if (diffDay < 7) return diffDay === 1 ? "yesterday" : `${diffDay} days ago`;
  const diffWk = Math.floor(diffDay / 7);
  if (diffWk < 5) return diffWk === 1 ? "1 week ago" : `${diffWk} weeks ago`;
  const diffMo = Math.floor(diffDay / 30);
  if (diffMo < 12) return diffMo === 1 ? "1 month ago" : `${diffMo} months ago`;
  const diffYr = Math.floor(diffDay / 365);
  return diffYr === 1 ? "1 year ago" : `${diffYr} years ago`;
}

function formatFullDateTime(value) {
  if (!value) return "";
  const d = new Date(value);
  if (Number.isNaN(d.getTime())) return "";
  return `${COMM_FULL_DATE_FMT.format(d)}, ${COMM_TIME_FMT.format(d).toLowerCase()}`;
}

const inputClass = "w-full px-3 py-2 text-sm rounded-md border";
const inputStyle = {
  backgroundColor: "var(--bg-input)",
  borderColor: "var(--border-primary)",
  color: "var(--text-primary)",
};
const labelStyle = {
  display: "block",
  marginBottom: 4,
  fontSize: 12,
  color: "var(--text-secondary)",
  fontWeight: 500,
};

function Field({ label, children }) {
  return (
    <div>
      <div style={labelStyle}>{label}</div>
      {children}
    </div>
  );
}

function ReadField({ label, value }) {
  return (
    <div>
      <div style={labelStyle}>{label}</div>
      <div className="text-sm" style={{ color: "var(--text-primary)" }}>{value || <span style={{ color: "var(--text-muted)" }}>—</span>}</div>
    </div>
  );
}

const TABS = ["Overview", "Communication", "Quotations", "Orders", "Finance", "Files & Media", "Devices", "Support"];

const LANGUAGE_LABELS = {
  el: "Greek",
  en: "English",
  de: "German",
  fr: "French",
  it: "Italian",
};

const CATEGORY_LABELS = {
  controller: "Controller",
  striker: "Striker",
  clock: "Clock",
  part: "Part",
  repair_service: "Repair / Service",
};

const MIME_ICON = (mime) => {
  if (!mime) return "📄";
  if (mime.startsWith("image/")) return "🖼️";
  if (mime === "application/pdf") return "📑";
  if (mime.startsWith("video/")) return "🎬";
  if (mime.startsWith("audio/")) return "🎵";
  if (mime.includes("spreadsheet") || mime.includes("excel")) return "📊";
  if (mime.includes("word") || mime.includes("document")) return "📝";
  return "📄";
};

// PDF thumbnail using PDF.js (UMD build loaded from CDN on first use)
function PdfThumbnail({ url }) {
  const canvasRef = useRef(null);
  const [failed, setFailed] = useState(false);
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        if (!window.pdfjsLib) {
          await new Promise((res, rej) => {
            if (document.getElementById("__pdfjs2__")) { res(); return; }
            const s = document.createElement("script");
            s.id = "__pdfjs2__";
            s.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js";
            s.onload = res; s.onerror = rej;
            document.head.appendChild(s);
          });
          window.pdfjsLib.GlobalWorkerOptions.workerSrc =
            "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
        }
        const loadingTask = window.pdfjsLib.getDocument(url);
        const pdf = await loadingTask.promise;
        if (cancelled) return;
        const page = await pdf.getPage(1);
        if (cancelled) return;
        const canvas = canvasRef.current;
        if (!canvas) return;
        const viewport = page.getViewport({ scale: 1 });
        const scale = Math.min(160 / viewport.width, 110 / viewport.height);
        const scaled = page.getViewport({ scale });
        canvas.width = scaled.width;
        canvas.height = scaled.height;
        await page.render({ canvasContext: canvas.getContext("2d"), viewport: scaled }).promise;
      } catch {
        if (!cancelled) setFailed(true);
      }
    })();
    return () => { cancelled = true; };
  }, [url]);
  if (failed) return <span style={{ fontSize: 36 }}>📑</span>;
  return <canvas ref={canvasRef} style={{ width: "100%", height: "100%", objectFit: "contain" }} />;
}

// Media categories — each maps to a Nextcloud subfolder
// hue: used for border/badge coloring
const MEDIA_CATEGORIES = [
  { value: "sent_media",     label: "Sent Media",          group: "media",    direction: "sent",      hue: { bg: "#2d2a1a", text: "#c9a84c", border: "#6b5a2a" } },
  { value: "received_media", label: "Received Media",      group: "media",    direction: "received",  hue: { bg: "#28250f", text: "#b8922e", border: "#5c4b1a" } },
  { value: "media",          label: "General Media",       group: "media",    direction: "internal",  hue: { bg: "#2d2a1a", text: "#d4a843", border: "#6b5a2a" } },
  { value: "invoices",       label: "Invoices / Shipping", group: "invoices", direction: "internal",  hue: { bg: "#2d1a1a", text: "#c47070", border: "#6b2e2e" } },
  { value: "quotations",     label: "Quotations",          group: "quotes",   direction: "internal",  hue: { bg: "#1a2d1e", text: "#6aab7a", border: "#2e5e38" } },
  { value: "documents",      label: "Documents",            group: "other",    direction: "internal",  hue: { bg: "#1e1a2d", text: "#a78bfa", border: "#5b21b6" } },
];

// Legacy subfolder names → category value mapping (for files already in NC)
const LEGACY_SUBFOLDER_MAP = {
  sent:     "sent_media",
  received: "received_media",
  media:    "media",
  documents: "documents",
};

// Detect legacy serial format: all caps alphanumeric, no hyphens, 10+ chars
function isLegacySerial(serial) {
  return /^[A-Z0-9]{10,}$/.test(serial || "");
}

function LegacyBadge() {
  return (
    <span style={{
      fontSize: 9, fontWeight: 700, padding: "1px 5px", borderRadius: 3,
      backgroundColor: "#2e1a00", color: "#fb923c", letterSpacing: "0.04em",
      textTransform: "uppercase", flexShrink: 0,
    }}>
      legacy
    </span>
  );
}

function OwnedItemRow({ item, sku, thumbUrl, canEdit, saving, onQtyChange, onRemove }) {
  const [editing, setEditing] = useState(false);
  const [draftQty, setDraftQty] = useState(String(item.quantity || 1));
  const [hovered, setHovered] = useState(false);

  const commitQty = () => {
    const n = parseInt(draftQty, 10);
    if (!isNaN(n) && n >= 1) onQtyChange(n);
    setEditing(false);
  };

  const serials = Array.isArray(item.serial_numbers) ? item.serial_numbers : [];
  const hasLegacySerial = serials.some(isLegacySerial);

  return (
    <div
      style={{ display: "grid", gridTemplateColumns: "88px minmax(120px,1.5fr) minmax(60px,0.6fr) minmax(100px,1.2fr) 80px", gap: 16, padding: "10px 20px", alignItems: "center" }}
      className="text-sm"
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
    >
      {/* Col 0: thumbnail */}
      <div style={{ width: 72, height: 72, borderRadius: 6, overflow: "hidden", flexShrink: 0, backgroundColor: "var(--bg-primary)", display: "flex", alignItems: "center", justifyContent: "center" }}>
        {thumbUrl ? (
          <img src={thumbUrl} alt="" style={{ width: "100%", height: "100%", objectFit: "contain", padding: 8 }} />
        ) : (
          <span style={{ fontSize: 32, opacity: 0.2 }}>📦</span>
        )}
      </div>
      {/* Col 1: name + notes */}
      <div className="min-w-0">
        <div className="flex items-center gap-2 flex-wrap">
          <span className="font-medium truncate" style={{ color: "var(--text-primary)" }}>
            {item.product_name || item.description || item.product_id || "Custom Device"}
          </span>
          {hasLegacySerial && <LegacyBadge />}
        </div>
        {item.notes && <p className="text-xs mt-0.5 truncate" style={{ color: "var(--text-muted)" }}>{item.notes}</p>}
      </div>

      {/* Col 2: SKU */}
      {sku ? (
        <span className="text-xs font-mono flex-shrink-0" style={{ color: "var(--text-muted)" }}>
          {sku}
        </span>
      ) : <span />}

      {/* Col 3: Serial numbers */}
      {serials.length > 0 ? (
        <div className="flex flex-col gap-0.5 flex-shrink-0 min-w-0">
          {serials.map((sn, i) => (
            <span key={i} className="text-xs font-mono truncate" style={{ color: "var(--text-muted)" }}>
              SN: {sn}
            </span>
          ))}
        </div>
      ) : <span />}

      {/* Col 4: qty + remove */}
      <div className="flex items-center gap-2 flex-shrink-0">
        {editing ? (
          <input
            type="number"
            min="1"
            autoFocus
            value={draftQty}
            onChange={(e) => setDraftQty(e.target.value)}
            onBlur={commitQty}
            onKeyDown={(e) => { if (e.key === "Enter") commitQty(); if (e.key === "Escape") { setEditing(false); setDraftQty(String(item.quantity || 1)); } }}
            className="px-2 py-1 text-sm rounded-md border"
            style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--accent)", color: "var(--text-primary)", width: 54, outline: "none" }}
          />
        ) : (
          <button
            type="button"
            onClick={() => { if (canEdit && !saving) { setDraftQty(String(item.quantity || 1)); setEditing(true); } }}
            title={canEdit ? "Click to edit quantity" : undefined}
            className="text-xs px-2 py-1 rounded"
            style={{
              backgroundColor: "var(--bg-primary)",
              color: "var(--text-muted)",
              border: "1px solid transparent",
              cursor: canEdit ? "pointer" : "default",
            }}
          >
            × {item.quantity || 1}
          </button>
        )}
        {canEdit && (
          <button
            type="button"
            onClick={onRemove}
            disabled={saving}
            style={{
              background: "none", border: "none", cursor: "pointer",
              color: "var(--danger-text)", fontSize: 18, lineHeight: 1, padding: "2px 4px",
              opacity: hovered ? 1 : 0,
              transition: "opacity 0.15s",
            }}
            title="Remove"
          >
            ×
          </button>
        )}
      </div>
    </div>
  );
}

function ProductPickerRow({ product: p, existing, saving, onAdd }) {
  const [qty, setQty] = useState(1);
  return (
    <div
      style={{ display: "grid", gridTemplateColumns: "1fr auto 80px 80px", gap: 12, padding: "10px 20px", borderBottom: "1px solid var(--border-secondary)", alignItems: "center" }}
    >
      <div>
        <p className="text-sm font-medium" style={{ color: "var(--text-primary)" }}>{p.name}</p>
        <p className="text-xs" style={{ color: "var(--text-muted)" }}>{CATEGORY_LABELS[p.category] || p.category}</p>
      </div>
      <span className="font-mono text-xs" style={{ color: "var(--text-muted)" }}>{p.sku || "—"}</span>
      <span className="text-sm text-center" style={{ color: existing ? "var(--text-heading)" : "var(--text-muted)" }}>
        {existing ? `× ${existing.quantity || 1}` : "—"}
      </span>
      <div className="flex items-center gap-1">
        <input
          type="number"
          min="1"
          value={qty}
          onChange={(e) => setQty(Math.max(1, parseInt(e.target.value, 10) || 1))}
          onClick={(e) => e.stopPropagation()}
          className="px-2 py-1 text-sm rounded-md border"
          style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)", width: 44 }}
        />
        <button
          type="button"
          disabled={saving}
          onClick={(e) => { e.stopPropagation(); onAdd(qty); }}
          className="px-2 py-1 text-xs rounded-md cursor-pointer hover:opacity-90 flex-shrink-0"
          style={{ backgroundColor: existing ? "var(--accent)" : "var(--btn-primary)", color: "var(--text-white)", whiteSpace: "nowrap" }}
        >
          {existing ? "+ Add" : "Add"}
        </button>
      </div>
    </div>
  );
}

function OverviewQuickSection({ title, onViewAll, empty, children }) {
  const items = Array.isArray(children) ? children.filter(Boolean) : (children ? [children] : []);
  return (
    <div
      className="ui-section-card mb-4"
    >
      <div className="flex items-center justify-between mb-3">
        <h3 className="ui-section-card__title">{title}</h3>
        <button
          type="button"
          onClick={onViewAll}
          className="text-xs cursor-pointer hover:underline"
          style={{ color: "var(--accent)", background: "none", border: "none", padding: 0 }}
        >
          View all
        </button>
      </div>
      {items.length === 0
        ? <p className="text-xs" style={{ color: "var(--text-muted)" }}>{empty}</p>
        : items
      }
    </div>
  );
}

function formatFileSize(bytes) {
  if (!bytes || bytes <= 0) return null;
  const kb = bytes / 1024;
  if (kb < 900) return `${Math.round(kb)} KB`;
  const mb = kb / 1024;
  if (mb < 900) return mb < 10 ? `${mb.toFixed(1)} MB` : `${Math.round(mb)} MB`;
  const gb = mb / 1024;
  return gb < 10 ? `${gb.toFixed(1)} GB` : `${Math.round(gb)} GB`;
}

function getCategoryMeta(subfolder) {
  const mapped = LEGACY_SUBFOLDER_MAP[subfolder] || subfolder;
  return MEDIA_CATEGORIES.find(c => c.value === mapped) || MEDIA_CATEGORIES.find(c => c.value === "documents");
}

// Group definitions for group view
const CATEGORY_GROUPS = [
  { key: "media",    label: "Media",               values: ["media", "sent_media", "received_media"] },
  { key: "invoices", label: "Invoices / Shipping",  values: ["invoices"] },
  { key: "quotes",   label: "Quotations",           values: ["quotations"] },
  { key: "other",    label: "Documents",             values: ["documents"] },
];

export default function CustomerDetail() {
  const { id } = useParams();
  const navigate = useNavigate();
  const [searchParams] = useSearchParams();
  const { user, hasPermission } = useAuth();
  const canEdit = hasPermission("crm", "edit");

  // ALL state hooks must be before any early returns
  const [customer, setCustomer] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [activeTab, setActiveTab] = useState(() => {
    const tab = searchParams.get("tab");
    const TABS = ["Overview", "Communication", "Quotations", "Orders", "Finance", "Files & Media", "Devices", "Support"];
    // Also accept old tab names for backwards compat
    if (tab === "Financials") return "Finance";
    return TABS.includes(tab) ? tab : "Overview";
  });

  // Comm direction (used by other parts)
  const [lastCommDirection, setLastCommDirection] = useState(null);

  // Quick-entry modals
  const [showInitNegModal, setShowInitNegModal] = useState(false);
  const [showIssueModal, setShowIssueModal] = useState(false);
  const [showPaymentModal, setShowPaymentModal] = useState(false);

  // Orders tab
  const [orders, setOrders] = useState([]);
  const [ordersLoading, setOrdersLoading] = useState(false);
  const [expandOrderId, setExpandOrderId] = useState(null);

  // Quotations (overview preview)
  const [latestQuotations, setLatestQuotations] = useState([]);

  // Comms tab
  const [comms, setComms] = useState([]);
  const [commsLoading, setCommsLoading] = useState(false);
  const [showCommsForm, setShowCommsForm] = useState(false);
  const [commsForm, setCommsForm] = useState({
    type: "note",
    direction: "internal",
    subject: "",
    body: "",
    occurred_at: new Date().toISOString().slice(0, 16),
  });
  const [commsSaving, setCommsSaving] = useState(false);
  const [expandedComms, setExpandedComms] = useState({});
  const [commsViewEntry, setCommsViewEntry] = useState(null);   // MailViewModal
  const [composeEmailOpen, setComposeEmailOpen] = useState(false);
  const [composeEmailTo, setComposeEmailTo] = useState("");
  const [composeEmailSubject, setComposeEmailSubject] = useState("");
  const [composeEmailFromAccount, setComposeEmailFromAccount] = useState("");
  const [composeDefaultServerAttachments, setComposeDefaultServerAttachments] = useState([]);
  const [commsDeleteId, setCommsDeleteId] = useState(null);     // id pending delete confirm
  const [commsDeleting, setCommsDeleting] = useState(false);
  const [commsHoverId, setCommsHoverId] = useState(null);       // id of hovered comm entry
  const [commsEditId, setCommsEditId] = useState(null);         // id being edited
  const [commsEditForm, setCommsEditForm] = useState({});       // edit form state
  const [commsEditSaving, setCommsEditSaving] = useState(false);

  // Media tab
  const [media, setMedia] = useState([]);
  const [mediaLoading, setMediaLoading] = useState(false);
  const [ncFiles, setNcFiles] = useState([]);      // files browsed from Nextcloud
  const [ncBrowsing, setNcBrowsing] = useState(false);
  const [ncThumbMapState, setNcThumbMapState] = useState(null); // null = not yet loaded
  const [showUpload, setShowUpload] = useState(false);
  const [showCreateTxt, setShowCreateTxt] = useState(false);
  const [createTxtName, setCreateTxtName] = useState("new-file.txt");
  const [createTxtFolder, setCreateTxtFolder] = useState("documents");
  const [createTxtSaving, setCreateTxtSaving] = useState(false);
  const [mediaDeleting, setMediaDeleting] = useState({});

  // Media tab — view/filter/sort/upload enhancements
  const [mediaView, setMediaView] = useState("tile");      // "tile" | "list"
  const [mediaFilter, setMediaFilter] = useState("all");   // legacy single filter (kept for group logic)
  const [mediaFilterTypes, setMediaFilterTypes] = useState(new Set()); // multi-select category filters
  const [mediaTypeFilter, setMediaTypeFilter] = useState("all"); // "all" | "photos" | "videos"
  const [mediaFilterModalOpen, setMediaFilterModalOpen] = useState(false);
  const [mediaSort, setMediaSort] = useState("date_desc");
  const [mediaSearch, setMediaSearch] = useState("");
  const [mediaPageSize, setMediaPageSize] = useState(20);  // default: 20 per page
  const [mediaPage, setMediaPage] = useState(1);
  const [groupView, setGroupView] = useState(true);        // group by category when filter=all
  const previewListRef = useRef([]);                        // ordered list of previewable files for prev/next nav
  // Upload modal: each entry is { file: File, name: string, subfolder: string, tags: string[], tagInput: string, progress: null|number|"done"|"error" }
  const [uploadFiles, setUploadFiles] = useState([]);
  const [uploadDragging, setUploadDragging] = useState(false);
  const [uploadRunning, setUploadRunning] = useState(false);
  // Upload modal bulk-set state
  const [bulkSubfolder, setBulkSubfolder] = useState("media");
  const [bulkTagInput, setBulkTagInput] = useState("");
  const [bulkTags, setBulkTags] = useState([]);
  const [syncLoading, setSyncLoading] = useState(false);
  const [untrackLoading, setUntrackLoading] = useState(false);
  const [thumbGenLoading, setThumbGenLoading] = useState(false);
  const [thumbGenResult, setThumbGenResult] = useState(null);  // { generated, skipped, failed } | null
  const [mediaGearOpen, setMediaGearOpen] = useState(false);
  const [previewFile, setPreviewFile] = useState(null);    // { path, mime_type, filename } | null
  const [selectedPaths, setSelectedPaths] = useState(new Set()); // multi-select
  const [selectionMenuOpen, setSelectionMenuOpen] = useState(false);
  const [tileHovered, setTileHovered] = useState(null);  // path of hovered tile
  const [txtContent, setTxtContent] = useState(null);     // loaded text content for preview
  const [txtEditing, setTxtEditing] = useState(false);    // TXT edit mode
  const [txtDraft, setTxtDraft] = useState("");            // TXT draft being edited
  const [txtSaving, setTxtSaving] = useState(false);
  const fileInputRef = useRef(null);

  // Email compose
  const [showEmailCompose, setShowEmailCompose] = useState(false);
  const [emailForm, setEmailForm] = useState({ to: "", subject: "", body: "", cc: "" });
  const [emailSending, setEmailSending] = useState(false);
  const [emailError, setEmailError] = useState("");

  // Devices tab
  const [allDevices, setAllDevices] = useState([]);           // BellCloud devices list
  const [allProducts, setAllProducts] = useState([]);         // CRM products list
  const [showAddLinked, setShowAddLinked] = useState(false);  // modal for BellCloud linked
  const [showAddOwned, setShowAddOwned] = useState(false);    // modal for owned devices
  const [linkedSearch, setLinkedSearch] = useState("");
  const [ownedSearch, setOwnedSearch] = useState("");
  const [ownedCatFilter, setOwnedCatFilter] = useState("all");
  const [addOwnedMode, setAddOwnedMode] = useState("product"); // "product" | "custom"
  const [customDevice, setCustomDevice] = useState({ name: "", description: "", quantity: "1" });
  const [devicesSaving, setDevicesSaving] = useState(false);
  const [hoveredDeviceIdx, setHoveredDeviceIdx] = useState(null);

  // Global ESC handler — closes any open modal
  useEffect(() => {
    const handler = (e) => {
      if (e.key !== "Escape") return;
      if (showInitNegModal) { setShowInitNegModal(false); return; }
      if (showIssueModal) { setShowIssueModal(false); return; }
      if (showPaymentModal) { setShowPaymentModal(false); return; }
      if (previewFile) { setPreviewFile(null); return; }
      if (mediaFilterModalOpen) { setMediaFilterModalOpen(false); return; }
      if (showUpload) { setShowUpload(false); return; }
      if (showCreateTxt) { setShowCreateTxt(false); return; }
      if (showAddLinked) { setShowAddLinked(false); return; }
      if (showAddOwned) { setShowAddOwned(false); return; }
      if (commsViewEntry) { setCommsViewEntry(null); return; }
      if (composeEmailOpen) { setComposeEmailOpen(false); return; }
      if (commsDeleteId) { setCommsDeleteId(null); return; }
      if (showCommsForm) { setShowCommsForm(false); return; }
      if (showEmailCompose) { setShowEmailCompose(false); return; }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [showInitNegModal, showIssueModal, showPaymentModal, previewFile, mediaFilterModalOpen, showUpload, showCreateTxt, showAddLinked, showAddOwned, commsViewEntry, composeEmailOpen, commsDeleteId, showCommsForm, showEmailCompose]);

  const loadCustomer = useCallback(() => {
    setLoading(true);
    setError("");
    api.get(`/crm/customers/${id}`)
      .then((data) => setCustomer(data))
      .catch((err) => setError(err.message))
      .finally(() => setLoading(false));
  }, [id]);

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

  useEffect(() => {
    api.get(`/crm/customers/${id}/last-comm-direction`)
      .then((res) => setLastCommDirection(res.direction || null))
      .catch(() => setLastCommDirection(null));
  }, [id]);

  const loadOrders = useCallback(() => {
    setOrdersLoading(true);
    api.get(`/crm/customers/${id}/orders`)
      .then((data) => setOrders(data.orders || []))
      .catch(() => setOrders([]))
      .finally(() => setOrdersLoading(false));
  }, [id]);

  const loadLatestQuotations = useCallback(() => {
    api.get(`/crm/quotations/customer/${id}`)
      .then((data) => setLatestQuotations((data.quotations || []).slice(0, 3)))
      .catch(() => setLatestQuotations([]));
  }, [id]);

  const loadComms = useCallback(() => {
    setCommsLoading(true);
    api.get(`/crm/comms?customer_id=${id}`)
      .then((data) => setComms(data.entries || []))
      .catch(() => setComms([]))
      .finally(() => setCommsLoading(false));
  }, [id]);

  const loadMedia = useCallback(() => {
    setMediaLoading(true);
    api.get(`/crm/media?customer_id=${id}`)
      .then((data) => setMedia(data.items || []))
      .catch(() => setMedia([]))
      .finally(() => setMediaLoading(false));
  }, [id]);

  const browseNextcloud = useCallback(() => {
    setNcBrowsing(true);
    setNcThumbMapState(null); // mark as loading
    api.get(`/crm/nextcloud/browse-all?customer_id=${id}`)
      .then((data) => {
        const items = data.items || [];
        setNcFiles(items);
        // Build thumb map: original file path → thumb NC path.
        // Thumbs are always stored as {stem}.jpg, so we match by stem
        // (stripping the source file's own extension before comparing).
        const thumbsByKey = {}; // "parentFolder|stem" → thumb path
        for (const f of items) {
          if (!f.path.includes("/.thumbs/")) continue;
          const parentFolder = f.path.split("/.thumbs/")[0];
          const thumbFilename = f.path.split("/").pop();
          const stem = thumbFilename.replace(/\.[^.]+$/, "");
          thumbsByKey[`${parentFolder}|${stem}`] = f.path;
        }
        const thumbMap = {};
        for (const f of items) {
          if (f.path.includes("/.thumbs/")) continue;
          const parentFolder = f.path.split("/").slice(0, -1).join("/");
          const stem = f.path.split("/").pop().replace(/\.[^.]+$/, "");
          const thumbPath = thumbsByKey[`${parentFolder}|${stem}`];
          if (thumbPath) thumbMap[f.path] = thumbPath;
        }
        setNcThumbMapState(thumbMap);
      })
      .catch(() => { setNcFiles([]); setNcThumbMapState({}); })
      .finally(() => setNcBrowsing(false));
  }, [id]);

  const loadDevicesAndProducts = useCallback(() => {
    api.get("/devices").then((d) => setAllDevices(d.devices || d || [])).catch(() => {});
    api.get("/crm/products").then((d) => setAllProducts(d.products || [])).catch(() => {});
  }, []);

  useEffect(() => {
    if (activeTab === "Overview") { loadOrders(); loadComms(); loadDevicesAndProducts(); loadLatestQuotations(); }
    if (activeTab === "Support") { /* customer data already loaded */ }
    if (activeTab === "Finance") { loadOrders(); }
    if (activeTab === "Orders") loadOrders();
    if (activeTab === "Communication") loadComms();
    if (activeTab === "Files & Media") { setNcThumbMapState(null); loadMedia(); browseNextcloud(); }
    if (activeTab === "Devices") loadDevicesAndProducts();
  }, [activeTab, loadOrders, loadComms, loadMedia, browseNextcloud, loadDevicesAndProducts, loadLatestQuotations]);

  // Load TXT content when a TXT file is opened for preview
  useEffect(() => {
    if (!previewFile) { setTxtContent(null); setTxtEditing(false); return; }
    const isTxtFile = previewFile.mime_type === "text/plain" || previewFile.filename?.endsWith(".txt");
    if (!isTxtFile) { setTxtContent(null); return; }
    const ncToken = localStorage.getItem("access_token") || "";
    const url = `/api/crm/nextcloud/file?path=${encodeURIComponent(previewFile.path)}&token=${encodeURIComponent(ncToken)}`;
    setTxtContent(null);
    fetch(url).then(r => r.text()).then(t => setTxtContent(t)).catch(() => setTxtContent("(failed to load)"));
  }, [previewFile]);

  const handleTabChange = useCallback((tab, extraArg) => {
    setActiveTab(tab);
    if (tab === "Orders" && extraArg) {
      setExpandOrderId(extraArg);
    }
  }, []);

  // Early returns after all hooks
  if (loading) {
    return <div className="text-center py-8" style={{ color: "var(--text-muted)" }}>Loading...</div>;
  }
  if (error) {
    return (
      <div className="text-sm rounded-md p-3 border" style={{ backgroundColor: "var(--danger-bg)", borderColor: "var(--danger)", color: "var(--danger-text)" }}>
        {error}
      </div>
    );
  }
  if (!customer) return null;

  const handleAddComms = async () => {
    setCommsSaving(true);
    try {
      await api.post("/crm/comms", {
        customer_id: id,
        type: commsForm.type,
        direction: commsForm.direction,
        subject: commsForm.subject || null,
        body: commsForm.body || null,
        occurred_at: commsForm.occurred_at || new Date().toISOString(),
        logged_by: user?.name || null,
      });
      setShowCommsForm(false);
      setCommsForm({ type: "note", direction: "internal", subject: "", body: "", occurred_at: new Date().toISOString().slice(0, 16) });
      loadComms();
    } catch (err) {
      alert(err.message);
    } finally {
      setCommsSaving(false);
    }
  };

  const handleSendEmail = async () => {
    setEmailSending(true);
    setEmailError("");
    try {
      const cc = emailForm.cc ? emailForm.cc.split(",").map((s) => s.trim()).filter(Boolean) : [];
      const primaryEmail = (customer?.contacts || []).find((c) => c.type === "email" && c.primary)?.value || "";
      await api.post("/crm/comms/email/send", {
        customer_id: id,
        to: emailForm.to || primaryEmail,
        subject: emailForm.subject,
        body: emailForm.body,
        cc,
      });
      setShowEmailCompose(false);
      setEmailForm({ to: "", subject: "", body: "", cc: "" });
      loadComms();
    } catch (err) {
      setEmailError(err.message || "Failed to send email");
    } finally {
      setEmailSending(false);
    }
  };

  const handleDeleteComm = async (commId) => {
    setCommsDeleting(true);
    try {
      await api.delete(`/crm/comms/${commId}`);
      setCommsDeleteId(null);
      loadComms();
    } catch (err) {
      alert(err.message || "Failed to delete entry");
    } finally {
      setCommsDeleting(false);
    }
  };

  const startEditComm = (entry) => {
    setCommsEditId(entry.id);
    setCommsEditForm({
      type: entry.type || "",
      direction: entry.direction || "",
      subject: entry.subject || "",
      body: entry.body || "",
      logged_by: entry.logged_by || "",
      occurred_at: entry.occurred_at ? entry.occurred_at.slice(0, 16) : "",
    });
  };

  const handleSaveEditComm = async () => {
    setCommsEditSaving(true);
    try {
      const payload = {};
      if (commsEditForm.type) payload.type = commsEditForm.type;
      if (commsEditForm.direction) payload.direction = commsEditForm.direction;
      if (commsEditForm.subject !== undefined) payload.subject = commsEditForm.subject || null;
      if (commsEditForm.body !== undefined) payload.body = commsEditForm.body || null;
      if (commsEditForm.logged_by !== undefined) payload.logged_by = commsEditForm.logged_by || null;
      if (commsEditForm.occurred_at) payload.occurred_at = new Date(commsEditForm.occurred_at).toISOString();
      await api.put(`/crm/comms/${commsEditId}`, payload);
      setCommsEditId(null);
      loadComms();
    } catch (err) {
      alert(err.message || "Failed to save entry");
    } finally {
      setCommsEditSaving(false);
    }
  };

  const addFilesToUploadQueue = (files) => {
    const entries = files.map(file => ({
      file,
      name: file.name,
      subfolder: "media",
      tags: [],
      tagInput: "",
      progress: null, // null=pending, 0-100=uploading, "done", "error"
    }));
    setUploadFiles(prev => [...prev, ...entries]);
  };

  const handleUpload = async () => {
    if (!uploadFiles.length || uploadRunning) return;
    setUploadRunning(true);
    const token = localStorage.getItem("access_token");
    for (let i = 0; i < uploadFiles.length; i++) {
      const entry = uploadFiles[i];
      if (entry.progress === "done") continue;
      // Set to 0 = starting
      setUploadFiles(prev => prev.map((e, idx) => idx === i ? { ...e, progress: 0 } : e));
      try {
        const formData = new FormData();
        // Rename file if user changed the name
        const uploadFile = entry.name !== entry.file.name
          ? new File([entry.file], entry.name, { type: entry.file.type })
          : entry.file;
        formData.append("file", uploadFile);
        formData.append("customer_id", id);
        formData.append("subfolder", entry.subfolder);
        if (entry.tags.length) formData.append("tags", entry.tags.join(","));
        const resp = await fetch("/api/crm/nextcloud/upload", {
          method: "POST",
          headers: { Authorization: `Bearer ${token}` },
          body: formData,
        });
        if (!resp.ok) {
          const err = await resp.json().catch(() => ({}));
          throw new Error(err.detail || resp.status);
        }
        setUploadFiles(prev => prev.map((e, idx) => idx === i ? { ...e, progress: "done" } : e));
      } catch {
        setUploadFiles(prev => prev.map((e, idx) => idx === i ? { ...e, progress: "error" } : e));
      }
    }
    setUploadRunning(false);
    loadMedia();
    browseNextcloud();
  };

  const handleCreateTxt = async () => {
    if (!createTxtName.trim() || createTxtSaving) return;
    setCreateTxtSaving(true);
    try {
      const token = localStorage.getItem("access_token");
      const ncToken = localStorage.getItem("access_token") || "";
      // Ensure name ends with .txt
      const filename = createTxtName.trim().endsWith(".txt") ? createTxtName.trim() : `${createTxtName.trim()}.txt`;
      const nc_path = customer.folder_id || id;
      const filePath = `customers/${nc_path}/${createTxtFolder}/${filename}`;
      await fetch(`/api/crm/nextcloud/file-put?path=${encodeURIComponent(filePath)}&token=${encodeURIComponent(ncToken)}`, {
        method: "PUT",
        headers: { "Content-Type": "text/plain", Authorization: `Bearer ${token}` },
        body: "",
      });
      setShowCreateTxt(false);
      setCreateTxtName("new-file.txt");
      loadMedia();
      browseNextcloud();
    } catch (e) {
      alert("Failed to create file: " + e.message);
    } finally {
      setCreateTxtSaving(false);
    }
  };

  const handleSync = async () => {
    setSyncLoading(true);
    try {
      const formData = new FormData();
      formData.append("customer_id", id);
      const token = localStorage.getItem("access_token");
      const resp = await fetch("/api/crm/nextcloud/sync", {
        method: "POST",
        headers: { Authorization: `Bearer ${token}` },
        body: formData,
      });
      if (!resp.ok) {
        const err = await resp.json().catch(() => ({}));
        throw new Error(err.detail || `Server error ${resp.status}`);
      }
      loadMedia();
      browseNextcloud();
    } catch (err) {
      alert(`Sync failed: ${err.message}`);
    } finally {
      setSyncLoading(false);
    }
  };

  const handleUntrackDeleted = async () => {
    setUntrackLoading(true);
    try {
      const formData = new FormData();
      formData.append("customer_id", id);
      const token = localStorage.getItem("access_token");
      const resp = await fetch("/api/crm/nextcloud/untrack-deleted", {
        method: "POST",
        headers: { Authorization: `Bearer ${token}` },
        body: formData,
      });
      if (!resp.ok) {
        const err = await resp.json().catch(() => ({}));
        throw new Error(err.detail || `Server error ${resp.status}`);
      }
      loadMedia();
      browseNextcloud();
    } catch (err) {
      alert(`Untrack failed: ${err.message}`);
    } finally {
      setUntrackLoading(false);
    }
  };

  const handleClearThumbs = async () => {
    if (!window.confirm("Delete ALL thumbnails for this customer? They can be regenerated afterwards.")) return;
    setMediaGearOpen(false);
    try {
      const formData = new FormData();
      formData.append("customer_id", id);
      const token = localStorage.getItem("access_token");
      const resp = await fetch("/api/crm/nextcloud/clear-thumbs", {
        method: "POST",
        headers: { Authorization: `Bearer ${token}` },
        body: formData,
      });
      if (!resp.ok) {
        const err = await resp.json().catch(() => ({}));
        throw new Error(err.detail || `Server error ${resp.status}`);
      }
      browseNextcloud();
    } catch (err) {
      alert(`Clear thumbnails failed: ${err.message}`);
    }
  };

  const handleGenerateThumbs = async () => {
    setThumbGenLoading(true);
    setThumbGenResult(null);
    setMediaGearOpen(false);
    try {
      const formData = new FormData();
      formData.append("customer_id", id);
      const token = localStorage.getItem("access_token");
      const resp = await fetch("/api/crm/nextcloud/generate-thumbs", {
        method: "POST",
        headers: { Authorization: `Bearer ${token}` },
        body: formData,
      });
      if (!resp.ok) {
        const err = await resp.json().catch(() => ({}));
        throw new Error(err.detail || `Server error ${resp.status}`);
      }
      const result = await resp.json();
      setThumbGenResult(result);
      browseNextcloud();
    } catch (err) {
      alert(`Thumbnail generation failed: ${err.message}`);
    } finally {
      setThumbGenLoading(false);
    }
  };

  const handleDeleteMedia = async (mediaId, ncPath) => {
    if (!window.confirm("Delete this file from Nextcloud and remove the record?")) return;
    setMediaDeleting((prev) => ({ ...prev, [mediaId]: true }));
    try {
      if (ncPath) {
        await api.delete(`/crm/nextcloud/file?path=${encodeURIComponent(ncPath)}`);
      } else {
        await api.delete(`/crm/media/${mediaId}`);
      }
      loadMedia();
      browseNextcloud();
    } catch (err) {
      alert(err.message);
    } finally {
      setMediaDeleting((prev) => ({ ...prev, [mediaId]: false }));
    }
  };

  const handleBulkDelete = async () => {
    const count = selectedPaths.size;
    if (!window.confirm(`Delete ${count} selected file${count !== 1 ? "s" : ""} from Nextcloud?`)) return;
    for (const path of selectedPaths) {
      try {
        await api.delete(`/crm/nextcloud/file?path=${encodeURIComponent(path)}`);
      } catch {
        // continue deleting others
      }
    }
    setSelectedPaths(new Set());
    loadMedia();
    browseNextcloud();
  };

  const initCustomerFolder = async () => {
    try {
      const formData = new FormData();
      formData.append("customer_id", id);
      formData.append("customer_name", customer.name);
      const token = localStorage.getItem("access_token");
      const resp = await fetch("/api/crm/nextcloud/init-customer-folder", {
        method: "POST",
        headers: { Authorization: `Bearer ${token}` },
        body: formData,
      });
      if (!resp.ok) {
        const err = await resp.json().catch(() => ({}));
        throw new Error(err.detail || `Server error ${resp.status}`);
      }
      browseNextcloud();
    } catch (err) {
      alert(`Init folder failed: ${err.message}`);
    }
  };

  // Only one comm expanded at a time
  const toggleComm = (id) =>
    setExpandedComms((prev) => {
      const alreadyOpen = !!prev[id];
      // Collapse all, then open this one if it wasn't open
      const cleared = {};
      if (!alreadyOpen) cleared[id] = true;
      return cleared;
    });

  const loc = customer.location || {};
  const locationStr = [loc.address, loc.city, loc.postal_code, loc.region, loc.country].filter(Boolean).join(", ");

  return (
    <div>
      {/* Header */}
      <div className="flex items-start justify-between mb-5">
        <div>
          <h1 className="text-2xl font-bold" style={{ color: "var(--text-heading)" }}>
            {[customer.title, customer.name, customer.surname].filter(Boolean).join(" ")}
          </h1>
          <p className="text-sm mt-1" style={{ color: "var(--text-muted)" }}>
            {[customer.organization, loc.city].filter(Boolean).join(" · ")}
          </p>
        </div>

        {/* Action buttons */}
        <div style={{ display: "flex", gap: 8, alignItems: "center", flexShrink: 0 }}>
          {canEdit && (
            <>
              {/* Init Negotiations */}
              <button
                type="button"
                onClick={() => setShowInitNegModal(true)}
                title="Init Negotiations"
                style={{
                  display: "flex", alignItems: "center", gap: 6,
                  padding: "7px 14px", borderRadius: 7, fontSize: 13, fontWeight: 600,
                  border: "1px solid var(--crm-rel-prospect-border)",
                  backgroundColor: "var(--crm-rel-prospect-bg)",
                  color: "var(--crm-rel-prospect-text)",
                  cursor: "pointer",
                  minWidth: 150, justifyContent: "center",
                }}
              >
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/>
                  <path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>
                </svg>
                Init Negotiations
              </button>

              {/* Record Issue/Support */}
              <button
                type="button"
                onClick={() => setShowIssueModal(true)}
                title="Record Issue or Support"
                style={{
                  display: "flex", alignItems: "center", gap: 6,
                  padding: "7px 14px", borderRadius: 7, fontSize: 13, fontWeight: 600,
                  border: "1px solid var(--crm-support-active-text)",
                  backgroundColor: "var(--crm-support-active-bg)",
                  color: "var(--crm-support-active-text)",
                  cursor: "pointer",
                  minWidth: 150, justifyContent: "center",
                }}
              >
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" 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>
                Record Issue
              </button>

              {/* Record Payment */}
              <button
                type="button"
                onClick={() => setShowPaymentModal(true)}
                title="Record Payment"
                style={{
                  display: "flex", alignItems: "center", gap: 6,
                  padding: "7px 14px", borderRadius: 7, fontSize: 13, fontWeight: 600,
                  border: "1px solid var(--crm-rel-active-border)",
                  backgroundColor: "var(--crm-rel-active-bg)",
                  color: "var(--crm-rel-active-text)",
                  cursor: "pointer",
                  minWidth: 150, justifyContent: "center",
                }}
              >
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <rect x="2" y="5" width="20" height="14" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/>
                </svg>
                Record Payment
              </button>
            </>
          )}

          {/* Edit Customer */}
          <button
            type="button"
            onClick={() => navigate(`/crm/customers/${id}/edit`)}
            style={{
              display: "flex", alignItems: "center", gap: 6,
              padding: "7px 14px", borderRadius: 7, fontSize: 13, fontWeight: 600,
              border: "1px solid var(--border-primary)",
              backgroundColor: "transparent",
              color: "var(--text-secondary)",
              cursor: "pointer",
              minWidth: 150, justifyContent: "center",
            }}
          >
            Edit Customer
          </button>
        </div>
      </div>

      {/* Tabs */}
      <div className="flex gap-1 mb-5 border-b" style={{ borderColor: "var(--border-primary)" }}>
        {TABS.map((tab) => (
          <button
            key={tab}
            type="button"
            onClick={() => setActiveTab(tab)}
            className="px-4 py-2 text-sm font-medium cursor-pointer transition-colors"
            style={{
              color: activeTab === tab ? "var(--accent)" : "var(--text-muted)",
              borderBottom: activeTab === tab ? "2px solid var(--accent)" : "2px solid transparent",
              marginBottom: -1,
              background: "none",
            }}
          >
            {tab}
          </button>
        ))}
      </div>

      {/* Overview Tab */}
      {activeTab === "Overview" && (
        <OverviewTab
          customer={customer}
          orders={orders}
          comms={comms}
          latestQuotations={latestQuotations}
          allDevices={allDevices}
          canEdit={canEdit}
          onCustomerUpdated={setCustomer}
          onTabChange={handleTabChange}
          onExpandComm={(commId) => setExpandedComms((prev) => ({ ...prev, [commId]: true }))}
          user={user}
        />
      )}

      {/* OLD Overview content — replaced, keeping stub to avoid parse error */}
      {false && (
        <div>
          {/* Hero: Basic Info card — 75/25 split */}
          <div className="ui-section-card mb-4" style={{ display: "grid", gridTemplateColumns: "8fr 2fr", gap: 40 }}>

            {/* LEFT: all info */}
            <div>
              {/* Row 1: Name / Org / Language / Religion */}
              <div style={{ display: "grid", gridTemplateColumns: "max-content max-content max-content max-content", gap: 16, columnGap: "max(100px, 16px)" }}>
                <ReadField
                  label="Name"
                  value={[customer.title, customer.name, customer.surname].filter(Boolean).join(" ")}
                />
                <ReadField label="Organization" value={customer.organization} />
                <ReadField label="Language" value={LANGUAGE_LABELS[customer.language] || customer.language} />
                <ReadField label="Religion" value={customer.religion} />
              </div>

              {/* Tags row */}
              {(customer.tags || []).length > 0 && (
                <div className="mt-3">
                  <div style={labelStyle}>Tags</div>
                  <div className="flex flex-wrap gap-1.5">
                    {customer.tags.map((tag) => (
                      <span
                        key={tag}
                        className="px-2 py-0.5 text-xs rounded-full"
                        style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-secondary)" }}
                      >
                        {tag}
                      </span>
                    ))}
                  </div>
                </div>
              )}

              {/* Address row */}
              {locationStr && (
                <div className="mt-3">
                  <ReadField
                    label="Address"
                    value={[loc.address, loc.city, loc.postal_code, loc.region, loc.country].filter(Boolean).join(", ")}
                  />
                </div>
              )}

              {/* Contacts */}
              {(customer.contacts || []).length > 0 && (
                <div className="mt-4 pt-4 border-t" style={{ borderColor: "var(--border-secondary)" }}>
                  <div style={{ ...labelStyle, marginBottom: 8 }}>Contacts</div>
                  <div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: "4px 0" }}>
                    {customer.contacts.map((c, i) => (
                      <div key={i} style={{ display: "flex", alignItems: "center", flexShrink: 0 }}>
                        <div className="flex items-center gap-1.5 text-sm" style={{ padding: "2px 12px 2px 0" }}>
                          <span className="w-4 flex-shrink-0 text-center">{CONTACT_TYPE_ICONS[c.type] || "🔗"}</span>
                          <span style={{ color: "var(--text-muted)", flexShrink: 0, fontSize: 11 }}>{c.type}{c.label ? ` (${c.label})` : ""}</span>
                          <span style={{ color: "var(--text-primary)" }}>{c.value}</span>
                          {c.primary && (
                            <span className="px-1.5 py-0.5 text-xs rounded-full flex-shrink-0" style={{ backgroundColor: "var(--badge-blue-bg)", color: "var(--badge-blue-text)" }}>
                              Primary
                            </span>
                          )}
                        </div>
                        {i < customer.contacts.length - 1 && (
                          <span style={{ color: "var(--border-primary)", paddingRight: 12, fontSize: 16, lineHeight: 1 }}>|</span>
                        )}
                      </div>
                    ))}
                  </div>
                </div>
              )}

              {/* Notes */}
              <div className="mt-4 pt-4 border-t" style={{ borderColor: "var(--border-secondary)" }}>
                <div style={{ ...labelStyle, marginBottom: 8 }}>Notes</div>
                {(customer.notes || []).length > 0 ? (
                  <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                    {customer.notes.map((note, i) => (
                      <div key={i} className="px-3 py-2 rounded-md text-sm" style={{ backgroundColor: "var(--bg-primary)" }}>
                        <p style={{ color: "var(--text-primary)", whiteSpace: "pre-wrap" }}>{note.text}</p>
                        <p className="text-xs mt-1" style={{ color: "var(--text-muted)" }}>
                          {note.by} · {note.at ? new Date(note.at).toLocaleDateString() : ""}
                        </p>
                      </div>
                    ))}
                  </div>
                ) : (
                  <p style={{ fontSize: 13, color: "var(--text-muted)", fontStyle: "italic" }}>No notes.</p>
                )}
              </div>
            </div>

            {/* RIGHT: Status panel */}
            <div style={{ borderLeft: "1px solid var(--border-secondary)", paddingLeft: 30, display: "flex", flexDirection: "column", gap: 16 }}>
              <div style={{ fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.05em" }}>Status</div>

              {/* Negotiations subsection */}
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                <div style={{ fontSize: 10, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.04em" }}>Negotiations</div>
                {customer.negotiating && (
                  <div style={{
                    padding: "5px 10px", borderRadius: 8, fontSize: 11, fontWeight: 600, lineHeight: 1.3,
                    backgroundColor: "rgba(234,179,8,0.12)", color: "#a16207",
                    border: "1px solid rgba(234,179,8,0.35)",
                  }}>
                    {lastCommDirection === "inbound" ? "Ongoing – Pending Reply" : "Ongoing"}
                  </div>
                )}
                {canEdit && (
                  <button
                    onClick={handleToggleNegotiating}
                    disabled={statusToggling === "negotiating"}
                    style={{
                      padding: "6px 10px", fontSize: 11, fontWeight: 600, borderRadius: 6, cursor: "pointer", textAlign: "left",
                      border: "1px solid",
                      borderColor: customer.negotiating ? "rgba(234,179,8,0.5)" : "var(--border-primary)",
                      backgroundColor: customer.negotiating ? "rgba(234,179,8,0.08)" : "transparent",
                      color: customer.negotiating ? "#a16207" : "var(--text-secondary)",
                    }}
                  >
                    {statusToggling === "negotiating" ? "..." : customer.negotiating ? "End Negotiations" : "Start Negotiating"}
                  </button>
                )}
              </div>

              {/* Issue subsection */}
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                <div style={{ fontSize: 10, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.04em" }}>Issue</div>
                {customer.has_problem && (
                  <div style={{
                    padding: "5px 10px", borderRadius: 8, fontSize: 11, fontWeight: 600, lineHeight: 1.3,
                    backgroundColor: "rgba(239,68,68,0.10)", color: "#b91c1c",
                    border: "1px solid rgba(239,68,68,0.35)",
                  }}>
                    {lastCommDirection === "outbound" ? "Unsolved – Pending Reply" : "Unsolved Issue"}
                  </div>
                )}
                {canEdit && (
                  <button
                    onClick={handleToggleProblem}
                    disabled={statusToggling === "problem"}
                    style={{
                      padding: "6px 10px", fontSize: 11, fontWeight: 600, borderRadius: 6, cursor: "pointer", textAlign: "left",
                      border: "1px solid",
                      borderColor: customer.has_problem ? "rgba(239,68,68,0.5)" : "var(--border-primary)",
                      backgroundColor: customer.has_problem ? "rgba(239,68,68,0.08)" : "transparent",
                      color: customer.has_problem ? "#b91c1c" : "var(--text-secondary)",
                    }}
                  >
                    {statusToggling === "problem" ? "..." : customer.has_problem ? "Resolve Issue" : "Has Problem"}
                  </button>
                )}
              </div>
            </div>
          </div>

          {/* Full-width: Latest Communications */}
          <OverviewQuickSection
            title="Latest Communications"
            onViewAll={() => setActiveTab("Communication")}
            empty="No communications yet."
          >
            {[...comms].sort((a, b) => {
              const ta = Date.parse(a?.occurred_at || a?.created_at || "") || 0;
              const tb = Date.parse(b?.occurred_at || b?.created_at || "") || 0;
              if (tb !== ta) return tb - ta;
              return String(b?.id || "").localeCompare(String(a?.id || ""));
            }).slice(0, 5).map((entry) => {
              return (
                <div
                  key={entry.id}
                  className="flex items-center gap-2 text-sm py-2 border-b last:border-0 cursor-pointer hover:opacity-80"
                  style={{ borderColor: "var(--border-secondary)" }}
                  onClick={() => {
                    setActiveTab("Communication");
                    // After tab switch, expand this entry
                    setTimeout(() => setExpandedComms((prev) => ({ ...prev, [entry.id]: true })), 50);
                  }}
                >
                  <CommTypeIconBadge type={entry.type} />
                  <CommDirectionIcon direction={entry.direction} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <p className="font-medium" style={{ color: "var(--text-primary)" }}>
                      {entry.subject || <span style={{ color: "var(--text-muted)", fontStyle: "italic" }}>{COMM_TYPE_LABELS[entry.type] || entry.type}</span>}
                    </p>
                    {entry.body && (
                      <p className="text-xs mt-0.5" style={{ color: "var(--text-muted)", display: "-webkit-box", WebkitLineClamp: 3, WebkitBoxOrient: "vertical", overflow: "hidden", whiteSpace: "pre-wrap" }}>{entry.body}</p>
                    )}
                  </div>
                  <span className="text-xs" style={{ color: "var(--text-muted)", flexShrink: 0, marginLeft: 100, whiteSpace: "nowrap", alignSelf: "flex-start" }}>
                    {formatCommDate(entry.occurred_at)}
                  </span>
                </div>
              );
            })}
          </OverviewQuickSection>

          {/* Two-column row: Orders+Quotations left, Devices right */}
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
            {/* Left: Orders then Quotations */}
            <div>
              <OverviewQuickSection
                title="Latest Orders"
                onViewAll={() => setActiveTab("Orders")}
                empty="No orders yet."
              >
                {[...orders].sort((a, b) => new Date(b.created_at) - new Date(a.created_at)).slice(0, 3).map((o) => (
                  <div
                    key={o.id}
                    className="flex items-center gap-2 text-sm py-1.5 border-b last:border-0 cursor-pointer hover:opacity-80"
                    style={{ borderColor: "var(--border-secondary)" }}
                    onClick={() => navigate(`/crm/orders/${o.id}`)}
                  >
                    <span className="font-mono text-xs" style={{ color: "var(--text-heading)" }}>{o.order_number || o.id.slice(0, 8)}</span>
                    <span className="px-1.5 py-0.5 text-xs rounded-full capitalize" style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-secondary)" }}>{o.status}</span>
                    <span className="ml-auto" style={{ color: "var(--text-primary)" }}>€{Number(o.total_price || 0).toFixed(2)}</span>
                  </div>
                ))}
              </OverviewQuickSection>

              <OverviewQuickSection
                title="Latest Quotations"
                onViewAll={() => setActiveTab("Quotations")}
                empty="No quotations yet."
              >
                {latestQuotations.map((q) => (
                  <div
                    key={q.id}
                    className="flex items-center gap-2 text-sm py-1.5 border-b last:border-0 cursor-pointer hover:opacity-80"
                    style={{ borderColor: "var(--border-secondary)" }}
                    onClick={() => setActiveTab("Quotations")}
                  >
                    <span className="font-mono text-xs" style={{ color: "var(--text-heading)" }}>{q.quotation_number}</span>
                    <span className="px-1.5 py-0.5 text-xs rounded-full capitalize" style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-secondary)" }}>{q.status}</span>
                    <span className="ml-auto" style={{ color: "var(--text-primary)" }}>€{Number(q.final_total || 0).toFixed(2)}</span>
                  </div>
                ))}
              </OverviewQuickSection>
            </div>

            {/* Right: Devices */}
            <div>
              <OverviewQuickSection
                title="Devices"
                onViewAll={() => setActiveTab("Devices")}
                empty="No devices recorded."
              >
                {(customer.owned_items || []).map((item, i) => {
                  const matchedDevice = item.type === "console_device"
                    ? allDevices.find((d) => (d.device_id || d.id) === item.device_id)
                    : null;
                  return (
                    <div
                      key={i}
                      className="flex items-center gap-2 text-sm py-1.5 border-b last:border-0"
                      style={{
                        borderColor: "var(--border-secondary)",
                        cursor: item.type === "console_device" && matchedDevice ? "pointer" : "default",
                      }}
                      onClick={() => {
                        if (item.type === "console_device" && matchedDevice) {
                          navigate(`/devices/${matchedDevice.id || matchedDevice.device_id}`);
                        }
                      }}
                    >
                      {item.type === "console_device" && (
                        <>
                          <span style={{ color: "var(--text-primary)", fontWeight: 500 }}>{item.label || matchedDevice?.device_name || item.device_id}</span>
                          {matchedDevice?.device_type && (
                            <span style={{ color: "var(--text-muted)" }}>· {matchedDevice.device_type}</span>
                          )}
                          <span className="font-mono text-xs flex-shrink-0" style={{ color: "var(--text-muted)" }}>· {item.device_id}</span>
                        </>
                      )}
                      {item.type === "product" && (
                        <>
                          <span style={{ color: "var(--text-primary)" }}>{item.product_name || item.product_id}</span>
                          <span className="text-xs ml-auto" style={{ color: "var(--text-muted)" }}>× {item.quantity || 1}</span>
                        </>
                      )}
                      {item.type === "freetext" && (
                        <span style={{ color: "var(--text-primary)" }}>{item.description}</span>
                      )}
                    </div>
                  );
                })}
              </OverviewQuickSection>
            </div>
          </div>
        </div>
      )}

      {/* Support Tab */}
      {activeTab === "Support" && (
        <SupportTab
          customer={customer}
          canEdit={canEdit}
          onCustomerUpdated={setCustomer}
          user={user}
        />
      )}

      {/* Finance Tab */}
      {activeTab === "Finance" && (
        <FinancialsTab
          customer={customer}
          orders={orders}
          canEdit={canEdit}
          onCustomerUpdated={setCustomer}
          onReloadOrders={loadOrders}
          user={user}
          onTabChange={handleTabChange}
        />
      )}

      {/* Orders Tab */}
      {activeTab === "Orders" && (
        <OrdersTab
          customerId={id}
          orders={orders}
          ordersLoading={ordersLoading}
          canEdit={canEdit}
          user={user}
          onReload={loadOrders}
          expandOrderId={expandOrderId}
          onExpandOrderIdConsumed={() => setExpandOrderId(null)}
        />
      )}

      {/* Quotations Tab */}
      {activeTab === "Quotations" && (
        <QuotationList
          customerId={customer.id}
          onSend={(quotation) => {
            const primaryEmail = (customer?.contacts || []).find((c) => c.type === "email" && c.primary)?.value || "";
            setComposeEmailTo(primaryEmail);
            setComposeEmailSubject("BellSystems Quotation");
            setComposeEmailFromAccount("sales");
            setComposeDefaultServerAttachments(
              quotation?.nextcloud_pdf_path
                ? [{ path: quotation.nextcloud_pdf_path, filename: `${quotation.quotation_number || "quotation"}.pdf`, mime_type: "application/pdf" }]
                : []
            );
            setComposeEmailOpen(true);
          }}
        />
      )}

      {/* Communication Tab */}
      {activeTab === "Communication" && (
        <div>
          <div className="flex items-center justify-between mb-4">
            <span className="text-sm" style={{ color: "var(--text-muted)" }}>{comms.length} entr{comms.length !== 1 ? "ies" : "y"}</span>
            {canEdit && (
              <div className="flex gap-2">
                <button
                  onClick={() => {
                    const primaryEmail = (customer?.contacts || []).find((c) => c.type === "email" && c.primary)?.value || "";
                    setComposeEmailTo(primaryEmail);
                    setComposeEmailSubject("");
                    setComposeEmailFromAccount("");
                    setComposeDefaultServerAttachments([]);
                    setComposeEmailOpen(true);
                  }}
                  className="px-3 py-1.5 text-sm rounded-md cursor-pointer hover:opacity-90"
                  style={{ border: "1px solid var(--border-primary)", color: "var(--text-secondary)", backgroundColor: "var(--bg-card)" }}
                >
                  ✉ Compose Email
                </button>
                <button
                  onClick={() => { setShowCommsForm((v) => !v); setShowEmailCompose(false); }}
                  className="px-3 py-1.5 text-sm rounded-md cursor-pointer hover:opacity-90"
                  style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
                >
                  {showCommsForm ? "Cancel" : "Log Entry"}
                </button>
              </div>
            )}
          </div>

          {/* Quick Reply inline (replaces the old Compose Email inline) */}
          {showEmailCompose && (() => {
            const primaryEmail = (customer?.contacts || []).find((c) => c.type === "email" && c.primary)?.value || "";
            return (
              <div className="rounded-lg border p-4 mb-4" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}>
                <p className="text-xs font-semibold mb-3" style={{ color: "var(--text-muted)" }}>QUICK REPLY</p>
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 12 }}>
                  <Field label="To">
                    <input
                      className={inputClass} style={inputStyle}
                      value={emailForm.to || primaryEmail}
                      onChange={(e) => setEmailForm((f) => ({ ...f, to: e.target.value }))}
                      placeholder={primaryEmail || "recipient@example.com"}
                    />
                  </Field>
                  <Field label="CC (comma-separated)">
                    <input
                      className={inputClass} style={inputStyle}
                      value={emailForm.cc}
                      onChange={(e) => setEmailForm((f) => ({ ...f, cc: e.target.value }))}
                      placeholder="cc@example.com, ..."
                    />
                  </Field>
                </div>
                <Field label="Subject">
                  <input
                    className={inputClass} style={{ ...inputStyle, marginBottom: 8 }}
                    value={emailForm.subject}
                    onChange={(e) => setEmailForm((f) => ({ ...f, subject: e.target.value }))}
                    placeholder="Subject"
                  />
                </Field>
                <Field label="Body">
                  <textarea
                    className={inputClass} style={{ ...inputStyle, resize: "vertical", minHeight: 120, marginBottom: 12 }}
                    value={emailForm.body}
                    onChange={(e) => setEmailForm((f) => ({ ...f, body: e.target.value }))}
                    placeholder="Write your message..."
                  />
                </Field>
                {emailError && (
                  <p className="text-xs mb-3" style={{ color: "var(--danger-text)" }}>{emailError}</p>
                )}
                <div className="flex items-center gap-2">
                  <button
                    onClick={handleSendEmail}
                    disabled={emailSending || !emailForm.subject || !emailForm.body}
                    className="px-4 py-2 text-sm rounded-md cursor-pointer hover:opacity-90"
                    style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)", opacity: (emailSending || !emailForm.subject || !emailForm.body) ? 0.6 : 1 }}
                  >
                    {emailSending ? "Sending..." : "Send"}
                  </button>
                  <button
                    onClick={() => setShowEmailCompose(false)}
                    className="px-3 py-2 text-sm rounded-md cursor-pointer hover:opacity-80"
                    style={{ border: "1px solid var(--border-primary)", color: "var(--text-secondary)", backgroundColor: "var(--bg-card)" }}
                  >
                    Cancel
                  </button>
                </div>
              </div>
            );
          })()}

          {showCommsForm && (
            <div className="rounded-lg border p-4 mb-4" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}>
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12, marginBottom: 12 }}>
                <Field label="Type">
                  <select className={inputClass} style={inputStyle} value={commsForm.type}
                    onChange={(e) => setCommsForm((f) => ({ ...f, type: e.target.value }))}>
                    {COMMS_TYPES.map((t) => <option key={t} value={t}>{t}</option>)}
                  </select>
                </Field>
                <Field label="Direction">
                  <select className={inputClass} style={inputStyle} value={commsForm.direction}
                    onChange={(e) => setCommsForm((f) => ({ ...f, direction: e.target.value }))}>
                    {COMMS_DIRECTIONS.map((d) => <option key={d} value={d}>{d}</option>)}
                  </select>
                </Field>
                <Field label="Date/Time">
                  <input type="datetime-local" className={inputClass} style={inputStyle}
                    value={commsForm.occurred_at}
                    onChange={(e) => setCommsForm((f) => ({ ...f, occurred_at: e.target.value }))} />
                </Field>
              </div>
              <Field label="Subject">
                <input className={inputClass} style={{ ...inputStyle, marginBottom: 8 }} value={commsForm.subject}
                  onChange={(e) => setCommsForm((f) => ({ ...f, subject: e.target.value }))} placeholder="Subject (optional)" />
              </Field>
              <Field label="Body">
                <textarea className={inputClass} style={{ ...inputStyle, resize: "vertical", minHeight: 80, marginBottom: 12 }}
                  value={commsForm.body}
                  onChange={(e) => setCommsForm((f) => ({ ...f, body: e.target.value }))} placeholder="Message body..." />
              </Field>
              <button
                onClick={handleAddComms}
                disabled={commsSaving}
                className="px-4 py-2 text-sm rounded-md cursor-pointer hover:opacity-90"
                style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)", opacity: commsSaving ? 0.7 : 1 }}
              >
                {commsSaving ? "Saving..." : "Save Entry"}
              </button>
            </div>
          )}

          {commsLoading ? (
            <div className="text-center py-8" style={{ color: "var(--text-muted)" }}>Loading...</div>
          ) : comms.length === 0 ? (
            <div className="rounded-lg p-8 text-center text-sm border" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)", color: "var(--text-muted)" }}>
              No communications logged yet.
            </div>
          ) : (
            <div style={{ position: "relative" }}>
              {/* Timeline connector line */}
              <div style={{ position: "absolute", left: 19, top: 12, bottom: 12, width: 2, backgroundColor: "var(--border-secondary)", zIndex: 0 }} />
              <div className="space-y-2">
                {[...comms].sort((a, b) => {
                  const ta = Date.parse(a?.occurred_at || a?.created_at || "") || 0;
                  const tb = Date.parse(b?.occurred_at || b?.created_at || "") || 0;
                  if (tb !== ta) return tb - ta;
                  return String(b?.id || "").localeCompare(String(a?.id || ""));
                }).map((entry) => {
                  const isExpanded = !!expandedComms[entry.id];
                  const hasBody = !!entry.body;
                  const isEmail = entry.type === "email";
                  const isPendingDelete = commsDeleteId === entry.id;
                  const isEditing = commsEditId === entry.id;
                  const isHovered = commsHoverId === entry.id;

                  return (
                    <div key={entry.id} style={{ position: "relative", paddingLeft: 44 }}
                      onMouseEnter={() => setCommsHoverId(entry.id)}
                      onMouseLeave={() => setCommsHoverId(null)}>
                      {/* Type icon marker */}
                      <div style={{ position: "absolute", left: 8, top: 11, zIndex: 1 }}>
                        <CommTypeIconBadge type={entry.type} />
                      </div>

                      <div
                        className="rounded-lg border"
                        style={{
                          backgroundColor: "var(--bg-card)",
                          borderColor: isPendingDelete ? "var(--danger)" : isEditing ? "var(--accent)" : "var(--border-primary)",
                          cursor: hasBody && !isEditing ? "pointer" : "default",
                          position: "relative",
                          overflow: "hidden",
                        }}
                        onClick={() => !isEditing && hasBody && toggleComm(entry.id)}
                      >
                        {/* Hover overlay: gradient + 3-col action panel (no layout shift) */}
                        {isHovered && !isPendingDelete && !isEditing && (
                          <div style={{ position: "absolute", inset: 0, pointerEvents: "none", zIndex: 2, background: "linear-gradient(to left, rgba(24, 35, 48, 0.95) 0%, rgba(31, 41, 55, 0.2) 32%, transparent 45%)", borderRadius: "inherit" }}>
                            <div style={{ position: "absolute", right: 12, top: "50%", transform: "translateY(-50%)", display: "flex", flexDirection: "row", alignItems: "center", gap: 10, pointerEvents: "all" }}>

                              {/* Col 1 — date info */}
                              <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 3 }}>
                                <span style={{ fontSize: 10, color: "rgba(255,255,255,0.55)", whiteSpace: "nowrap", lineHeight: 1.4 }}>
                                  {entry.direction === "inbound" ? "Received" : entry.direction === "outbound" ? "Sent" : "Logged"} via {COMM_TYPE_LABELS[entry.type] || entry.type}
                                </span>
                                <span style={{ fontSize: 10, color: "rgba(255,255,255,0.9)", whiteSpace: "nowrap", lineHeight: 1.4 }}>
                                  {formatFullDateTime(entry.occurred_at)}
                                </span>
                              </div>

                              {/* Divider */}
                              <div style={{ width: 1, alignSelf: "stretch", backgroundColor: "rgba(255,255,255,0.18)", flexShrink: 0, margin: "2px 0" }} />

                              {/* Col 2 — Full View / Reply */}
                              <div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
                                <button type="button" onClick={(e) => { e.stopPropagation(); isEmail && setCommsViewEntry(entry); }}
                                  style={{ display: "flex", alignItems: "center", gap: 5, width: 90, justifyContent: "center", padding: "4px 0", fontSize: 11, borderRadius: 5, cursor: isEmail ? "pointer" : "default", backdropFilter: "blur(4px)", whiteSpace: "nowrap", color: isEmail ? "#fff" : "rgba(255,255,255,0.3)", backgroundColor: isEmail ? "rgba(255,255,255,0.12)" : "transparent", border: `1px solid ${isEmail ? "rgba(255,255,255,0.25)" : "rgba(255,255,255,0.1)"}` }}>
                                  <IconExpand /><span>Full View</span>
                                </button>
                                <button type="button" onClick={(e) => {
                                  e.stopPropagation();
                                  const replyTo = entry.direction === "inbound" ? (entry.from_addr || "") : (Array.isArray(entry.to_addrs) ? entry.to_addrs[0] : "");
                                  setShowEmailCompose(true);
                                  setEmailForm((f) => ({ ...f, to: replyTo, subject: `Re: ${entry.subject || ""}` }));
                                  setShowCommsForm(false);
                                }}
                                  style={{ display: "flex", alignItems: "center", gap: 5, width: 90, justifyContent: "center", padding: "4px 0", fontSize: 11, borderRadius: 5, cursor: "pointer", backdropFilter: "blur(4px)", whiteSpace: "nowrap", color: "#fff", backgroundColor: "rgba(255,255,255,0.12)", border: "1px solid rgba(255,255,255,0.25)" }}>
                                  <IconReply /><span>Reply</span>
                                </button>
                              </div>

                              {/* Col 3 — Edit / Delete (canEdit only) */}
                              {canEdit && (
                                <div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
                                  <button type="button" onClick={(e) => { e.stopPropagation(); startEditComm(entry); }}
                                    style={{ display: "flex", alignItems: "center", gap: 5, width: 90, justifyContent: "center", padding: "4px 0", fontSize: 11, borderRadius: 5, cursor: "pointer", backdropFilter: "blur(4px)", whiteSpace: "nowrap", color: "#fff", backgroundColor: "rgba(255,255,255,0.12)", border: "1px solid rgba(255,255,255,0.25)" }}>
                                    <IconEdit /><span>Edit</span>
                                  </button>
                                  <button type="button" onClick={(e) => { e.stopPropagation(); setCommsDeleteId(entry.id); }}
                                    style={{ display: "flex", alignItems: "center", gap: 5, width: 90, justifyContent: "center", padding: "4px 0", fontSize: 11, borderRadius: 5, cursor: "pointer", backdropFilter: "blur(4px)", whiteSpace: "nowrap", color: "#fff", backgroundColor: "rgba(185,28,28,0.7)", border: "1px solid rgba(220,38,38,0.5)" }}>
                                    <IconDelete /><span>Delete</span>
                                  </button>
                                </div>
                              )}

                            </div>
                          </div>
                        )}

                        {/* Header row - order: direction icon, subject */}
                        <div className="flex items-center gap-2 px-4 py-3">
                          <CommDirectionIcon direction={entry.direction} />
                          {entry.subject && (
                            <span className="text-sm font-medium" style={{ color: "var(--text-heading)", flex: 1, minWidth: 0 }}>
                              {entry.subject}
                            </span>
                          )}
                          <div className="flex items-center gap-2" style={{ flexShrink: 0, marginLeft: entry.subject ? 100 : "auto" }}>
                            <span className="text-xs" style={{ color: "var(--text-muted)", whiteSpace: "nowrap" }}>
                              {formatRelativeTime(entry.occurred_at)}
                            </span>
                          </div>
                        </div>

                        {/* Body — divider indented, no second line before footer */}
                        {hasBody && (
                          <div className="pb-3" style={{ paddingLeft: 16, paddingRight: 16 }}>
                            <div style={{ borderTop: "1px solid var(--border-secondary)" }} />
                            <p
                              className="text-sm mt-2"
                              style={{
                                color: "var(--text-primary)",
                                display: "-webkit-box",
                                WebkitLineClamp: isExpanded ? "unset" : 3,
                                WebkitBoxOrient: "vertical",
                                overflow: isExpanded ? "visible" : "hidden",
                                whiteSpace: "pre-wrap",
                              }}
                            >
                              {entry.body}
                            </p>
                          </div>
                        )}

                        {/* Footer */}
                        {(entry.logged_by || (entry.attachments?.length > 0) || isPendingDelete) && (
                          <div className="px-4 pb-3 flex items-center gap-3 flex-wrap">
                            {entry.logged_by && (
                              <span className="text-xs" style={{ color: "var(--text-muted)" }}>by {entry.logged_by}</span>
                            )}
                            {entry.attachments?.length > 0 && (
                              <span className="text-xs" style={{ color: "var(--text-muted)" }}>
                                📎 {entry.attachments.length} attachment{entry.attachments.length !== 1 ? "s" : ""}
                              </span>
                            )}

                            {/* Delete confirmation */}
                            {isPendingDelete && (
                              <div className="flex items-center gap-2 ml-auto">
                                <span className="text-xs" style={{ color: "var(--danger-text)" }}>
                                  Delete this entry?
                                </span>
                                <button
                                  type="button"
                                  disabled={commsDeleting}
                                  onClick={(e) => { e.stopPropagation(); handleDeleteComm(entry.id); }}
                                  className="text-xs px-2 py-1 rounded cursor-pointer hover:opacity-90"
                                  style={{ backgroundColor: "var(--danger)", color: "#fff", opacity: commsDeleting ? 0.7 : 1 }}
                                >
                                  {commsDeleting ? "..." : "Confirm"}
                                </button>
                                <button
                                  type="button"
                                  onClick={(e) => { e.stopPropagation(); setCommsDeleteId(null); }}
                                  className="text-xs px-2 py-1 rounded cursor-pointer hover:opacity-80"
                                  style={{ border: "1px solid var(--border-primary)", color: "var(--text-secondary)" }}
                                >
                                  Cancel
                                </button>
                              </div>
                            )}
                          </div>
                        )}

                        {/* Inline edit form */}
                        {isEditing && (
                          <div className="px-4 pb-4 border-t" style={{ borderColor: "var(--border-secondary)" }} onClick={e => e.stopPropagation()}>
                            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10, marginTop: 12, marginBottom: 10 }}>
                              <div>
                                <div style={{ fontSize: 11, color: "var(--text-muted)", marginBottom: 4 }}>Type</div>
                                <select value={commsEditForm.type} onChange={e => setCommsEditForm(f => ({...f, type: e.target.value}))}
                                  className="w-full px-2 py-1.5 text-xs rounded-md border"
                                  style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }}>
                                  {["email","whatsapp","call","sms","note","in_person"].map(t => <option key={t} value={t}>{t}</option>)}
                                </select>
                              </div>
                              <div>
                                <div style={{ fontSize: 11, color: "var(--text-muted)", marginBottom: 4 }}>Direction</div>
                                <select value={commsEditForm.direction} onChange={e => setCommsEditForm(f => ({...f, direction: e.target.value}))}
                                  className="w-full px-2 py-1.5 text-xs rounded-md border"
                                  style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }}>
                                  {["inbound","outbound","internal"].map(d => <option key={d} value={d}>{d}</option>)}
                                </select>
                              </div>
                              <div>
                                <div style={{ fontSize: 11, color: "var(--text-muted)", marginBottom: 4 }}>Date &amp; Time</div>
                                <input type="datetime-local" value={commsEditForm.occurred_at}
                                  onChange={e => setCommsEditForm(f => ({...f, occurred_at: e.target.value}))}
                                  className="w-full px-2 py-1.5 text-xs rounded-md border"
                                  style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }} />
                              </div>
                            </div>
                            <div style={{ marginBottom: 8 }}>
                              <div style={{ fontSize: 11, color: "var(--text-muted)", marginBottom: 4 }}>Subject</div>
                              <input value={commsEditForm.subject} onChange={e => setCommsEditForm(f => ({...f, subject: e.target.value}))}
                                className="w-full px-2 py-1.5 text-xs rounded-md border"
                                style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }} />
                            </div>
                            <div style={{ marginBottom: 8 }}>
                              <div style={{ fontSize: 11, color: "var(--text-muted)", marginBottom: 4 }}>Body</div>
                              <textarea value={commsEditForm.body} onChange={e => setCommsEditForm(f => ({...f, body: e.target.value}))}
                                rows={3} className="w-full px-2 py-1.5 text-xs rounded-md border resize-none"
                                style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }} />
                            </div>
                            <div style={{ marginBottom: 12 }}>
                              <div style={{ fontSize: 11, color: "var(--text-muted)", marginBottom: 4 }}>Logged By</div>
                              <input value={commsEditForm.logged_by} onChange={e => setCommsEditForm(f => ({...f, logged_by: e.target.value}))}
                                className="w-full px-2 py-1.5 text-xs rounded-md border"
                                style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }} />
                            </div>
                            <div className="flex gap-2 justify-end">
                              <button type="button" onClick={(e) => { e.stopPropagation(); setCommsEditId(null); }}
                                className="text-xs px-3 py-1.5 rounded-md cursor-pointer hover:opacity-80"
                                style={{ border: "1px solid var(--border-primary)", color: "var(--text-secondary)", backgroundColor: "var(--bg-input)" }}>
                                Cancel
                              </button>
                              <button type="button" disabled={commsEditSaving} onClick={(e) => { e.stopPropagation(); handleSaveEditComm(); }}
                                className="text-xs px-3 py-1.5 rounded-md cursor-pointer hover:opacity-90"
                                style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)", border: "none", opacity: commsEditSaving ? 0.6 : 1 }}>
                                {commsEditSaving ? "Saving…" : "Save"}
                              </button>
                            </div>
                          </div>
                        )}
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>
          )}

          {/* Mail View Modal */}
          <MailViewModal
            open={!!commsViewEntry}
            onClose={() => setCommsViewEntry(null)}
            entry={commsViewEntry}
            customerName={customer?.name}
            onReply={(toAddr) => {
              setCommsViewEntry(null);
              setShowEmailCompose(true);
              setEmailForm((f) => ({ ...f, to: toAddr, subject: `Re: ${commsViewEntry?.subject || ""}` }));
              setShowCommsForm(false);
            }}
          />

          {/* Compose Email Modal */}
          <ComposeEmailModal
            open={composeEmailOpen}
            onClose={() => {
              setComposeEmailOpen(false);
              setComposeEmailSubject("");
              setComposeEmailFromAccount("");
              setComposeDefaultServerAttachments([]);
            }}
            defaultTo={composeEmailTo}
            defaultSubject={composeEmailSubject}
            defaultFromAccount={composeEmailFromAccount}
            requireFromAccount={true}
            defaultServerAttachments={composeDefaultServerAttachments}
            customerId={id}
            onSent={() => loadComms()}
          />
        </div>
      )}

      {/* Media Tab */}
      {activeTab === "Files & Media" && (() => {
        // ncThumbMapState: null = NC browse still in flight, {} or {key→path} = done
        const thumbMapReady = ncThumbMapState !== null;
        const ncThumbMap = ncThumbMapState || {};

        // Build NC file map (non-thumb files only)
        const ncFileMap = {};
        for (const f of ncFiles) {
          if (f.path.includes("/.thumbs/")) continue; // handled via ncThumbMapState
          const rawSub = f.subfolder || (f.path.split("/")[2] ?? "other");
          const sub = LEGACY_SUBFOLDER_MAP[rawSub] || rawSub;
          ncFileMap[f.path] = { ...f, subfolder: sub, tracked: false, nc_missing: false };
        }

        const dbMap = {};
        for (const m of media) dbMap[m.nextcloud_path] = m;

        // Mark NC files as tracked if a DB record exists
        for (const path of Object.keys(ncFileMap)) {
          if (dbMap[path]) ncFileMap[path].tracked = true;
        }

        // PRIMARY SOURCE: DB records — show immediately on page load.
        // NC files that aren't in DB yet will be added below as untracked.
        const allFiles = [];
        for (const m of media) {
          const parts = (m.nextcloud_path || "").split("/");
          const rawSub = parts.length >= 3 ? parts[2] : "other";
          const sub = LEGACY_SUBFOLDER_MAP[rawSub] || rawSub;
          const ncEntry = ncFileMap[m.nextcloud_path];
          // Resolve thumbnail: DB record first, then ncThumbMap (once loaded)
          let thumbnail_path = m.thumbnail_path || null;
          if (!thumbnail_path && thumbMapReady && m.nextcloud_path) {
            thumbnail_path = ncThumbMap[m.nextcloud_path] || null;
          }
          allFiles.push({
            filename: m.filename,
            path: m.nextcloud_path,
            mime_type: m.mime_type,
            size: ncEntry ? ncEntry.size : 0,
            last_modified: ncEntry ? ncEntry.last_modified : m.created_at,
            is_dir: false,
            subfolder: sub,
            tracked: true,
            nc_missing: thumbMapReady && !ncEntry,
            db_id: m.id,
            db_tags: m.tags || [],
            db_direction: m.direction,
            thumbnail_path,
            thumbMapReady, // passed so tile knows whether to show placeholder
          });
        }

        // Add NC-only files (untracked — not in DB yet)
        // Only show once NC browse is done to avoid empty-flash while loading.
        if (thumbMapReady) {
          for (const f of Object.values(ncFileMap)) {
            if (!dbMap[f.path]) {
              const thumbnail_path = ncThumbMap[f.path] || null;
              allFiles.push({ ...f, tracked: false, nc_missing: false, thumbnail_path, thumbMapReady });
            }
          }
        }

        const untrackedCount = allFiles.filter(f => !f.tracked && !f.nc_missing).length;
        const ncMissingCount = allFiles.filter(f => f.nc_missing).length;

        const TYPE_ORDER = ["video/", "image/", "audio/", "application/pdf", "text/"];
        const mimeRank = (mime) => {
          const i = TYPE_ORDER.findIndex(t => (mime || "").startsWith(t));
          return i === -1 ? TYPE_ORDER.length : i;
        };
        const sortFn = (a, b) => {
          if (mediaSort === "name_asc") return (a.filename || "").localeCompare(b.filename || "");
          if (mediaSort === "name_desc") return (b.filename || "").localeCompare(a.filename || "");
          if (mediaSort === "size_asc") return (a.size || 0) - (b.size || 0);
          if (mediaSort === "size_desc") return (b.size || 0) - (a.size || 0);
          if (mediaSort === "type_date") {
            const rankA = mimeRank(a.mime_type), rankB = mimeRank(b.mime_type);
            if (rankA !== rankB) return rankA - rankB;
            return new Date(b.last_modified || 0).getTime() - new Date(a.last_modified || 0).getTime();
          }
          const da = new Date(a.last_modified || 0).getTime();
          const db2 = new Date(b.last_modified || 0).getTime();
          return mediaSort === "date_asc" ? da - db2 : db2 - da;
        };

        // Filter + sort
        const filtered = allFiles
          .filter(f => {
            // Multi-select category filter
            if (mediaFilterTypes.size > 0 && !mediaFilterTypes.has(f.subfolder)) return false;
            return true;
          })
          .filter(f => {
            // Photos/Videos only filter
            if (mediaTypeFilter === "photos") return f.mime_type?.startsWith("image/");
            if (mediaTypeFilter === "videos") return f.mime_type?.startsWith("video/");
            return true;
          })
          .filter(f => {
            if (!mediaSearch) return true;
            const q = mediaSearch.toLowerCase();
            const name = (f.filename || "").toLowerCase();
            const ext = name.includes(".") ? name.split(".").pop() : "";
            return name.includes(q) || ext === q;
          })
          .sort(sortFn);

        // Pagination
        const totalFiltered = filtered.length;
        const totalPages = mediaPageSize > 0 ? Math.ceil(totalFiltered / mediaPageSize) : 1;
        const safeCurrentPage = Math.min(mediaPage, Math.max(1, totalPages));
        const paginatedFiles = mediaPageSize > 0
          ? filtered.slice((safeCurrentPage - 1) * mediaPageSize, safeCurrentPage * mediaPageSize)
          : filtered;

        // Group view: only active when no filters active
        const showGroupView = mediaFilterTypes.size === 0 && mediaTypeFilter === "all" && groupView;
        const groupedSections = showGroupView
          ? CATEGORY_GROUPS.map(g => ({
              ...g,
              files: paginatedFiles.filter(f => g.values.includes(f.subfolder)).sort(sortFn),
            })).filter(g => g.files.length > 0)
          : null;

        const isPreviewable = (mime, filename) => mime && (
          mime.startsWith("image/") || mime.startsWith("video/") ||
          mime.startsWith("audio/") ||
          mime === "application/pdf" || mime === "text/plain" ||
          (filename || "").endsWith(".txt")
        );

        // Browser-native requests (img src, video src, a href) can't send Authorization headers,
        // so we pass the JWT as a query param which the backend also accepts.
        const ncToken = localStorage.getItem("access_token") || "";
        const ncFileUrl = (path) =>
          `/api/crm/nextcloud/file?path=${encodeURIComponent(path)}&token=${encodeURIComponent(ncToken)}`;

        // Track previewable files for prev/next navigation in the preview modal
        previewListRef.current = paginatedFiles.filter(f => isPreviewable(f.mime_type, f.filename) && !f.nc_missing);

        // Render a single file tile
        const renderTile = (f) => {
          const proxyUrl = ncFileUrl(f.path);
          const thumbUrl = f.thumbnail_path ? ncFileUrl(f.thumbnail_path) : null;
          const isImg = f.mime_type?.startsWith("image/");
          const isVideo = f.mime_type?.startsWith("video/");
          const isPdf = f.mime_type === "application/pdf";
          const canPreview = isPreviewable(f.mime_type, f.filename);
          const isSelected = selectedPaths.has(f.path);
          const isHovered = tileHovered === f.path;
          const showCheckbox = isSelected || isHovered || selectedPaths.size > 0;
          const cat = getCategoryMeta(f.subfolder);
          const borderColor = isSelected
            ? "var(--accent)"
            : f.nc_missing ? "var(--danger)"
            : !f.tracked ? "#f97316"
            : cat.hue.border;
          const borderStyle = (f.nc_missing || !f.tracked) ? "dashed" : "solid";
          const toggleSelect = (e) => {
            e.stopPropagation();
            setSelectedPaths(prev => {
              const next = new Set(prev);
              next.has(f.path) ? next.delete(f.path) : next.add(f.path);
              return next;
            });
          };
          const dateStr = f.last_modified ? new Date(f.last_modified).toLocaleDateString("en-GB", { day: "2-digit", month: "2-digit", year: "2-digit" }) : "";
          return (
            <div
              key={f.path}
              onMouseEnter={() => setTileHovered(f.path)}
              onMouseLeave={() => setTileHovered(null)}
              style={{
                borderRadius: 8, border: `2px ${borderStyle} ${borderColor}`,
                backgroundColor: isSelected ? "color-mix(in srgb, var(--accent) 8%, var(--bg-card))" : "var(--bg-card)",
                overflow: "hidden", position: "relative",
                display: "flex", flexDirection: "column",
                transition: "border-color 0.15s",
              }}
            >
              {/* Thumbnail */}
              <div
                onClick={() => {
                  if (selectedPaths.size > 0) { toggleSelect({ stopPropagation: () => {} }); return; }
                  if (canPreview && !f.nc_missing) setPreviewFile({ path: f.path, mime_type: f.mime_type, filename: f.filename, db_id: f.db_id });
                }}
                style={{
                  height: 160, display: "flex", alignItems: "center", justifyContent: "center",
                  backgroundColor: "var(--bg-primary)", position: "relative",
                  cursor: (selectedPaths.size > 0 || (canPreview && !f.nc_missing)) ? "pointer" : "default",
                  overflow: "hidden",
                }}
              >
                {(isImg || isVideo || isPdf) && !f.nc_missing ? (
                  <>
                    {!f.thumbMapReady ? (
                      // NC browse still in flight — show placeholder, load nothing
                      <div style={{ width: "100%", height: "100%", backgroundColor: "var(--bg-secondary)" }} />
                    ) : thumbUrl ? (
                      // Thumb exists — load only the small thumbnail
                      <img src={thumbUrl} alt={f.filename} style={{ width: "100%", height: "100%", objectFit: "cover" }} />
                    ) : isPdf ? (
                      // PDF, no thumb — render in-browser as last resort
                      <PdfThumbnail url={proxyUrl} />
                    ) : isVideo ? (
                      // Video, no thumb yet — show icon placeholder (can't use <img> for video)
                      <span style={{ fontSize: 42 }}>{MIME_ICON(f.mime_type)}</span>
                    ) : (
                      // Image, no thumb — fall back to full file
                      <img src={proxyUrl} alt={f.filename} style={{ width: "100%", height: "100%", objectFit: "cover" }} />
                    )}
                    {isVideo && (
                      <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", pointerEvents: "none" }}>
                        <IconVideo size={38} style={{ color: isHovered ? "#ef4444" : "#def1ff", transition: "color 0.15s", filter: "drop-shadow(0 0 8px rgba(0,0,0,0.9)) drop-shadow(0 0 2px rgba(0,0,0,1))" }} />
                      </div>
                    )}
                  </>
                ) : (
                  <span style={{ fontSize: 42 }}>{MIME_ICON(f.mime_type)}</span>
                )}

                {/* Status badges */}
                {f.nc_missing && (
                  <div style={{ position: "absolute", top: 4, left: 4, fontSize: 10, padding: "2px 6px", borderRadius: 4, backgroundColor: "var(--danger)", color: "#fff", fontWeight: 600 }}>Missing</div>
                )}
                {!f.tracked && !f.nc_missing && (
                  <div style={{ position: "absolute", top: 4, left: 4, fontSize: 10, padding: "2px 6px", borderRadius: 4, backgroundColor: "#f97316", color: "#fff", fontWeight: 600 }}>Untracked</div>
                )}

                {/* Checkbox — top-right */}
                {showCheckbox && (
                  <div
                    onClick={toggleSelect}
                    style={{
                      position: "absolute", top: 6, right: 6, zIndex: 10,
                      width: 20, height: 20, borderRadius: 4, cursor: "pointer",
                      border: `2px solid ${isSelected ? "var(--accent)" : "rgba(255,255,255,0.8)"}`,
                      backgroundColor: isSelected ? "var(--accent)" : "rgba(0,0,0,0.35)",
                      display: "flex", alignItems: "center", justifyContent: "center",
                    }}
                  >
                    {isSelected && <span style={{ color: "#fff", fontSize: 12, lineHeight: 1 }}>✓</span>}
                  </div>
                )}
                {/* Action icons visible on hover — top-left (opposite side of checkbox) */}
                {isHovered && !f.nc_missing && selectedPaths.size === 0 && (
                  <div style={{
                    position: "absolute", top: 6, left: 6, zIndex: 10,
                    display: "flex", gap: 4,
                  }}>
                    <a
                      href={proxyUrl}
                      download={f.filename}
                      onClick={e => e.stopPropagation()}
                      title="Download"
                      style={{
                        width: 28, height: 28, borderRadius: 6, display: "flex", alignItems: "center", justifyContent: "center",
                        backgroundColor: "rgba(34,197,94,0.85)", color: "#fff", textDecoration: "none", fontSize: 14,
                        backdropFilter: "blur(4px)",
                      }}
                    >↓</a>
                    {canEdit && (
                      <button
                        onClick={e => { e.stopPropagation(); handleDeleteMedia(f.db_id || "__nc_only__", f.path); }}
                        disabled={!!mediaDeleting[f.db_id]}
                        title="Delete"
                        style={{
                          width: 28, height: 28, borderRadius: 6, display: "flex", alignItems: "center", justifyContent: "center",
                          backgroundColor: "rgba(239,68,68,0.85)", color: "#fff", border: "none", cursor: "pointer", fontSize: 14,
                          backdropFilter: "blur(4px)",
                        }}
                      >{mediaDeleting[f.db_id] ? "…" : "✕"}</button>
                    )}
                  </div>
                )}
              </div>

              {/* Info row — clickable to open preview */}
              <div
                style={{ padding: "6px 8px", display: "flex", alignItems: "center", gap: 8, cursor: (canPreview && !f.nc_missing) ? "pointer" : "default" }}
                onClick={() => {
                  if (canPreview && !f.nc_missing) setPreviewFile({ path: f.path, mime_type: f.mime_type, filename: f.filename, db_id: f.db_id });
                }}
              >
                <span style={{ fontSize: 20, flexShrink: 0, lineHeight: 1 }}>{MIME_ICON(f.mime_type)}</span>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 12, fontWeight: 500, color: "var(--text-heading)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={f.filename}>
                    {f.filename}
                  </div>
                  <div style={{ fontSize: 11, color: "var(--text-muted)", marginTop: 2 }}>
                    {f.size > 0 ? formatFileSize(f.size) : ""}
                    {f.size > 0 && dateStr ? " · " : ""}{dateStr}
                  </div>
                </div>
              </div>
            </div>
          );
        };

        // Render tile grid (optionally grouped)
        const renderTileGrid = (files) => (
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(190px, 1fr))", gap: 10 }}>
            {files.map(f => renderTile(f))}
          </div>
        );

        // Render a single file list row
        const renderListRow = (f, idx, arr) => {
          const proxyUrl = ncFileUrl(f.path);
          const canPreview = isPreviewable(f.mime_type, f.filename);
          const cat = getCategoryMeta(f.subfolder);
          const rowAccent = f.nc_missing ? "var(--danger)" : !f.tracked ? "#f97316" : cat.hue.border;
          return (
            <tr
              key={f.path}
              style={{
                borderBottom: idx < arr.length - 1 ? "1px solid var(--border-secondary)" : "none",
                borderLeft: `3px solid ${rowAccent}`,
              }}
              onMouseEnter={e => (e.currentTarget.style.backgroundColor = "var(--bg-card-hover)")}
              onMouseLeave={e => (e.currentTarget.style.backgroundColor = "transparent")}
            >
              <td className="px-3 py-2"><span style={{ fontSize: 16 }}>{MIME_ICON(f.mime_type)}</span></td>
              <td className="px-3 py-2">
                <span
                  style={{ color: "var(--text-heading)", fontSize: 13, fontWeight: 500, cursor: canPreview && !f.nc_missing ? "pointer" : "default" }}
                  onClick={() => canPreview && !f.nc_missing && setPreviewFile({ path: f.path, mime_type: f.mime_type, filename: f.filename, db_id: f.db_id })}
                >{f.filename}</span>
                {f.nc_missing && <span style={{ marginLeft: 6, fontSize: 10, padding: "1px 5px", borderRadius: 4, backgroundColor: "var(--danger)", color: "#fff" }}>Missing</span>}
                {!f.tracked && !f.nc_missing && <span style={{ marginLeft: 6, fontSize: 10, padding: "1px 5px", borderRadius: 4, backgroundColor: "#f97316", color: "#fff" }}>Untracked</span>}
              </td>
              <td className="px-3 py-2">
                <span style={{ fontSize: 10, padding: "2px 7px", borderRadius: 10, backgroundColor: cat.hue.bg, color: cat.hue.text, fontWeight: 500, whiteSpace: "nowrap" }}>
                  {cat.label}
                </span>
              </td>
              <td className="px-3 py-2 text-xs" style={{ color: "var(--text-muted)" }}>{f.size > 0 ? formatFileSize(f.size) : "—"}</td>
              <td className="px-3 py-2 text-xs" style={{ color: "var(--text-muted)" }}>{f.last_modified ? new Date(f.last_modified).toLocaleDateString() : "—"}</td>
              <td className="px-3 py-2">
                <div style={{ display: "flex", gap: 3, flexWrap: "wrap" }}>
                  {(f.db_tags || []).map(t => <span key={t} style={{ fontSize: 10, padding: "1px 5px", borderRadius: 10, backgroundColor: "var(--bg-primary)", color: "var(--text-muted)" }}>{t}</span>)}
                </div>
              </td>
              <td className="px-3 py-2">
                <div style={{ display: "flex", gap: 4, justifyContent: "flex-end" }}>
                  {!f.nc_missing && (
                    <a href={proxyUrl} download={f.filename} style={{ fontSize: 11, padding: "3px 8px", borderRadius: 5, textDecoration: "none", color: "var(--accent)", backgroundColor: "var(--bg-primary)" }}>Download</a>
                  )}
                  {canEdit && (
                    <button
                      onClick={() => handleDeleteMedia(f.db_id || "__nc_only__", f.path)}
                      disabled={!!mediaDeleting[f.db_id]}
                      style={{ fontSize: 11, padding: "3px 8px", borderRadius: 5, cursor: "pointer", border: "none", color: "var(--danger-text)", backgroundColor: "var(--danger-bg)", opacity: mediaDeleting[f.db_id] ? 0.5 : 1 }}
                    >{mediaDeleting[f.db_id] ? "…" : "Delete"}</button>
                  )}
                </div>
              </td>
            </tr>
          );
        };

        const listTableHead = (
          <thead>
            <tr style={{ backgroundColor: "var(--bg-primary)", borderBottom: "1px solid var(--border-primary)" }}>
              <th className="px-3 py-2 text-left" style={{ color: "var(--text-secondary)", fontWeight: 500, fontSize: 12, width: 32 }}></th>
              <th className="px-3 py-2 text-left cursor-pointer hover:opacity-70" style={{ color: "var(--text-secondary)", fontWeight: 500, fontSize: 12 }} onClick={() => setMediaSort(s => s === "name_asc" ? "name_desc" : "name_asc")}>
                Name {mediaSort === "name_asc" ? "↑" : mediaSort === "name_desc" ? "↓" : ""}
              </th>
              <th className="px-3 py-2 text-left" style={{ color: "var(--text-secondary)", fontWeight: 500, fontSize: 12 }}>Category</th>
              <th className="px-3 py-2 text-left cursor-pointer hover:opacity-70" style={{ color: "var(--text-secondary)", fontWeight: 500, fontSize: 12 }} onClick={() => setMediaSort(s => s === "size_desc" ? "size_asc" : "size_desc")}>
                Size {mediaSort === "size_desc" ? "↓" : mediaSort === "size_asc" ? "↑" : ""}
              </th>
              <th className="px-3 py-2 text-left cursor-pointer hover:opacity-70" style={{ color: "var(--text-secondary)", fontWeight: 500, fontSize: 12 }} onClick={() => setMediaSort(s => s === "date_desc" ? "date_asc" : "date_desc")}>
                Date {mediaSort === "date_desc" ? "↓" : mediaSort === "date_asc" ? "↑" : ""}
              </th>
              <th className="px-3 py-2 text-left" style={{ color: "var(--text-secondary)", fontWeight: 500, fontSize: 12 }}>Tags</th>
              <th className="px-3 py-2 text-right" style={{ color: "var(--text-secondary)", fontWeight: 500, fontSize: 12 }}>Actions</th>
            </tr>
          </thead>
        );

        // Consistent table column widths — applied via colgroup on all sub-tables
        const listColGroup = (
          <colgroup>
            <col style={{ width: 32 }} />
            <col style={{ width: "30%" }} />
            <col style={{ width: 160 }} />
            <col style={{ width: 90 }} />
            <col style={{ width: 110 }} />
            <col style={{ width: 160 }} />
            <col style={{ width: 130 }} />
          </colgroup>
        );

        // Shared button style matching Refresh/Add Files
        const toolbarBtnStyle = { borderColor: "var(--border-primary)", color: "var(--text-secondary)", backgroundColor: "var(--bg-card)", fontSize: 12 };
        const toolbarBtnClass = "px-3 py-1.5 text-sm rounded-md cursor-pointer hover:opacity-80 border";
        const tbHeight = { height: 34, display: "flex", alignItems: "center" };

        // Active filter count for badge
        const activeFilterCount = mediaFilterTypes.size + (mediaTypeFilter !== "all" ? 1 : 0);

        return (
          <div onClick={() => { if (mediaFilterModalOpen) setMediaFilterModalOpen(false); }}>
            {/* Preview modal */}
            {previewFile && (() => {
              const pIsTxt = previewFile.mime_type === "text/plain" || previewFile.filename?.endsWith(".txt");
              const pIsVideo = previewFile.mime_type?.startsWith("video/");
              const pIsAudio = previewFile.mime_type?.startsWith("audio/");
              return (
                <div
                  onClick={() => { setPreviewFile(null); }}
                  style={{
                    position: "fixed", inset: 0, zIndex: 1000,
                    backgroundColor: "rgba(0,0,0,0.88)",
                    display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
                  }}
                >
                  <div
                    onClick={(e) => e.stopPropagation()}
                    style={{
                      backgroundColor: "var(--bg-card)", borderRadius: 10, overflow: "hidden",
                      maxWidth: "92vw", maxHeight: "90vh", display: "flex", flexDirection: "column",
                      boxShadow: "0 24px 64px rgba(0,0,0,0.5)",
                      width: (pIsTxt || previewFile.mime_type === "application/pdf") ? "80vw" : undefined,
                    }}
                  >
                    {/* Modal header */}
                    <div style={{
                      display: "flex", alignItems: "center", justifyContent: "space-between",
                      padding: "12px 16px", borderBottom: "1px solid var(--border-primary)",
                      gap: 12, flexShrink: 0,
                    }}>
                      <span className="text-sm font-medium truncate" style={{ color: "var(--text-heading)", maxWidth: "55vw" }}>
                        {previewFile.filename}
                      </span>
                      <div style={{ display: "flex", gap: 8, flexShrink: 0, alignItems: "center" }}>
                        {pIsTxt && !txtEditing && (
                          <button
                            onClick={() => { setTxtDraft(txtContent || ""); setTxtEditing(true); }}
                            style={{
                              padding: "4px 12px", fontSize: 12, borderRadius: 6, cursor: "pointer",
                              border: "1px solid var(--border-primary)", backgroundColor: "var(--bg-input)",
                              color: "var(--text-secondary)",
                            }}
                          >Edit</button>
                        )}
                        {pIsTxt && txtEditing && (
                          <>
                            <button
                              disabled={txtSaving}
                              onClick={async () => {
                                setTxtSaving(true);
                                try {
                                  const token = localStorage.getItem("access_token");
                                  const formData = new FormData();
                                  const blob = new Blob([txtDraft], { type: "text/plain" });
                                  formData.append("file", blob, previewFile.filename);
                                  formData.append("customer_id", id);
                                  // extract subfolder from path
                                  const parts = previewFile.path.split("/");
                                  formData.append("subfolder", parts[2] || "media");
                                  // Upload overwrites via PUT directly
                                  await fetch(`/api/crm/nextcloud/file-put?path=${encodeURIComponent(previewFile.path)}&token=${encodeURIComponent(token)}`, {
                                    method: "PUT",
                                    headers: { "Content-Type": "text/plain", Authorization: `Bearer ${token}` },
                                    body: txtDraft,
                                  });
                                  setTxtContent(txtDraft);
                                  setTxtEditing(false);
                                } catch (e) {
                                  alert("Save failed: " + e.message);
                                } finally {
                                  setTxtSaving(false);
                                }
                              }}
                              style={{
                                padding: "4px 12px", fontSize: 12, borderRadius: 6, cursor: "pointer",
                                backgroundColor: "var(--btn-primary)", color: "var(--text-white)", border: "none",
                                opacity: txtSaving ? 0.6 : 1,
                              }}
                            >{txtSaving ? "Saving…" : "Save"}</button>
                            <button
                              onClick={() => setTxtEditing(false)}
                              style={{
                                padding: "4px 12px", fontSize: 12, borderRadius: 6, cursor: "pointer",
                                border: "1px solid var(--border-primary)", backgroundColor: "var(--bg-input)",
                                color: "var(--text-secondary)",
                              }}
                            >Cancel</button>
                          </>
                        )}
                        <button
                          onClick={() => { setPreviewFile(null); }}
                          style={{
                            background: "none", border: "none", color: "var(--text-muted)",
                            fontSize: 22, cursor: "pointer", lineHeight: 1, padding: "0 4px",
                          }}
                        >×</button>
                      </div>
                    </div>
                    {/* Modal body */}
                    <div style={{ flex: 1, overflow: "auto", display: "flex", alignItems: "center", justifyContent: "center", padding: 16 }}>
                      {previewFile.mime_type?.startsWith("image/") && (
                        <img
                          src={ncFileUrl(previewFile.path)}
                          alt={previewFile.filename}
                          style={{ maxWidth: "80vw", maxHeight: "75vh", objectFit: "contain", borderRadius: 4 }}
                        />
                      )}
                      {pIsVideo && (
                        <video
                          controls
                          preload="metadata"
                          style={{ maxWidth: "80vw", maxHeight: "75vh", borderRadius: 4 }}
                        >
                          <source src={ncFileUrl(previewFile.path)} type={previewFile.mime_type || "video/mp4"} />
                          {/* Fallback without explicit type for broader codec support */}
                          <source src={ncFileUrl(previewFile.path)} />
                        </video>
                      )}
                      {pIsAudio && (
                        <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 16, padding: 32 }}>
                          <IconWaveform />
                          <p className="text-sm font-medium" style={{ color: "var(--text-heading)" }}>{previewFile.filename}</p>
                          <audio
                            src={ncFileUrl(previewFile.path)}
                            controls
                            preload="metadata"
                            style={{ width: "min(400px, 70vw)" }}
                          />
                        </div>
                      )}
                      {previewFile.mime_type === "application/pdf" && (
                        <iframe
                          src={ncFileUrl(previewFile.path)}
                          title={previewFile.filename}
                          style={{ width: "100%", height: "75vh", border: "none", borderRadius: 4 }}
                        />
                      )}
                      {pIsTxt && !txtEditing && (
                        <pre style={{
                          width: "100%", minHeight: 200, maxHeight: "72vh", overflow: "auto",
                          margin: 0, padding: 16, fontSize: 13, lineHeight: 1.6,
                          color: "var(--text-primary)", backgroundColor: "var(--bg-primary)",
                          borderRadius: 6, whiteSpace: "pre-wrap", wordBreak: "break-word",
                          fontFamily: "monospace",
                        }}>
                          {txtContent === null ? "Loading…" : txtContent}
                        </pre>
                      )}
                      {pIsTxt && txtEditing && (
                        <textarea
                          value={txtDraft}
                          onChange={e => setTxtDraft(e.target.value)}
                          style={{
                            width: "100%", height: "72vh", padding: 16, fontSize: 13, lineHeight: 1.6,
                            color: "var(--text-primary)", backgroundColor: "var(--bg-input)",
                            border: "1px solid var(--border-primary)", borderRadius: 6,
                            fontFamily: "monospace", resize: "none",
                          }}
                        />
                      )}
                    </div>

                    {/* Bottom nav: Prev | Open Full | Next */}
                    {(() => {
                      const list = previewListRef.current;
                      const idx = list.findIndex(f => f.path === previewFile.path);
                      const navBtnStyle = {
                        padding: "6px 16px", fontSize: 12, borderRadius: 6, cursor: "pointer",
                        border: "1px solid var(--border-primary)", backgroundColor: "var(--bg-input)",
                        color: "var(--text-secondary)", opacity: 1,
                      };
                      const disabledStyle = { ...navBtnStyle, opacity: 0.35, cursor: "default" };
                      return (
                        <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 10, padding: "10px 16px", borderTop: "1px solid var(--border-primary)", flexShrink: 0, flexWrap: "wrap" }}>
                          <button
                            onClick={() => { if (idx > 0) setPreviewFile({ path: list[idx-1].path, mime_type: list[idx-1].mime_type, filename: list[idx-1].filename, db_id: list[idx-1].db_id }); }}
                            disabled={idx <= 0}
                            style={idx <= 0 ? disabledStyle : navBtnStyle}
                          >← Prev</button>
                          <a
                            href={ncFileUrl(previewFile.path)}
                            target="_blank"
                            rel="noreferrer"
                            style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", fontSize: 12, borderRadius: 6, textDecoration: "none", border: "1px solid var(--border-primary)", color: "var(--text-secondary)", backgroundColor: "var(--bg-input)" }}
                            title="Open full view"
                          ><IconExpand size={14} /> Open Full</a>
                          <button
                            onClick={async () => {
                              try {
                                const data = await api.get(`/crm/nextcloud/web-url?path=${encodeURIComponent(previewFile.path)}`);
                                window.open(data.url, "_blank", "noreferrer");
                              } catch {
                                alert("Could not build Nextcloud URL.");
                              }
                            }}
                            style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", fontSize: 12, borderRadius: 6, border: "1px solid var(--border-primary)", color: "var(--text-secondary)", backgroundColor: "var(--bg-input)", cursor: "pointer" }}
                            title="Open the file's folder in Nextcloud web UI"
                          ><IconNextcloud size={14} /> Open In Nextcloud</button>
                          <a
                            href={ncFileUrl(previewFile.path)}
                            download={previewFile.filename}
                            style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", fontSize: 12, borderRadius: 6, textDecoration: "none", border: "1px solid var(--border-primary)", color: "var(--text-secondary)", backgroundColor: "var(--bg-input)" }}
                            title="Download"
                          ><span style={{ color: "#22c55e" }}><IconDownload size={14} /></span> Download</a>
                          {canEdit && (
                            <button
                              onClick={() => {
                                if (!window.confirm(`Delete "${previewFile.filename}"?`)) return;
                                handleDeleteMedia(previewFile.db_id || "__nc_only__", previewFile.path);
                                setPreviewFile(null);
                              }}
                              style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", fontSize: 12, borderRadius: 6, cursor: "pointer", border: "1px solid var(--border-primary)", backgroundColor: "var(--bg-input)", color: "var(--text-secondary)" }}
                              title="Delete file"
                            ><span style={{ color: "var(--danger)" }}><IconDelete size={14} /></span> Delete</button>
                          )}
                          <button
                            onClick={() => { if (idx < list.length - 1) setPreviewFile({ path: list[idx+1].path, mime_type: list[idx+1].mime_type, filename: list[idx+1].filename, db_id: list[idx+1].db_id }); }}
                            disabled={idx >= list.length - 1}
                            style={idx >= list.length - 1 ? disabledStyle : navBtnStyle}
                          >Next →</button>
                        </div>
                      );
                    })()}
                  </div>
                </div>
              );
            })()}

            {/* Toolbar */}
            <div style={{ display: "flex", alignItems: "center", marginBottom: 10, gap: 6, flexWrap: "wrap" }}>

              {/* Media Filter button */}
              <div style={{ position: "relative", flexShrink: 0 }}>
                <button
                  onClick={() => setMediaFilterModalOpen(v => !v)}
                  className={toolbarBtnClass}
                  style={{
                    ...toolbarBtnStyle, ...tbHeight, gap: 6, paddingLeft: 10, paddingRight: 10,
                    ...(activeFilterCount > 0 ? { borderColor: "var(--accent)", color: "var(--accent)" } : {}),
                  }}
                >
                  <span>Media Filter</span>
                  {activeFilterCount > 0 && (
                    <span style={{ backgroundColor: "var(--accent)", color: "#fff", borderRadius: "50%", width: 16, height: 16, fontSize: 10, display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 700 }}>
                      {activeFilterCount}
                    </span>
                  )}
                  <span style={{ fontSize: 10 }}>▾</span>
                </button>

                {mediaFilterModalOpen && (
                  <>
                  <div onClick={() => setMediaFilterModalOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 299 }} />
                  <div
                    style={{
                      position: "absolute", left: 0, top: "calc(100% + 4px)", zIndex: 300,
                      backgroundColor: "var(--bg-card)", border: "1px solid var(--border-primary)",
                      borderRadius: 10, boxShadow: "0 8px 32px rgba(0,0,0,0.3)",
                      minWidth: 280, padding: "14px 16px",
                    }}
                    onClick={e => e.stopPropagation()}
                  >
                    <div style={{ fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 10 }}>Categories</div>
                    <div style={{ display: "flex", flexDirection: "column", gap: 4, marginBottom: 12 }}>
                      {MEDIA_CATEGORIES.map(cat => {
                        const count = allFiles.filter(x => x.subfolder === cat.value).length;
                        const isOn = mediaFilterTypes.has(cat.value);
                        return (
                          <label key={cat.value} style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", padding: "4px 6px", borderRadius: 6, backgroundColor: isOn ? cat.hue.bg : "transparent" }}>
                            <input
                              type="checkbox"
                              checked={isOn}
                              onChange={() => {
                                setMediaFilterTypes(prev => {
                                  const next = new Set(prev);
                                  next.has(cat.value) ? next.delete(cat.value) : next.add(cat.value);
                                  return next;
                                });
                                setMediaPage(1);
                              }}
                              style={{ accentColor: "var(--accent)", width: 14, height: 14 }}
                            />
                            <span style={{ fontSize: 12, color: isOn ? cat.hue.text : "var(--text-primary)", flex: 1 }}>{cat.label}</span>
                            <span style={{ fontSize: 11, color: "var(--text-muted)" }}>{count}</span>
                          </label>
                        );
                      })}
                    </div>

                    <div style={{ borderTop: "1px solid var(--border-secondary)", paddingTop: 10, marginBottom: 10 }}>
                      <div style={{ fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 8 }}>File Type</div>
                      <div style={{ display: "flex", gap: 6 }}>
                        {[["all", "All"], ["photos", "Photos only"], ["videos", "Videos only"]].map(([v, label]) => (
                          <button
                            key={v}
                            onClick={() => { setMediaTypeFilter(v); setMediaPage(1); }}
                            style={{
                              padding: "4px 10px", fontSize: 11, borderRadius: 6, cursor: "pointer", border: "1px solid",
                              borderColor: mediaTypeFilter === v ? "var(--accent)" : "var(--border-primary)",
                              backgroundColor: mediaTypeFilter === v ? "color-mix(in srgb, var(--accent) 15%, var(--bg-card))" : "transparent",
                              color: mediaTypeFilter === v ? "var(--accent)" : "var(--text-secondary)",
                            }}
                          >{label}</button>
                        ))}
                      </div>
                    </div>

                    <div style={{ borderTop: "1px solid var(--border-secondary)", paddingTop: 10, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                      <button
                        onClick={() => { setMediaFilterTypes(new Set()); setMediaTypeFilter("all"); setMediaPage(1); setMediaFilterModalOpen(false); }}
                        style={{ fontSize: 12, cursor: "pointer", border: "none", background: "none", color: "var(--text-muted)", padding: "4px 0" }}
                      >Clear All Filters</button>
                      <button
                        onClick={() => setMediaFilterModalOpen(false)}
                        style={{ fontSize: 12, cursor: "pointer", border: "1px solid var(--border-primary)", borderRadius: 6, background: "var(--bg-input)", color: "var(--text-secondary)", padding: "4px 12px" }}
                      >Done</button>
                    </div>
                  </div>
                  </>
                )}
              </div>

              {/* Search box */}
              <input
                value={mediaSearch}
                onChange={e => { setMediaSearch(e.target.value); setMediaPage(1); }}
                placeholder="Search by name or extension…"
                className={toolbarBtnClass}
                style={{ ...toolbarBtnStyle, ...tbHeight, padding: "0 10px", flex: 1, minWidth: 120, outline: "none" }}
              />

              {/* Sort */}
              <select
                value={mediaSort}
                onChange={e => setMediaSort(e.target.value)}
                className={toolbarBtnClass}
                style={{ ...toolbarBtnStyle, ...tbHeight, paddingRight: 24, flexShrink: 0 }}
              >
                <option value="date_desc">Date ↓</option>
                <option value="date_asc">Date ↑</option>
                <option value="name_asc">Name A–Z</option>
                <option value="name_desc">Name Z–A</option>
                <option value="size_desc">Size ↓</option>
                <option value="size_asc">Size ↑</option>
                <option value="type_date">File Type</option>
              </select>

              {/* Show N dropdown */}
              <select
                value={String(mediaPageSize)}
                onChange={e => { setMediaPageSize(Number(e.target.value)); setMediaPage(1); }}
                className={toolbarBtnClass}
                style={{ ...toolbarBtnStyle, ...tbHeight, paddingRight: 24, flexShrink: 0 }}
              >
                <option value="0">Show All</option>
                <option value="10">Show 10</option>
                <option value="20">Show 20</option>
                <option value="50">Show 50</option>
                <option value="100">Show 100</option>
              </select>

              {/* Group view toggle */}
              {mediaFilterTypes.size === 0 && mediaTypeFilter === "all" && (
                <button
                  onClick={() => setGroupView(v => !v)}
                  className={toolbarBtnClass}
                  style={{ ...toolbarBtnStyle, ...tbHeight, flexShrink: 0, ...(groupView ? { backgroundColor: "var(--bg-primary)", borderColor: "var(--accent)", color: "var(--accent)" } : {}) }}
                >
                  Group
                </button>
              )}

              {/* View toggle */}
              <div style={{ display: "flex", border: "1px solid var(--border-primary)", borderRadius: 6, overflow: "hidden", flexShrink: 0, height: 34 }}>
                {[["tile", "⊞"], ["list", "☰"]].map(([v, icon]) => (
                  <button key={v} onClick={() => setMediaView(v)}
                    style={{ padding: "4px 10px", fontSize: 14, cursor: "pointer", border: "none", backgroundColor: mediaView === v ? "var(--accent)" : "var(--bg-card)", color: mediaView === v ? "#fff" : "var(--text-secondary)" }}
                  >{icon}</button>
                ))}
              </div>

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

              {/* Refresh icon button */}
              <button
                onClick={() => { loadMedia(); browseNextcloud(); }}
                className={toolbarBtnClass}
                style={{ ...toolbarBtnStyle, ...tbHeight, padding: "0 9px", flexShrink: 0 }}
                title="Refresh"
              >
                <IconRefresh />
              </button>

              {/* Selection dropdown */}
              {selectedPaths.size > 0 && (
                <div style={{ position: "relative", flexShrink: 0 }}>
                  <button onClick={() => setSelectionMenuOpen(v => !v)} className={toolbarBtnClass} style={{ ...toolbarBtnStyle, ...tbHeight, borderColor: "var(--accent)", color: "var(--accent)" }}>
                    Selection ({selectedPaths.size}) ▾
                  </button>
                  {selectionMenuOpen && (
                    <div style={{ position: "absolute", right: 0, top: "110%", zIndex: 50, backgroundColor: "var(--bg-card)", border: "1px solid var(--border-primary)", borderRadius: 8, overflow: "hidden", minWidth: 190, boxShadow: "0 8px 24px rgba(0,0,0,0.15)" }}
                      onMouseLeave={() => setSelectionMenuOpen(false)}>
                      <button onClick={async () => { setSelectionMenuOpen(false); for (const path of selectedPaths) { const a = document.createElement("a"); a.href = ncFileUrl(path); a.download = path.split("/").pop(); a.click(); await new Promise(r => setTimeout(r, 300)); } }}
                        style={{ display: "block", width: "100%", padding: "9px 14px", textAlign: "left", fontSize: 12, cursor: "pointer", border: "none", backgroundColor: "transparent", color: "var(--accent)" }}>
                        Download {selectedPaths.size} File{selectedPaths.size !== 1 ? "s" : ""}
                      </button>
                      {canEdit && <button onClick={() => { setSelectionMenuOpen(false); handleBulkDelete(); }}
                        style={{ display: "block", width: "100%", padding: "9px 14px", textAlign: "left", fontSize: 12, cursor: "pointer", border: "none", backgroundColor: "transparent", color: "var(--danger-text)", borderTop: "1px solid var(--border-secondary)" }}>
                        Delete {selectedPaths.size} File{selectedPaths.size !== 1 ? "s" : ""}
                      </button>}
                      <button onClick={() => { setSelectionMenuOpen(false); setSelectedPaths(new Set()); }}
                        style={{ display: "block", width: "100%", padding: "9px 14px", textAlign: "left", fontSize: 12, cursor: "pointer", border: "none", backgroundColor: "transparent", color: "var(--text-muted)", borderTop: "1px solid var(--border-secondary)" }}>
                        Clear Selection
                      </button>
                    </div>
                  )}
                </div>
              )}

              {canEdit && ncFiles.length === 0 && !ncBrowsing && (
                <button onClick={initCustomerFolder} className={toolbarBtnClass} style={{ ...toolbarBtnStyle, ...tbHeight, flexShrink: 0 }}>Init Folder</button>
              )}

              {canEdit && (
                <button onClick={() => { setShowCreateTxt(true); setCreateTxtName("new-file.txt"); }} className={toolbarBtnClass} style={{ ...toolbarBtnStyle, ...tbHeight, flexShrink: 0 }}>
                  📄 Create File
                </button>
              )}

              {canEdit && (
                <button
                  onClick={() => { setShowUpload(true); }}
                  className="px-3 rounded-md cursor-pointer hover:opacity-90"
                  style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)", fontSize: 12, border: "none", flexShrink: 0, height: 34, display: "flex", alignItems: "center" }}
                >
                  + Add Files
                </button>
              )}

              {/* Gear menu — far right, maintenance actions */}
              {canEdit && (
                <div style={{ position: "relative", flexShrink: 0 }}>
                  <button
                    onClick={() => setMediaGearOpen(v => !v)}
                    className={toolbarBtnClass}
                    style={{
                      ...toolbarBtnStyle, ...tbHeight, padding: "0 9px", fontSize: 15,
                      ...(thumbGenLoading ? { opacity: 0.6, cursor: "wait" } : {}),
                    }}
                    title="Media maintenance"
                    disabled={thumbGenLoading}
                  >
                    {thumbGenLoading ? "⏳" : "⚙"}
                  </button>
                  {mediaGearOpen && (
                    <div
                      style={{
                        position: "absolute", right: 0, top: "calc(100% + 4px)", zIndex: 200,
                        backgroundColor: "var(--bg-card)", border: "1px solid var(--border-primary)",
                        borderRadius: 8, boxShadow: "0 4px 16px rgba(0,0,0,0.3)",
                        minWidth: 300, overflow: "hidden",
                      }}
                      onMouseLeave={() => setMediaGearOpen(false)}
                    >
                      <button
                        onClick={handleGenerateThumbs}
                        style={{
                          display: "block", width: "100%", textAlign: "left",
                          padding: "12px 16px", fontSize: 13, cursor: "pointer",
                          color: "var(--text-primary)", background: "none", border: "none",
                          borderBottom: "1px solid var(--border-secondary)",
                        }}
                        onMouseEnter={e => e.currentTarget.style.backgroundColor = "var(--bg-primary)"}
                        onMouseLeave={e => e.currentTarget.style.backgroundColor = ""}
                      >
                        <div style={{ fontWeight: 500 }}>🖼 Generate Missing Thumbnails</div>
                        <div style={{ fontSize: 11, color: "var(--text-muted)", marginTop: 3 }}>
                          Scans all files for this customer across all folders, creates thumbs for any without one. Existing thumbs are skipped.
                        </div>
                      </button>
                      <button
                        onClick={handleClearThumbs}
                        style={{
                          display: "block", width: "100%", textAlign: "left",
                          padding: "12px 16px", fontSize: 13, cursor: "pointer",
                          color: "var(--danger-text)", background: "none", border: "none",
                        }}
                        onMouseEnter={e => e.currentTarget.style.backgroundColor = "var(--danger-bg)"}
                        onMouseLeave={e => e.currentTarget.style.backgroundColor = ""}
                      >
                        <div style={{ fontWeight: 500 }}>🗑 Clear All Thumbnails</div>
                        <div style={{ fontSize: 11, color: "var(--text-muted)", marginTop: 3 }}>
                          Deletes all .thumbs folders for this customer. Thumbnails will be regenerated on next request.
                        </div>
                      </button>
                    </div>
                  )}
                </div>
              )}
            </div>

            {/* Pagination controls */}
            {mediaPageSize > 0 && totalPages > 1 && (
              <div style={{ display: "flex", alignItems: "center", gap: 4, marginBottom: 10, flexWrap: "wrap" }}>
                <button
                  onClick={() => setMediaPage(p => Math.max(1, p - 1))}
                  disabled={safeCurrentPage <= 1}
                  style={{ padding: "3px 10px", fontSize: 12, borderRadius: 5, cursor: safeCurrentPage <= 1 ? "default" : "pointer", border: "1px solid var(--border-primary)", backgroundColor: "var(--bg-card)", color: "var(--text-secondary)", opacity: safeCurrentPage <= 1 ? 0.4 : 1 }}
                >‹</button>
                {Array.from({ length: totalPages }, (_, i) => i + 1).map(p => (
                  <button
                    key={p}
                    onClick={() => setMediaPage(p)}
                    style={{
                      padding: "3px 9px", fontSize: 12, borderRadius: 5, cursor: "pointer", border: "1px solid",
                      borderColor: p === safeCurrentPage ? "var(--accent)" : "var(--border-primary)",
                      backgroundColor: p === safeCurrentPage ? "var(--accent)" : "var(--bg-card)",
                      color: p === safeCurrentPage ? "#fff" : "var(--text-secondary)",
                    }}
                  >{p}</button>
                ))}
                <button
                  onClick={() => setMediaPage(p => Math.min(totalPages, p + 1))}
                  disabled={safeCurrentPage >= totalPages}
                  style={{ padding: "3px 10px", fontSize: 12, borderRadius: 5, cursor: safeCurrentPage >= totalPages ? "default" : "pointer", border: "1px solid var(--border-primary)", backgroundColor: "var(--bg-card)", color: "var(--text-secondary)", opacity: safeCurrentPage >= totalPages ? 0.4 : 1 }}
                >›</button>
                <span style={{ fontSize: 11, color: "var(--text-muted)", marginLeft: 4 }}>
                  {totalFiltered} file{totalFiltered !== 1 ? "s" : ""} · page {safeCurrentPage} of {totalPages}
                </span>
              </div>
            )}

            {/* Thumb generation result banner */}
            {thumbGenResult && (
              <div style={{
                display: "flex", alignItems: "center", gap: 10, padding: "8px 14px",
                marginBottom: 8, borderRadius: 8, border: "1px solid var(--accent)",
                backgroundColor: "color-mix(in srgb, var(--accent) 10%, var(--bg-card))", fontSize: 13,
              }}>
                <span style={{ color: "var(--accent)" }}>
                  ✓ Thumbnails: {thumbGenResult.generated} generated, {thumbGenResult.skipped} already existed
                  {thumbGenResult.failed > 0 ? `, ${thumbGenResult.failed} failed` : ""}
                </span>
                <button
                  onClick={() => setThumbGenResult(null)}
                  style={{ marginLeft: "auto", background: "none", border: "none", color: "var(--text-muted)", cursor: "pointer", fontSize: 16, lineHeight: 1 }}
                >×</button>
              </div>
            )}

            {/* Sync / Untrack banners */}
            {canEdit && untrackedCount > 0 && (
              <div style={{
                display: "flex", alignItems: "center", gap: 10, padding: "8px 14px",
                marginBottom: 8, borderRadius: 8, border: "1px solid #f97316",
                backgroundColor: "rgba(249,115,22,0.07)", fontSize: 13,
              }}>
                <span style={{ color: "#f97316" }}>⚠ {untrackedCount} untracked file{untrackedCount !== 1 ? "s" : ""} found in Nextcloud</span>
                <button
                  onClick={handleSync}
                  disabled={syncLoading}
                  style={{
                    marginLeft: "auto", padding: "3px 12px", fontSize: 12, borderRadius: 6, cursor: "pointer",
                    border: "1px solid #f97316", backgroundColor: "transparent", color: "#f97316",
                    opacity: syncLoading ? 0.6 : 1,
                  }}
                >
                  {syncLoading ? "Scanning Nextcloud..." : "Sync Now"}
                </button>
              </div>
            )}
            {canEdit && ncMissingCount > 0 && (
              <div style={{
                display: "flex", alignItems: "center", gap: 10, padding: "8px 14px",
                marginBottom: 8, borderRadius: 8, border: "1px solid var(--danger)",
                backgroundColor: "var(--danger-bg)", fontSize: 13,
              }}>
                <span style={{ color: "var(--danger-text)" }}>⚠ {ncMissingCount} record{ncMissingCount !== 1 ? "s" : ""} missing from Nextcloud</span>
                <button
                  onClick={handleUntrackDeleted}
                  disabled={untrackLoading}
                  style={{
                    marginLeft: "auto", padding: "3px 12px", fontSize: 12, borderRadius: 6, cursor: "pointer",
                    border: "1px solid var(--danger)", backgroundColor: "transparent", color: "var(--danger-text)",
                    opacity: untrackLoading ? 0.6 : 1,
                  }}
                >
                  {untrackLoading ? "Scanning Nextcloud..." : "Untrack Deleted"}
                </button>
              </div>
            )}

            {/* ── Upload Modal ── */}
            {showUpload && (
              <div
                onClick={e => { if (e.target === e.currentTarget && !uploadRunning) setShowUpload(false); }}
                onDragOver={e => { e.preventDefault(); setUploadDragging(true); }}
                onDragLeave={e => { if (!e.currentTarget.contains(e.relatedTarget)) setUploadDragging(false); }}
                onDrop={e => { e.preventDefault(); setUploadDragging(false); addFilesToUploadQueue(Array.from(e.dataTransfer.files)); }}
                style={{
                  position: "fixed", inset: 0, zIndex: 1000,
                  backgroundColor: uploadDragging ? "rgba(99,102,241,0.18)" : "rgba(0,0,0,0.6)",
                  display: "flex", alignItems: "center", justifyContent: "center",
                  transition: "background-color 0.15s",
                }}
              >
                <div
                  onClick={e => e.stopPropagation()}
                  style={{
                    backgroundColor: "var(--bg-card)", borderRadius: 12, overflow: "hidden",
                    width: "min(860px, 94vw)", maxHeight: "88vh", display: "flex", flexDirection: "column",
                    boxShadow: "0 24px 64px rgba(0,0,0,0.4)",
                  }}
                >
                  {/* Modal header */}
                  <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "14px 20px", borderBottom: "1px solid var(--border-primary)", flexShrink: 0 }}>
                    <span style={{ fontWeight: 600, fontSize: 15, color: "var(--text-heading)" }}>Add Files</span>
                    {!uploadRunning && (
                      <button onClick={() => { setShowUpload(false); setUploadFiles([]); setBulkTags([]); setBulkTagInput(""); setBulkSubfolder("media"); }} style={{ background: "none", border: "none", color: "var(--text-muted)", fontSize: 22, cursor: "pointer", lineHeight: 1 }}>×</button>
                    )}
                  </div>

                  {/* Drop zone header */}
                  <div
                    onClick={() => fileInputRef.current?.click()}
                    style={{
                      padding: "20px 24px", cursor: "pointer", display: "flex", alignItems: "center", gap: 14,
                      borderBottom: "1px solid var(--border-primary)", flexShrink: 0,
                      backgroundColor: uploadDragging ? "rgba(99,102,241,0.06)" : "transparent",
                    }}
                  >
                    <div style={{ width: 44, height: 44, borderRadius: 10, border: "2px dashed var(--border-primary)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 22, color: "var(--text-muted)", flexShrink: 0 }}>+</div>
                    <div>
                      <div style={{ fontSize: 14, fontWeight: 500, color: "var(--text-heading)" }}>Click to browse or drop files anywhere in this window</div>
                      <div style={{ fontSize: 12, color: "var(--text-muted)", marginTop: 2 }}>Multiple files supported</div>
                    </div>
                  </div>
                  <input ref={fileInputRef} type="file" multiple style={{ display: "none" }}
                    onChange={e => { addFilesToUploadQueue(Array.from(e.target.files || [])); e.target.value = ""; }} />

                  {/* File table */}
                  <div style={{ flex: 1, overflowY: "auto" }}>
                    {uploadFiles.length === 0 ? (
                      <div style={{ textAlign: "center", padding: "40px 0", fontSize: 13, color: "var(--text-muted)" }}>
                        No files selected yet. Click above or drag &amp; drop.
                      </div>
                    ) : (
                      <table className="w-full text-sm" style={{ borderCollapse: "collapse" }}>
                        <thead>
                          <tr style={{ backgroundColor: "var(--bg-primary)", borderBottom: "1px solid var(--border-primary)" }}>
                            <th className="px-4 py-2 text-left" style={{ color: "var(--text-secondary)", fontWeight: 500, fontSize: 11 }}>File Name</th>
                            <th className="px-4 py-2 text-left" style={{ color: "var(--text-secondary)", fontWeight: 500, fontSize: 11, width: 160 }}>Folder</th>
                            <th className="px-4 py-2 text-left" style={{ color: "var(--text-secondary)", fontWeight: 500, fontSize: 11, width: 200 }}>Tags</th>
                            <th className="px-4 py-2 text-left" style={{ color: "var(--text-secondary)", fontWeight: 500, fontSize: 11, width: 80 }}>Size</th>
                            <th className="px-4 py-2" style={{ width: 60 }}></th>
                          </tr>
                        </thead>
                        <tbody>
                          {/* Quick Set ALL row */}
                          <tr style={{ borderBottom: "2px solid var(--border-primary)", backgroundColor: "var(--bg-primary)" }}>
                            <td className="px-4 py-2">
                              <span style={{ fontSize: 11, fontWeight: 600, color: "var(--text-muted)", whiteSpace: "nowrap" }}>Quick Set ALL Files to:</span>
                            </td>
                            <td className="px-4 py-2">
                              <select
                                value={bulkSubfolder}
                                onChange={e => {
                                  setBulkSubfolder(e.target.value);
                                  setUploadFiles(prev => prev.map(x => x.progress === null ? { ...x, subfolder: e.target.value } : x));
                                }}
                                style={{ width: "100%", fontSize: 12, padding: "3px 6px", borderRadius: 4, border: "1px solid var(--border-primary)", backgroundColor: "var(--bg-input)", color: "var(--text-primary)" }}
                              >
                                {MEDIA_CATEGORIES.map(c => <option key={c.value} value={c.value}>{c.label}</option>)}
                              </select>
                            </td>
                            <td className="px-4 py-2">
                              <div style={{ display: "flex", flexWrap: "wrap", gap: 3, alignItems: "center", border: "1px solid var(--border-primary)", borderRadius: 4, padding: "2px 4px", backgroundColor: "var(--bg-input)", minHeight: 28 }}>
                                {bulkTags.map(t => (
                                  <span key={t} style={{ display: "flex", alignItems: "center", gap: 3, fontSize: 10, padding: "1px 6px", borderRadius: 10, backgroundColor: "var(--bg-primary)", color: "var(--text-secondary)" }}>
                                    {t}
                                    <button type="button" onClick={() => {
                                      setBulkTags(prev => prev.filter(bt => bt !== t));
                                      setUploadFiles(prev => prev.map(x => x.progress === null ? { ...x, tags: x.tags.filter(tt => tt !== t) } : x));
                                    }} style={{ background: "none", border: "none", cursor: "pointer", padding: 0, lineHeight: 1, color: "var(--text-muted)", fontSize: 11 }}>×</button>
                                  </span>
                                ))}
                                <input
                                  value={bulkTagInput}
                                  placeholder={bulkTags.length === 0 ? "tag, enter…" : ""}
                                  style={{ border: "none", outline: "none", backgroundColor: "transparent", fontSize: 11, color: "var(--text-primary)", minWidth: 60, flex: 1 }}
                                  onChange={e => setBulkTagInput(e.target.value)}
                                  onKeyDown={e => {
                                    if (e.key === "Enter" || e.key === ",") {
                                      e.preventDefault();
                                      const val = bulkTagInput.trim().replace(/,$/, "");
                                      if (val) {
                                        setBulkTags(prev => prev.includes(val) ? prev : [...prev, val]);
                                        setUploadFiles(prev => prev.map(x => x.progress === null && !x.tags.includes(val) ? { ...x, tags: [...x.tags, val] } : x));
                                      }
                                      setBulkTagInput("");
                                    }
                                  }}
                                />
                              </div>
                            </td>
                            <td className="px-4 py-2" />
                            <td className="px-4 py-2" />
                          </tr>
                          {uploadFiles.map((entry, i) => (
                            <tr key={i} style={{ borderBottom: "1px solid var(--border-secondary)" }}>
                              {/* Name */}
                              <td className="px-4 py-2">
                                <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                                  {entry.progress === "done" && <span style={{ color: "#22c55e", fontSize: 16 }}>✓</span>}
                                  {entry.progress === "error" && <span style={{ color: "var(--danger)", fontSize: 16 }}>✕</span>}
                                  {entry.progress !== null && entry.progress !== "done" && entry.progress !== "error" && (
                                    <div style={{ width: 14, height: 14, borderRadius: "50%", border: "2px solid var(--accent)", borderTopColor: "transparent", animation: "spin 0.7s linear infinite" }} />
                                  )}
                                  <input
                                    value={entry.name}
                                    onChange={e => setUploadFiles(prev => prev.map((x, idx) => idx === i ? { ...x, name: e.target.value } : x))}
                                    disabled={entry.progress !== null}
                                    style={{ flex: 1, fontSize: 12, padding: "3px 6px", borderRadius: 4, border: "1px solid var(--border-primary)", backgroundColor: "var(--bg-input)", color: "var(--text-primary)" }}
                                  />
                                </div>
                                {/* Per-file progress bar */}
                                {entry.progress !== null && entry.progress !== "done" && entry.progress !== "error" && (
                                  <div style={{ marginTop: 4, height: 3, borderRadius: 2, backgroundColor: "var(--border-secondary)", overflow: "hidden" }}>
                                    <div style={{ width: "100%", height: "100%", backgroundColor: "var(--accent)", animation: "pulse 1s ease-in-out infinite" }} />
                                  </div>
                                )}
                              </td>
                              {/* Folder */}
                              <td className="px-4 py-2">
                                <select
                                  value={entry.subfolder}
                                  onChange={e => setUploadFiles(prev => prev.map((x, idx) => idx === i ? { ...x, subfolder: e.target.value } : x))}
                                  disabled={entry.progress !== null}
                                  style={{ width: "100%", fontSize: 12, padding: "3px 6px", borderRadius: 4, border: "1px solid var(--border-primary)", backgroundColor: "var(--bg-input)", color: "var(--text-primary)" }}
                                >
                                  {MEDIA_CATEGORIES.map(c => <option key={c.value} value={c.value}>{c.label}</option>)}
                                </select>
                              </td>
                              {/* Tags */}
                              <td className="px-4 py-2">
                                <div style={{ display: "flex", flexWrap: "wrap", gap: 3, alignItems: "center", border: "1px solid var(--border-primary)", borderRadius: 4, padding: "2px 4px", backgroundColor: "var(--bg-input)", minHeight: 28 }}>
                                  {entry.tags.map(t => (
                                    <span key={t} style={{ display: "flex", alignItems: "center", gap: 3, fontSize: 10, padding: "1px 6px", borderRadius: 10, backgroundColor: "var(--bg-primary)", color: "var(--text-secondary)" }}>
                                      {t}
                                      <button type="button" onClick={() => setUploadFiles(prev => prev.map((x, idx) => idx === i ? { ...x, tags: x.tags.filter(tt => tt !== t) } : x))}
                                        style={{ background: "none", border: "none", cursor: "pointer", padding: 0, lineHeight: 1, color: "var(--text-muted)", fontSize: 11 }}>×</button>
                                    </span>
                                  ))}
                                  <input
                                    value={entry.tagInput}
                                    placeholder={entry.tags.length === 0 ? "tag, enter…" : ""}
                                    disabled={entry.progress !== null}
                                    style={{ border: "none", outline: "none", backgroundColor: "transparent", fontSize: 11, color: "var(--text-primary)", minWidth: 60, flex: 1 }}
                                    onChange={e => setUploadFiles(prev => prev.map((x, idx) => idx === i ? { ...x, tagInput: e.target.value } : x))}
                                    onKeyDown={e => {
                                      if (e.key === "Enter" || e.key === ",") {
                                        e.preventDefault();
                                        const val = entry.tagInput.trim().replace(/,$/, "");
                                        if (val && !entry.tags.includes(val)) setUploadFiles(prev => prev.map((x, idx) => idx === i ? { ...x, tags: [...x.tags, val], tagInput: "" } : x));
                                        else setUploadFiles(prev => prev.map((x, idx) => idx === i ? { ...x, tagInput: "" } : x));
                                      }
                                    }}
                                  />
                                </div>
                              </td>
                              {/* Size */}
                              <td className="px-4 py-2 text-xs" style={{ color: "var(--text-muted)", whiteSpace: "nowrap" }}>
                                {formatFileSize(entry.file.size)}
                              </td>
                              {/* Remove */}
                              <td className="px-4 py-2 text-center">
                                {entry.progress !== "done" && entry.progress === null && (
                                  <button type="button" onClick={() => setUploadFiles(prev => prev.filter((_, idx) => idx !== i))}
                                    style={{ background: "none", border: "none", color: "var(--text-muted)", cursor: "pointer", fontSize: 16, lineHeight: 1 }}>×</button>
                                )}
                              </td>
                            </tr>
                          ))}
                        </tbody>
                      </table>
                    )}
                  </div>

                  {/* Footer */}
                  <div style={{ padding: "14px 20px", borderTop: "1px solid var(--border-primary)", display: "flex", justifyContent: "center", gap: 10, flexShrink: 0 }}>
                    {!uploadRunning && uploadFiles.some(e => e.progress === "done") && (
                      <button onClick={() => { setShowUpload(false); setUploadFiles([]); setBulkTags([]); setBulkTagInput(""); setBulkSubfolder("media"); }} style={{ padding: "8px 20px", fontSize: 13, borderRadius: 6, cursor: "pointer", border: "1px solid var(--border-primary)", backgroundColor: "var(--bg-input)", color: "var(--text-secondary)" }}>
                        Done
                      </button>
                    )}
                    <button
                      onClick={handleUpload}
                      disabled={uploadFiles.filter(e => e.progress === null).length === 0 || uploadRunning}
                      style={{
                        padding: "8px 28px", fontSize: 13, borderRadius: 6, cursor: "pointer", border: "none",
                        backgroundColor: "#22c55e", color: "#fff",
                        opacity: (uploadFiles.filter(e => e.progress === null).length === 0 || uploadRunning) ? 0.5 : 1,
                      }}
                    >
                      {uploadRunning ? "Uploading…" : `Upload ${uploadFiles.filter(e => e.progress === null).length} File${uploadFiles.filter(e => e.progress === null).length !== 1 ? "s" : ""}`}
                    </button>
                  </div>
                </div>
              </div>
            )}

            {/* ── Create TXT Modal ── */}
            {showCreateTxt && (
              <div
                style={{ position: "fixed", inset: 0, zIndex: 1000, backgroundColor: "rgba(0,0,0,0.6)", display: "flex", alignItems: "center", justifyContent: "center" }}>
                <div onClick={e => e.stopPropagation()}
                  style={{ backgroundColor: "var(--bg-card)", borderRadius: 10, padding: 24, width: 420, boxShadow: "0 16px 48px rgba(0,0,0,0.35)", display: "flex", flexDirection: "column", gap: 14 }}>
                  <div style={{ fontWeight: 600, fontSize: 15, color: "var(--text-heading)" }}>Create Text File</div>
                  <Field label="File Name">
                    <input
                      className={inputClass} style={inputStyle}
                      value={createTxtName}
                      onChange={e => setCreateTxtName(e.target.value)}
                      placeholder="filename.txt"
                      autoFocus
                    />
                  </Field>
                  <Field label="Folder">
                    <select className={inputClass} style={inputStyle} value={createTxtFolder} onChange={e => setCreateTxtFolder(e.target.value)}>
                      {MEDIA_CATEGORIES.map(c => <option key={c.value} value={c.value}>{c.label}</option>)}
                    </select>
                  </Field>
                  <div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
                    <button onClick={() => setShowCreateTxt(false)} style={{ padding: "7px 16px", fontSize: 12, borderRadius: 6, border: "1px solid var(--border-primary)", backgroundColor: "var(--bg-input)", color: "var(--text-secondary)", cursor: "pointer" }}>Cancel</button>
                    <button onClick={handleCreateTxt} disabled={createTxtSaving} style={{ padding: "7px 16px", fontSize: 12, borderRadius: 6, border: "none", backgroundColor: "var(--btn-primary)", color: "var(--text-white)", cursor: "pointer", opacity: createTxtSaving ? 0.6 : 1 }}>
                      {createTxtSaving ? "Creating…" : "Create"}
                    </button>
                  </div>
                </div>
              </div>
            )}

            {/* Loading / empty states */}
            {mediaLoading && <div className="text-center py-6" style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading files...</div>}
            {!mediaLoading && ncBrowsing && <div style={{ fontSize: 12, color: "var(--text-muted)", marginBottom: 8, textAlign: "right" }}>Checking Nextcloud...</div>}
            {!mediaLoading && !ncBrowsing && allFiles.length === 0 && (
              <div className="rounded-lg p-8 text-center text-sm border" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)", color: "var(--text-muted)" }}>
                No files yet. Use "Init Folder" to create the folder structure, then add files.
              </div>
            )}

            {/* Empty filter state */}
            {!mediaLoading && allFiles.length > 0 && filtered.length === 0 && (
              <div className="rounded-lg p-6 text-center text-sm border" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)", color: "var(--text-muted)" }}>
                No files match the current filters.{" "}
                <button onClick={() => { setMediaFilterTypes(new Set()); setMediaTypeFilter("all"); setMediaSearch(""); }} style={{ color: "var(--accent)", background: "none", border: "none", cursor: "pointer", fontSize: "inherit" }}>Clear filters</button>
              </div>
            )}

            {/* ── Tile view ── */}
            {!mediaLoading && mediaView === "tile" && paginatedFiles.length > 0 && (
              showGroupView ? (
                <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
                  {groupedSections.map(g => (
                    <div key={g.key}>
                      <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 10 }}>
                        <span style={{ fontSize: 12, fontWeight: 600, color: "var(--text-secondary)", textTransform: "uppercase", letterSpacing: "0.05em" }}>{g.label}</span>
                        <div style={{ flex: 1, height: 1, backgroundColor: "var(--border-secondary)" }} />
                        <span style={{ fontSize: 11, color: "var(--text-muted)" }}>{g.files.length}</span>
                      </div>
                      {renderTileGrid(g.files)}
                    </div>
                  ))}
                </div>
              ) : renderTileGrid(paginatedFiles)
            )}

            {/* ── List view ── */}
            {!mediaLoading && mediaView === "list" && paginatedFiles.length > 0 && (
              showGroupView ? (
                <div style={{ display: "flex", flexDirection: "column", gap: 20, marginTop: 12 }}>
                  {groupedSections.map(g => (
                    <div key={g.key}>
                      <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 8 }}>
                        <span style={{ fontSize: 12, fontWeight: 600, color: "var(--text-secondary)", textTransform: "uppercase", letterSpacing: "0.05em" }}>{g.label}</span>
                        <div style={{ flex: 1, height: 1, backgroundColor: "var(--border-secondary)" }} />
                        <span style={{ fontSize: 11, color: "var(--text-muted)" }}>{g.files.length}</span>
                      </div>
                      <div className="rounded-lg border overflow-hidden" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}>
                        <table className="w-full text-sm" style={{ tableLayout: "fixed" }}>{listColGroup}{listTableHead}<tbody>{g.files.map((f, idx) => renderListRow(f, idx, g.files))}</tbody></table>
                      </div>
                    </div>
                  ))}
                </div>
              ) : (
                <div className="rounded-lg border overflow-hidden" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}>
                  <table className="w-full text-sm" style={{ tableLayout: "fixed" }}>{listColGroup}{listTableHead}<tbody>{paginatedFiles.map((f, idx) => renderListRow(f, idx, paginatedFiles))}</tbody></table>
                </div>
              )
            )}
          </div>
        );
      })()}

      {/* Devices Tab */}
      {activeTab === "Devices" && (() => {
        const items = customer.owned_items || [];
        const linkedDevices = items.filter((i) => i.type === "console_device");
        const ownedItems = items.filter((i) => i.type !== "console_device");

        const saveItems = async (newItems) => {
          setDevicesSaving(true);
          try {
            await api.put(`/crm/customers/${id}`, { owned_items: newItems });
            loadCustomer();
          } catch (err) {
            alert(err.message);
          } finally {
            setDevicesSaving(false);
          }
        };

        const removeItem = (idx) => {
          const updated = items.filter((_, i) => i !== idx);
          saveItems(updated);
        };

        // Match a product by hw_type or product_id
        function normalize(s) {
          return (s || "").toLowerCase().replace(/[^a-z0-9]/g, "");
        }
        function findProductByHwType(hwType) {
          if (!hwType) return null;
          const norm = normalize(hwType);
          return allProducts.find(p =>
            normalize(p.name) === norm ||
            normalize(p.sku) === norm ||
            normalize(p.name).includes(norm) ||
            norm.includes(normalize(p.name))
          ) || null;
        }
        function productPhotoUrl(product) {
          if (!product?.photo_url) return null;
          return `/api${product.photo_url}`;
        }

        // Filtered BellCloud devices for picker — search by name, location, or serial
        const filteredBCDevices = allDevices.filter((d) => {
          const q = linkedSearch.toLowerCase();
          return !q ||
            (d.device_name || "").toLowerCase().includes(q) ||
            (d.device_id || "").toLowerCase().includes(q) ||
            (d.device_location || "").toLowerCase().includes(q);
        });

        // Filtered products for picker
        const filteredProducts = allProducts.filter((p) => {
          const q = ownedSearch.toLowerCase();
          const catOk = ownedCatFilter === "all" || p.category === ownedCatFilter;
          return catOk && (!q || p.name.toLowerCase().includes(q) || (p.sku || "").toLowerCase().includes(q));
        });

        return (
          <div className="space-y-6">
            {/* Section 1: BellCloud Linked Devices */}
            <div className="rounded-lg border" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}>
              <div className="flex items-center justify-between px-5 py-4 border-b" style={{ borderColor: "var(--border-primary)" }}>
                <div>
                  <h2 className="text-sm font-semibold" style={{ color: "var(--text-heading)" }}>BellCloud Linked Devices</h2>
                  <p className="text-xs mt-0.5" style={{ color: "var(--text-muted)" }}>Devices managed in the BellCloud console assigned to this customer</p>
                </div>
                {canEdit && (
                  <button
                    type="button"
                    onClick={() => { setShowAddLinked(true); setLinkedSearch(""); }}
                    className="px-3 py-1.5 text-sm rounded-md cursor-pointer hover:opacity-90"
                    style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
                  >
                    + Assign Device
                  </button>
                )}
              </div>

              {linkedDevices.length === 0 ? (
                <div className="px-5 py-6 text-sm text-center" style={{ color: "var(--text-muted)" }}>No BellCloud devices assigned.</div>
              ) : (
                <div>
                  {/* Column headers */}
                  <div style={{ display: "grid", gridTemplateColumns: "88px minmax(120px,1.5fr) minmax(80px,1fr) minmax(100px,1.2fr) 32px", gap: 16, padding: "6px 20px", borderBottom: "1px solid var(--border-secondary)" }}>
                    {["Photo", "Name", "Location", "Serial", ""].map((h, i) => (
                      <span key={i} style={{ display: "block", fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.04em" }}>{h}</span>
                    ))}
                  </div>
                  {linkedDevices.map((item, i) => {
                    const globalIdx = items.indexOf(item);
                    const matchedDevice = allDevices.find((d) => (d.device_id || d.id) === item.device_id);
                    const docId = matchedDevice?.id || item.device_id;
                    const deviceLocation = matchedDevice?.device_location || "";
                    const legacy = isLegacySerial(item.device_id);
                    const matchedProduct = findProductByHwType(matchedDevice?.hw_type || matchedDevice?.device_type);
                    const thumbUrl = productPhotoUrl(matchedProduct);
                    return (
                      <div key={i}>
                        {i > 0 && (
                          <div style={{ margin: "0 20px", height: 1, backgroundColor: "rgba(255,255,255,0.05)" }} />
                        )}
                        <div
                          style={{ display: "grid", gridTemplateColumns: "88px minmax(120px,1.5fr) minmax(80px,1fr) minmax(100px,1.2fr) 32px", gap: 16, padding: "10px 20px", alignItems: "center", cursor: "pointer", transition: "background 0.1s" }}
                          className="text-sm"
                          onClick={() => navigate(`/devices/${docId}`)}
                          onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = "var(--bg-card-hover)"; setHoveredDeviceIdx(globalIdx); }}
                          onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = ""; setHoveredDeviceIdx(null); }}
                        >
                          {/* Col 0: thumbnail */}
                          <div style={{ width: 72, height: 72, borderRadius: 6, overflow: "hidden", flexShrink: 0, backgroundColor: "var(--bg-primary)", display: "flex", alignItems: "center", justifyContent: "center" }}>
                            {thumbUrl ? (
                              <img src={thumbUrl} alt="" style={{ width: "100%", height: "100%", objectFit: "contain", padding: 6 }} />
                            ) : (
                              <span style={{ fontSize: 24, opacity: 0.2 }}>📡</span>
                            )}
                          </div>
                          {/* Col 1: name */}
                          <div className="min-w-0 flex items-center gap-2">
                            <span className="font-medium truncate" style={{ color: "var(--text-primary)" }}>
                              {item.label || matchedDevice?.device_name || <span style={{ color: "var(--text-muted)", fontStyle: "italic" }}>Unknown</span>}
                            </span>
                            {legacy && <LegacyBadge />}
                          </div>
                          {/* Col 2: location */}
                          <span className="text-xs truncate" style={{ color: "var(--text-muted)" }}>
                            {deviceLocation || "—"}
                          </span>
                          {/* Col 3: serial */}
                          <span className="font-mono text-xs truncate flex-shrink-0" style={{ color: "var(--text-muted)" }}>
                            {item.device_id}
                          </span>
                          {/* Col 4: remove */}
                          <button
                            type="button"
                            onClick={(e) => { e.stopPropagation(); canEdit && removeItem(globalIdx); }}
                            style={{
                              background: "none", border: "none", cursor: canEdit ? "pointer" : "default", flexShrink: 0,
                              color: "var(--danger-text)", fontSize: 18, lineHeight: 1, padding: "2px 4px",
                              opacity: canEdit && hoveredDeviceIdx === globalIdx ? 1 : 0,
                              transition: "opacity 0.15s",
                              visibility: canEdit ? "visible" : "hidden",
                            }}
                            title="Remove"
                          >
                            ×
                          </button>
                        </div>
                      </div>
                    );
                  })}
                </div>
              )}
            </div>

            {/* Section 2: User Owned Devices */}
            <div className="rounded-lg border" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}>
              <div className="flex items-center justify-between px-5 py-4 border-b" style={{ borderColor: "var(--border-primary)" }}>
                <div>
                  <h2 className="text-sm font-semibold" style={{ color: "var(--text-heading)" }}>User Owned Devices</h2>
                  <p className="text-xs mt-0.5" style={{ color: "var(--text-muted)" }}>Products or custom devices owned by this customer</p>
                </div>
                {canEdit && (
                  <button
                    type="button"
                    onClick={() => { setShowAddOwned(true); setOwnedSearch(""); setOwnedCatFilter("all"); setAddOwnedMode("product"); setCustomDevice({ name: "", description: "", quantity: "1" }); }}
                    className="px-3 py-1.5 text-sm rounded-md cursor-pointer hover:opacity-90"
                    style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
                  >
                    + Add Device
                  </button>
                )}
              </div>

              {ownedItems.length === 0 ? (
                <div className="px-5 py-6 text-sm text-center" style={{ color: "var(--text-muted)" }}>No owned devices recorded.</div>
              ) : (
                <div>
                  {/* Column headers */}
                  <div style={{ display: "grid", gridTemplateColumns: "88px minmax(120px,1.5fr) minmax(60px,0.6fr) minmax(100px,1.2fr) 80px", gap: 16, padding: "6px 20px", borderBottom: "1px solid var(--border-secondary)" }}>
                    {["Photo", "Product", "SKU", "Serial Numbers", "Qty"].map((h) => (
                      <span key={h} style={{ display: "block", fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.04em" }}>{h}</span>
                    ))}
                  </div>
                  {ownedItems.map((item, i) => {
                    const globalIdx = items.indexOf(item);
                    const matchedProduct = item.product_id ? allProducts.find(p => p.id === item.product_id) : null;
                    return (
                      <div key={i}>
                        {i > 0 && (
                          <div style={{ margin: "0 20px", height: 1, backgroundColor: "rgba(255,255,255,0.05)" }} />
                        )}
                        <OwnedItemRow
                          item={item}
                          sku={matchedProduct?.sku || ""}
                          thumbUrl={productPhotoUrl(matchedProduct)}
                          canEdit={canEdit}
                          saving={devicesSaving}
                          onQtyChange={(newQty) => {
                            if (newQty < 1) return;
                            const updated = items.map((it, idx) => idx === globalIdx ? { ...it, quantity: newQty } : it);
                            saveItems(updated);
                          }}
                          onRemove={() => removeItem(globalIdx)}
                        />
                      </div>
                    );
                  })}
                </div>
              )}
            </div>

            {/* Modal: Assign BellCloud Device */}
            {showAddLinked && (
              <div
                style={{ position: "fixed", inset: 0, backgroundColor: "rgba(0,0,0,0.5)", zIndex: 50, display: "flex", alignItems: "center", justifyContent: "center" }}
                onClick={(e) => { if (e.target === e.currentTarget) setShowAddLinked(false); }}
              >
                <div className="rounded-lg border" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)", width: 520, display: "flex", flexDirection: "column" }}>
                  <div className="flex items-center justify-between px-5 py-4 border-b flex-shrink-0" style={{ borderColor: "var(--border-primary)" }}>
                    <h3 className="text-sm font-semibold" style={{ color: "var(--text-heading)" }}>Assign BellCloud Device</h3>
                    <button type="button" onClick={() => setShowAddLinked(false)} style={{ color: "var(--text-muted)", background: "none", border: "none", fontSize: 18, cursor: "pointer" }}>×</button>
                  </div>
                  <div className="px-5 py-3 border-b flex-shrink-0" style={{ borderColor: "var(--border-primary)" }}>
                    <input
                      className="w-full px-3 py-2 text-sm rounded-md border"
                      style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }}
                      placeholder="Search by name or location..."
                      value={linkedSearch}
                      onChange={(e) => setLinkedSearch(e.target.value)}
                      autoFocus
                    />
                  </div>
                  <div style={{ overflowY: "auto", maxHeight: 264 }}>
                    {/* Column headers */}
                    {filteredBCDevices.length > 0 && (
                      <div
                        style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12, padding: "8px 20px", borderBottom: "1px solid var(--border-secondary)" }}
                      >
                        <span style={{ fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.05em" }}>Name</span>
                        <span style={{ fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.05em" }}>Location</span>
                        <span style={{ fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.05em" }}>Serial</span>
                      </div>
                    )}
                    {filteredBCDevices.length === 0 ? (
                      <div className="p-5 text-sm text-center" style={{ color: "var(--text-muted)" }}>No devices found.</div>
                    ) : filteredBCDevices.map((d) => {
                      const devId = d.device_id || d.id;
                      const alreadyLinked = linkedDevices.some((it) => it.device_id === devId);
                      return (
                        <div
                          key={devId}
                          style={{
                            display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12,
                            padding: "10px 20px", borderBottom: "1px solid var(--border-secondary)",
                            cursor: alreadyLinked ? "default" : "pointer",
                            opacity: alreadyLinked ? 0.5 : 1,
                          }}
                          onClick={async () => {
                            if (alreadyLinked || devicesSaving) return;
                            const newItem = { type: "console_device", device_id: devId, label: d.device_name || "" };
                            await saveItems([...items, newItem]);
                            setShowAddLinked(false);
                          }}
                          onMouseEnter={(e) => { if (!alreadyLinked) e.currentTarget.style.backgroundColor = "var(--bg-card-hover)"; }}
                          onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = ""; }}
                        >
                          <div className="flex items-center gap-2 min-w-0">
                            <span className="text-sm font-medium truncate" style={{ color: "var(--text-primary)" }}>
                              {d.device_name || devId}
                            </span>
                            {isLegacySerial(devId) && <LegacyBadge />}
                            {alreadyLinked && <span className="text-xs" style={{ color: "var(--text-muted)" }}>Assigned</span>}
                          </div>
                          <span className="text-sm truncate" style={{ color: "var(--text-muted)" }}>{d.device_location || "—"}</span>
                          <span className="font-mono text-xs truncate" style={{ color: "var(--text-muted)" }}>{devId}</span>
                        </div>
                      );
                    })}
                  </div>
                </div>
              </div>
            )}

            {/* Modal: Add Owned Device */}
            {showAddOwned && (
              <div
                style={{ position: "fixed", inset: 0, backgroundColor: "rgba(0,0,0,0.5)", zIndex: 50, display: "flex", alignItems: "center", justifyContent: "center" }}
                onClick={(e) => { if (e.target === e.currentTarget) setShowAddOwned(false); }}
              >
                <div className="rounded-lg border" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)", width: 560, display: "flex", flexDirection: "column" }}>
                  <div className="flex items-center justify-between px-5 py-4 border-b flex-shrink-0" style={{ borderColor: "var(--border-primary)" }}>
                    <h3 className="text-sm font-semibold" style={{ color: "var(--text-heading)" }}>Add Owned Device</h3>
                    <button type="button" onClick={() => setShowAddOwned(false)} style={{ color: "var(--text-muted)", background: "none", border: "none", fontSize: 18, cursor: "pointer" }}>×</button>
                  </div>

                  {/* Mode selector */}
                  <div className="flex gap-2 px-5 py-3 border-b flex-shrink-0" style={{ borderColor: "var(--border-primary)" }}>
                    {["product", "custom"].map((m) => (
                      <button
                        key={m}
                        type="button"
                        onClick={() => setAddOwnedMode(m)}
                        className="px-3 py-1.5 text-sm rounded-md cursor-pointer capitalize"
                        style={{
                          backgroundColor: addOwnedMode === m ? "var(--btn-primary)" : "transparent",
                          color: addOwnedMode === m ? "var(--text-white)" : "var(--text-muted)",
                          border: addOwnedMode === m ? "none" : "1px solid var(--border-primary)",
                        }}
                      >
                        {m === "product" ? "From Products" : "Custom Device"}
                      </button>
                    ))}
                  </div>

                  {addOwnedMode === "product" && (
                    <>
                      <div className="px-5 py-3 border-b flex-shrink-0 flex gap-2" style={{ borderColor: "var(--border-primary)" }}>
                        <input
                          className="flex-1 px-3 py-2 text-sm rounded-md border"
                          style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }}
                          placeholder="Search by name or SKU..."
                          value={ownedSearch}
                          onChange={(e) => setOwnedSearch(e.target.value)}
                          autoFocus
                        />
                        <select
                          className="px-3 py-2 text-sm rounded-md border"
                          style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }}
                          value={ownedCatFilter}
                          onChange={(e) => setOwnedCatFilter(e.target.value)}
                        >
                          <option value="all">All categories</option>
                          {Object.entries(CATEGORY_LABELS).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
                        </select>
                      </div>
                      <div style={{ overflowY: "auto", maxHeight: 264 }}>
                        {/* Column headers */}
                        {filteredProducts.length > 0 && (
                          <div style={{ display: "grid", gridTemplateColumns: "1fr auto 80px 80px", gap: 12, padding: "8px 20px", borderBottom: "1px solid var(--border-secondary)" }}>
                            <span style={{ fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.05em" }}>Product</span>
                            <span style={{ fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.05em" }}>SKU</span>
                            <span style={{ fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.05em" }}>Owned</span>
                            <span style={{ fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.05em" }}>Add Qty</span>
                          </div>
                        )}
                        {filteredProducts.length === 0 ? (
                          <div className="p-5 text-sm text-center" style={{ color: "var(--text-muted)" }}>No products found.</div>
                        ) : filteredProducts.map((p) => {
                          const existing = ownedItems.find((it) => it.type === "product" && it.product_id === p.id);
                          return (
                            <ProductPickerRow
                              key={p.id}
                              product={p}
                              existing={existing}
                              saving={devicesSaving}
                              onAdd={async (qty) => {
                                if (devicesSaving) return;
                                let newItems;
                                if (existing) {
                                  // Merge: increment existing entry's quantity
                                  newItems = items.map((it) =>
                                    it === existing
                                      ? { ...it, quantity: (it.quantity || 1) + qty }
                                      : it
                                  );
                                } else {
                                  newItems = [...items, { type: "product", product_id: p.id, product_name: p.name, quantity: qty, serial_numbers: [] }];
                                }
                                await saveItems(newItems);
                                setShowAddOwned(false);
                              }}
                            />
                          );
                        })}
                      </div>
                    </>
                  )}

                  {addOwnedMode === "custom" && (
                    <div className="px-5 py-4 flex flex-col gap-4 flex-1">
                      <div>
                        <label style={labelStyle}>Device Name *</label>
                        <input
                          className="w-full px-3 py-2 text-sm rounded-md border"
                          style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }}
                          placeholder="e.g. Custom Bell Controller"
                          value={customDevice.name}
                          onChange={(e) => setCustomDevice((c) => ({ ...c, name: e.target.value }))}
                          autoFocus
                        />
                      </div>
                      <div>
                        <label style={labelStyle}>Short Description</label>
                        <input
                          className="w-full px-3 py-2 text-sm rounded-md border"
                          style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }}
                          placeholder="Optional description..."
                          value={customDevice.description}
                          onChange={(e) => setCustomDevice((c) => ({ ...c, description: e.target.value }))}
                        />
                      </div>
                      <div style={{ width: 120 }}>
                        <label style={labelStyle}>Quantity</label>
                        <input
                          type="number"
                          min="1"
                          className="w-full px-3 py-2 text-sm rounded-md border"
                          style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }}
                          value={customDevice.quantity}
                          onChange={(e) => setCustomDevice((c) => ({ ...c, quantity: e.target.value }))}
                        />
                      </div>
                      <button
                        type="button"
                        disabled={!customDevice.name.trim() || devicesSaving}
                        onClick={async () => {
                          if (!customDevice.name.trim() || devicesSaving) return;
                          const newItem = {
                            type: "freetext",
                            description: customDevice.name.trim(),
                            notes: customDevice.description.trim() || null,
                            quantity: parseInt(customDevice.quantity, 10) || 1,
                          };
                          await saveItems([...items, newItem]);
                          setShowAddOwned(false);
                        }}
                        className="px-4 py-2 text-sm rounded-md cursor-pointer hover:opacity-90 self-start"
                        style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)", opacity: customDevice.name.trim() ? 1 : 0.5 }}
                      >
                        {devicesSaving ? "Saving..." : "Add Device"}
                      </button>
                    </div>
                  )}
                </div>
              </div>
            )}
          </div>
        );
      })()}

      {/* Quick-entry modals */}
      {showInitNegModal && (
        <InitNegotiationsModal
          customerId={id}
          user={user}
          onClose={() => setShowInitNegModal(false)}
          onSuccess={() => { loadCustomer(); loadOrders(); }}
        />
      )}
      {showIssueModal && (
        <RecordIssueModal
          customerId={id}
          user={user}
          onClose={() => setShowIssueModal(false)}
          onSuccess={loadCustomer}
        />
      )}
      {showPaymentModal && (
        <RecordPaymentModal
          customerId={id}
          user={user}
          orders={orders}
          onClose={() => setShowPaymentModal(false)}
          onSuccess={loadCustomer}
        />
      )}
    </div>
  );
}


