import { useState, useEffect, useRef, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import api from "../api/client";
import { useAuth } from "../auth/AuthContext";
import SearchBar from "../components/SearchBar";
import ConfirmDialog from "../components/ConfirmDialog";
import BinaryTableModal from "./BinaryTableModal";
import {
  getLocalizedValue,
  getLanguageName,
  normalizeColor,
  formatDuration,
} from "./melodyUtils";

const MELODY_TYPES = ["", "orthodox", "catholic", "all"];
const MELODY_TONES = ["", "normal", "festive", "cheerful", "lamentation"];
const NOTE_LABELS = "ABCDEFGHIJKLMNOP";
const SORTABLE_COLUMN_TO_KEY = {
  name: "name",
  totalActiveBells: "totalActiveBells",
  dateCreated: "dateCreated",
  dateEdited: "dateEdited",
};

// All available columns with their defaults
const ALL_COLUMNS = [
  { key: "color", label: "Color", defaultOn: true },
  { key: "name", label: "Name", defaultOn: true, alwaysOn: true },
  { key: "status", label: "Status", defaultOn: true },
  { key: "description", label: "Description", defaultOn: false },
  { key: "type", label: "Type", defaultOn: true },
  { key: "tone", label: "Tone", defaultOn: true },
  { key: "totalActiveBells", label: "Unique Bells", defaultOn: true },
  { key: "minSpeed", label: "Min Speed", defaultOn: false },
  { key: "maxSpeed", label: "Max Speed", defaultOn: false },
  { key: "tags", label: "Tags", defaultOn: false },
  { key: "speed", label: "Speed", defaultOn: false },
  { key: "duration", label: "Duration", defaultOn: false },
  { key: "totalRunDuration", label: "Total Run", defaultOn: false },
  { key: "pauseDuration", label: "Pause", defaultOn: false },
  { key: "infiniteLoop", label: "Infinite", defaultOn: false },
  { key: "noteAssignments", label: "Note Assignments", defaultOn: false },
  { key: "binaryFile", label: "Binary File", defaultOn: false },
  { key: "dateCreated", label: "Date Created", defaultOn: false },
  { key: "dateEdited", label: "Last Edited", defaultOn: false },
  { key: "createdBy", label: "Created By", defaultOn: false },
  { key: "lastEditedBy", label: "Last Edited By", defaultOn: false },
  { key: "isTrueRing", label: "True Ring", defaultOn: true },
  { key: "docId", label: "Document ID", defaultOn: false },
  { key: "pid", label: "PID", defaultOn: false },
];

const MEL_COL_VIS_KEY   = "melodyListColumns";
const MEL_COL_ORDER_KEY = "melodyListColumnsOrder";

function loadColumnPrefs() {
  try {
    const vis   = JSON.parse(localStorage.getItem(MEL_COL_VIS_KEY)   || "null");
    const order = JSON.parse(localStorage.getItem(MEL_COL_ORDER_KEY) || "null");
    const visible = vis
      ? Object.fromEntries(ALL_COLUMNS.map((c) => [c.key, vis.includes ? vis.includes(c.key) : Boolean(vis[c.key])]))
      : Object.fromEntries(ALL_COLUMNS.map((c) => [c.key, c.defaultOn]));
    const orderedIds = order || ALL_COLUMNS.map((c) => c.key);
    // always-on columns
    for (const c of ALL_COLUMNS) {
      if (c.alwaysOn) visible[c.key] = true;
      if (!orderedIds.includes(c.key)) orderedIds.push(c.key);
    }
    return { visible, orderedIds };
  } catch {
    return {
      visible:    Object.fromEntries(ALL_COLUMNS.map((c) => [c.key, c.defaultOn])),
      orderedIds: ALL_COLUMNS.map((c) => c.key),
    };
  }
}

function saveColumnPrefs(visible, orderedIds) {
  localStorage.setItem(MEL_COL_VIS_KEY,   JSON.stringify(visible));
  localStorage.setItem(MEL_COL_ORDER_KEY, JSON.stringify(orderedIds));
}

// ─── Melody Column Toggle (drag-and-drop) ─────────────────────────────────────

function MelodyColumnToggle({ visible, orderedIds, onChange, onReorder }) {
  const [open, setOpen]       = useState(false);
  const [dragging, setDragging] = useState(null);
  const ref = useRef(null);

  useEffect(() => {
    const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, []);

  const handleDragStart = (id) => setDragging(id);
  const handleDragOver  = (e, id) => {
    e.preventDefault();
    if (dragging && dragging !== id) {
      const next = [...orderedIds];
      const from = next.indexOf(dragging);
      const to   = next.indexOf(id);
      next.splice(from, 1);
      next.splice(to, 0, dragging);
      onReorder(next);
    }
  };
  const handleDragEnd = () => setDragging(null);

  const visibleCount = Object.values(visible).filter(Boolean).length;

  return (
    <div className="relative" ref={ref}>
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        className="flex items-center gap-1.5 px-3 py-2 text-sm rounded-md transition-colors cursor-pointer border"
        style={{ borderColor: "var(--border-primary)", color: "var(--text-secondary)" }}
      >
        <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
        </svg>
        Columns ({visibleCount})
      </button>

      {open && (
        <div
          className="absolute right-0 top-full mt-1 z-[9999] rounded-lg border shadow-lg p-2 overflow-y-auto"
          style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)", width: 220, maxHeight: 420 }}
        >
          <p className="text-xs font-medium px-2 py-1 mb-1" style={{ color: "var(--text-muted)" }}>
            Drag to reorder
          </p>
          {orderedIds.map((key) => {
            const col = ALL_COLUMNS.find((c) => c.key === key);
            if (!col) return null;
            return (
              <div
                key={key}
                draggable={!col.alwaysOn}
                onDragStart={() => !col.alwaysOn && handleDragStart(key)}
                onDragOver={(e) => handleDragOver(e, key)}
                onDragEnd={handleDragEnd}
                className="flex items-center gap-2 px-2 py-1.5 rounded select-none"
                style={{
                  cursor: col.alwaysOn ? "default" : "grab",
                  backgroundColor: dragging === key ? "var(--bg-card-hover)" : "transparent",
                  opacity: dragging === key ? 0.5 : 1,
                }}
              >
                <span style={{ color: "var(--text-muted)", fontSize: 10, opacity: col.alwaysOn ? 0.3 : 1 }}>⠿</span>
                <label className="flex items-center gap-2 cursor-pointer flex-1">
                  <input
                    type="checkbox"
                    checked={!!visible[key]}
                    disabled={col.alwaysOn}
                    onChange={(e) => onChange(key, e.target.checked)}
                    className="cursor-pointer"
                    onClick={(e) => e.stopPropagation()}
                  />
                  <span
                    className="text-sm"
                    style={{ color: col.alwaysOn ? "var(--text-muted)" : "var(--text-secondary)" }}
                  >
                    {col.label}
                  </span>
                </label>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

function speedBarColor(speedPercent) {
  const v = Math.max(0, Math.min(100, Number(speedPercent || 0)));
  if (v <= 50) {
    const t = v / 50;
    const hue = 120 + (210 - 120) * t; // green -> blue
    return `hsl(${hue}, 85%, 46%)`;
  }
  const t = (v - 50) / 50;
  const hue = 210 + (0 - 210) * t; // blue -> red
  return `hsl(${hue}, 85%, 46%)`;
}

function durationBarColor(percent) {
  const v = Math.max(0, Math.min(100, Number(percent || 0)));
  const hue = 220 + (0 - 220) * (v / 100); // blue -> red
  return `hsl(${hue}, 85%, 46%)`;
}

function formatBpm(ms) {
  const value = Number(ms);
  if (!value || value <= 0) return null;
  return Math.round(60000 / value);
}

function mapPercentageToStepDelay(percent, minSpeed, maxSpeed) {
  if (minSpeed == null || maxSpeed == null) return null;
  const p = Math.max(0, Math.min(100, Number(percent || 0)));
  const t = p / 100;
  const a = Number(minSpeed);
  const b = Number(maxSpeed);
  if (a <= 0 || b <= 0) return Math.round(a + (b - a) * t);
  return Math.round(a * Math.pow(b / a, t));
}

function parseDateValue(isoValue) {
  if (!isoValue) return 0;
  const time = new Date(isoValue).getTime();
  return Number.isNaN(time) ? 0 : time;
}

function formatDurationVerbose(seconds) {
  const total = Number(seconds || 0);
  const mins = Math.floor(total / 60);
  const secs = total % 60;
  return `${mins} min ${secs} sec`;
}

function formatDateTwoLine(isoValue) {
  const d = new Date(isoValue);
  if (Number.isNaN(d.getTime())) return { date: "-", time: "-" };
  const dd = String(d.getDate()).padStart(2, "0");
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const yyyy = d.getFullYear();
  const hh = String(d.getHours()).padStart(2, "0");
  const min = String(d.getMinutes()).padStart(2, "0");
  return { date: `${dd}/${mm}/${yyyy}`, time: `${hh}:${min}` };
}

function formatRelativeTime(isoValue) {
  if (!isoValue) return "-";
  const ts = new Date(isoValue).getTime();
  if (Number.isNaN(ts)) return "-";
  const now = Date.now();
  const diffSec = Math.max(1, Math.floor((now - ts) / 1000));
  if (diffSec < 60) return `${diffSec} sec ago`;
  const diffMin = Math.floor(diffSec / 60);
  if (diffMin < 60) return `${diffMin} min ago`;
  const diffHours = Math.floor(diffMin / 60);
  if (diffHours < 24) return `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`;
  const diffDays = Math.floor(diffHours / 24);
  if (diffDays < 30) return `${diffDays} day${diffDays === 1 ? "" : "s"} ago`;
  const diffMonths = Math.floor(diffDays / 30);
  if (diffMonths < 12) return `${diffMonths} month${diffMonths === 1 ? "" : "s"} ago`;
  const diffYears = Math.floor(diffMonths / 12);
  return `${diffYears} year${diffYears === 1 ? "" : "s"} ago`;
}

function toSafeCppSymbol(input, fallback = "melody") {
  const base = String(input || "").trim().toLowerCase();
  const cleaned = base.replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
  const symbol = cleaned || fallback;
  return /^[a-z_]/.test(symbol) ? symbol : `m_${symbol}`;
}

function escapeCppString(value) {
  return String(value || "").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
}

function parseStepTokenToMask(token) {
  const raw = String(token || "").trim();
  if (!raw) return 0;
  if (raw.startsWith("0x") || raw.startsWith("0X")) {
    const parsed = Number.parseInt(raw, 16);
    return Number.isNaN(parsed) ? 0 : parsed & 0xffff;
  }
  if (raw.includes("+")) {
    return raw
      .split("+")
      .map((part) => Number.parseInt(part.trim(), 10))
      .filter((n) => Number.isInteger(n) && n > 0)
      .reduce((mask, bell) => {
        if (bell > 16) return mask;
        return mask | (1 << (bell - 1));
      }, 0) & 0xffff;
  }
  const n = Number.parseInt(raw, 10);
  if (!Number.isInteger(n) || n < 0) return 0;
  if (n === 0) return 0;
  if (n <= 16) return (1 << (n - 1)) & 0xffff;
  return n & 0xffff;
}

function parseArchetypeCsv(archetypeCsv) {
  if (!archetypeCsv || typeof archetypeCsv !== "string") return [];
  return archetypeCsv
    .split(",")
    .map((step) => step.trim())
    .filter(Boolean)
    .map(parseStepTokenToMask);
}

function formatHex16(value) {
  return `0x${(Number(value || 0) & 0xffff).toString(16).toUpperCase().padStart(4, "0")}`;
}


export default function MelodyList() {
  const [melodies, setMelodies] = useState([]);
  const [total, setTotal] = useState(0);
  const [allMelodyCount, setAllMelodyCount] = useState(0);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [search, setSearch] = useState("");
  const [typeFilter, setTypeFilter] = useState("");
  const [toneFilter, setToneFilter] = useState("");
  const [statusFilter, setStatusFilter] = useState("");
  const [createdByFilter, setCreatedByFilter] = useState([]);
  const [sortBy, setSortBy] = useState("dateCreated");
  const [sortDir, setSortDir] = useState("desc");
  const [displayLang, setDisplayLang] = useState("en");
  const [melodySettings, setMelodySettings] = useState(null);
  const [deleteTarget, setDeleteTarget] = useState(null);
  const [unpublishTarget, setUnpublishTarget] = useState(null);
  const [actionLoading, setActionLoading] = useState(null);
  const [colPrefs, setColPrefs] = useState(loadColumnPrefs);
  const [showCreatorPicker, setShowCreatorPicker] = useState(false);
  const [viewRow, setViewRow] = useState(null);
  const [builtMap, setBuiltMap] = useState({});
  const [pageSize, setPageSize] = useState(20);
  const [page, setPage] = useState(1);
  const creatorPickerRef = useRef(null);

  // Derived helpers from colPrefs
  const visibleColumns = colPrefs.orderedIds.filter((k) => colPrefs.visible[k]);
  const isVisible = (key) => Boolean(colPrefs.visible[key]);

  const handleColVisChange = (key, checked) => {
    const col = ALL_COLUMNS.find((c) => c.key === key);
    if (col?.alwaysOn) return;
    setColPrefs((prev) => {
      const next = { ...prev, visible: { ...prev.visible, [key]: checked } };
      saveColumnPrefs(next.visible, next.orderedIds);
      return next;
    });
  };

  const handleColReorder = (orderedIds) => {
    setColPrefs((prev) => {
      const next = { ...prev, orderedIds };
      saveColumnPrefs(next.visible, next.orderedIds);
      return next;
    });
  };
  const navigate = useNavigate();
  const { hasPermission } = useAuth();
  const canEdit = hasPermission("melodies", "edit");

  useEffect(() => {
    api.get("/settings/melody").then((ms) => {
      setMelodySettings(ms);
      setDisplayLang(ms.primary_language || "en");
    });
  }, []);

  // Close creator picker on outside click
  useEffect(() => {
    const handleClick = (e) => {
      if (creatorPickerRef.current && !creatorPickerRef.current.contains(e.target)) {
        setShowCreatorPicker(false);
      }
    };
    if (showCreatorPicker) {
      document.addEventListener("mousedown", handleClick);
      return () => document.removeEventListener("mousedown", handleClick);
    }
  }, [showCreatorPicker]);

  const fetchMelodies = async () => {
    setLoading(true);
    setError("");
    try {
      const params = new URLSearchParams();
      if (search) params.set("search", search);
      if (typeFilter) params.set("type", typeFilter);
      if (toneFilter) params.set("tone", toneFilter);
      if (statusFilter) params.set("status", statusFilter);
      const qs = params.toString();
      const [data, allData] = await Promise.all([
        api.get(`/melodies${qs ? `?${qs}` : ""}`),
        api.get("/melodies"),
      ]);
      setMelodies(data.melodies || []);
      setTotal(data.total || 0);
      setAllMelodyCount(allData.total || (allData.melodies || []).length || 0);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchMelodies();
  }, [search, typeFilter, toneFilter, statusFilter]);

  useEffect(() => {
    let canceled = false;
    const loadBuiltAssignments = async () => {
      if (!melodies.length) {
        setBuiltMap({});
        return;
      }
      const pairs = await Promise.all(
        melodies.map(async (m) => {
          try {
            const bm = await api.get(`/builder/melodies/for-melody/${m.id}`);
            return [m.id, bm || null];
          } catch {
            return [m.id, null];
          }
        })
      );
      if (canceled) return;
      const next = {};
      for (const [id, bm] of pairs) next[id] = bm;
      setBuiltMap(next);
    };
    loadBuiltAssignments();
    return () => { canceled = true; };
  }, [melodies]);

  const resolveEffectiveBinary = (row) => {
    const built = builtMap[row?.id] || null;
    const candidate = built?.binary_url
      ? `/api${built.binary_url}`
      : row?.url || null;
    const url = candidate
      ? (candidate.startsWith("http") || candidate.startsWith("/api")
        ? candidate
        : candidate.startsWith("/")
          ? `/api${candidate}`
          : `/api/${candidate}`)
      : null;
    const source = built?.binary_url ? "Archetype" : (url ? "Melody URL" : null);
    const missingArchetype = Boolean(row?.pid) && !built?.id;
    const filename = (() => {
      if (built?.pid) return `${built.pid}.bsm`;
      if (row?.pid) return `${row.pid}.bsm`;
      if (url) {
        try {
          const parsed = new URL(url, window.location.origin);
          const path = decodeURIComponent(parsed.pathname || "");
          const parts = path.split("/");
          const name = parts[parts.length - 1];
          if (name && name.toLowerCase().endsWith(".bsm")) return name;
        } catch {
          // fallback below
        }
      }
      return "melody.bsm";
    })();
    return { url, filename, source, built, missingArchetype };
  };

  const handleDelete = async () => {
    if (!deleteTarget) return;
    try {
      await api.delete(`/melodies/${deleteTarget.id}`);
      setDeleteTarget(null);
      fetchMelodies();
    } catch (err) {
      setError(err.message);
      setDeleteTarget(null);
    }
  };

  const handlePublish = async (row) => {
    setActionLoading(row.id);
    try {
      await api.post(`/melodies/${row.id}/publish`);
      fetchMelodies();
    } catch (err) {
      setError(err.message);
    } finally {
      setActionLoading(null);
    }
  };

  const handleUnpublish = async () => {
    if (!unpublishTarget) return;
    setActionLoading(unpublishTarget.id);
    try {
      await api.post(`/melodies/${unpublishTarget.id}/unpublish`);
      setUnpublishTarget(null);
      fetchMelodies();
    } catch (err) {
      setError(err.message);
      setUnpublishTarget(null);
    } finally {
      setActionLoading(null);
    }
  };

  const downloadBinary = async (e, row) => {
    e.stopPropagation();
    const resolved = resolveEffectiveBinary(row);
    const binaryUrl = resolved.url;
    const preferredUrl = row?.id ? `/api/melodies/${row.id}/download/binary` : binaryUrl;
    if (!binaryUrl) return;

    try {
      const token = localStorage.getItem("access_token");
      let res = null;
      try {
        res = await fetch(preferredUrl, {
          headers: token ? { Authorization: `Bearer ${token}` } : {},
        });
      } catch {
        if (binaryUrl.startsWith("http")) {
          res = await fetch(binaryUrl);
        } else {
          throw new Error("Download failed: network error");
        }
      }
      if ((!res || !res.ok) && binaryUrl.startsWith("http")) {
        res = await fetch(binaryUrl);
      }
      if (!res.ok) throw new Error(`Download failed: ${res.statusText}`);

      const blob = await res.blob();
      const objectUrl = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = objectUrl;
      a.download = resolved.filename || "melody.bsm";
      a.click();
      URL.revokeObjectURL(objectUrl);
    } catch (err) {
      const fallback = resolveEffectiveBinary(row);
      const fallbackUrl = fallback.url;
      if (fallbackUrl) {
        const a = document.createElement("a");
        a.href = fallbackUrl;
        a.download = fallback.filename || "melody.bsm";
        a.rel = "noopener noreferrer";
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
      } else {
        setError(err.message);
      }
    }
  };

  const openBinaryView = (e, row) => {
    e.stopPropagation();
    setViewRow(row);
  };

  const toggleCreator = (creator) => {
    setCreatedByFilter((prev) =>
      prev.includes(creator) ? prev.filter((c) => c !== creator) : [...prev, creator]
    );
  };

  const getDisplayName = (nameVal) => getLocalizedValue(nameVal, displayLang, "Untitled");

  const allCreators = useMemo(() => {
    const creators = new Set();
    for (const row of melodies) {
      const creator = row?.metadata?.createdBy;
      if (creator) creators.add(creator);
    }
    return Array.from(creators).sort((a, b) => a.localeCompare(b));
  }, [melodies]);

  const getSortValue = (row, key) => {
    const info = row?.information || {};
    const metadata = row?.metadata || {};

    switch (key) {
      case "name":
        return getDisplayName(info.name).toLowerCase();
      case "totalActiveBells":
        return Number(info.totalActiveBells || 0);
      case "dateEdited":
        return parseDateValue(metadata.dateEdited);
      case "dateCreated":
      default:
        return parseDateValue(metadata.dateCreated);
    }
  };

  const displayRows = useMemo(() => {
    let rows = melodies;

    if (createdByFilter.length > 0) {
      rows = rows.filter((row) => createdByFilter.includes(row?.metadata?.createdBy || ""));
    }

    return [...rows].sort((a, b) => {
      const av = getSortValue(a, sortBy);
      const bv = getSortValue(b, sortBy);

      if (typeof av === "string" && typeof bv === "string") {
        return sortDir === "asc" ? av.localeCompare(bv) : bv.localeCompare(av);
      }

      return sortDir === "asc" ? av - bv : bv - av;
    });
  }, [melodies, createdByFilter, sortBy, sortDir]); // eslint-disable-line react-hooks/exhaustive-deps

  const totalPages = pageSize > 0 ? Math.ceil(displayRows.length / pageSize) : 1;
  const safePage = Math.min(page, Math.max(1, totalPages));
  const pagedRows = pageSize > 0
    ? displayRows.slice((safePage - 1) * pageSize, safePage * pageSize)
    : displayRows;


  const hasAnyFilter = Boolean(
    search || typeFilter || toneFilter || statusFilter || createdByFilter.length > 0
  );

  const handleSortClick = (columnKey) => {
    const nextSortKey = SORTABLE_COLUMN_TO_KEY[columnKey];
    if (!nextSortKey) return;
    if (sortBy === nextSortKey) {
      setSortDir((prev) => (prev === "asc" ? "desc" : "asc"));
      return;
    }
    setSortBy(nextSortKey);
    setSortDir(nextSortKey === "dateCreated" || nextSortKey === "dateEdited" ? "desc" : "asc");
  };

  const getSortIndicator = (columnKey) => {
    const sortKey = SORTABLE_COLUMN_TO_KEY[columnKey];
    if (!sortKey) return null;
    if (sortBy !== sortKey) return "↕";
    return sortDir === "asc" ? "▲" : "▼";
  };

  const renderCellValue = (key, row) => {
    const info = row.information || {};
    const ds = row.default_settings || {};
    const metadata = row.metadata || {};

    switch (key) {
      case "status": {
        const isOutdated = Boolean(info.outdated_archetype);
        return (
          <div className="flex flex-col items-start gap-1">
            <span
              className="px-2 py-0.5 text-xs font-semibold rounded-full"
              style={
                row.status === "published"
                  ? { backgroundColor: "rgba(22,163,74,0.15)", color: "#22c55e" }
                  : { backgroundColor: "rgba(156,163,175,0.15)", color: "#9ca3af" }
              }
            >
              {row.status === "published" ? "Live" : "Draft"}
            </span>
            {isOutdated && (
              <span className="px-1.5 py-0.5 text-xs rounded-full font-medium"
                style={{ backgroundColor: "rgba(245,158,11,0.15)", color: "#f59e0b", border: "1px solid rgba(245,158,11,0.3)" }}
                title="This melody's archetype has been changed or removed. Re-assign an archetype to clear this.">
                Outdated
              </span>
            )}
          </div>
        );
      }
      case "color":
        return info.color ? (
          <span
            className="inline-block w-3 h-8 rounded-sm"
            style={{ backgroundColor: normalizeColor(info.color) }}
            title={info.color}
          />
        ) : (
          <span className="inline-block w-3 h-8 rounded-sm" style={{ backgroundColor: "var(--border-primary)" }} />
        );
      case "name": {
        const description = getLocalizedValue(info.description, displayLang) || "-";
        const isOutdated = Boolean(info.outdated_archetype);
        return (
          <div>
            <div className="flex items-center gap-2 flex-wrap">
              <span className="font-medium" style={{ color: isOutdated ? "#f59e0b" : "var(--text-heading)" }}>
                {getDisplayName(info.name)}
              </span>
            </div>
            {isVisible("description") && (
              <p
                className="text-xs mt-0.5 truncate max-w-xs"
                style={{ color: "var(--text-muted)" }}
                title={description}
              >
                {description}
              </p>
            )}
          </div>
        );
      }
      case "type":
        {
          const typeStyles = {
            orthodox: { color: "#7dd3fc", backgroundColor: "rgba(14,165,233,0.15)" },
            catholic: { color: "#fda4af", backgroundColor: "rgba(244,63,94,0.15)" },
            all: { color: "#86efac", backgroundColor: "rgba(34,197,94,0.14)" },
          };
          const style = typeStyles[row.type] || { color: "var(--text-secondary)", backgroundColor: "var(--bg-card-hover)" };
          return (
            <span className="px-2 py-0.5 text-xs rounded-full capitalize" style={style}>
              {row.type || "-"}
            </span>
          );
        }
      case "tone":
        return <span className="capitalize">{info.melodyTone || "-"}</span>;
      case "totalActiveBells":
        return info.totalActiveBells ?? "-";
      case "minSpeed":
        if (!info.minSpeed) return "-";
        return (
          <span className="inline-flex items-baseline gap-1 whitespace-nowrap" title={`${info.minSpeed} ms`}>
            <span>{formatBpm(info.minSpeed)}</span>
            <span className="text-[11px]" style={{ color: "var(--text-muted)" }}>bpm</span>
          </span>
        );
      case "maxSpeed":
        if (!info.maxSpeed) return "-";
        return (
          <span className="inline-flex items-baseline gap-1 whitespace-nowrap" title={`${info.maxSpeed} ms`}>
            <span>{formatBpm(info.maxSpeed)}</span>
            <span className="text-[11px]" style={{ color: "var(--text-muted)" }}>bpm</span>
          </span>
        );
      case "tags":
        return info.customTags?.length > 0 ? (
          <div className="flex flex-wrap gap-1">
            {info.customTags.map((tag) => (
              <span
                key={tag}
                className="px-1.5 py-0.5 text-xs rounded-full"
                style={{ backgroundColor: "var(--badge-blue-bg)", color: "var(--badge-blue-text)" }}
              >
                {tag}
              </span>
            ))}
          </div>
        ) : (
          "-"
        );
      case "speed":
        if (ds.speed == null) return "-";
        {
          const speedMs = mapPercentageToStepDelay(ds.speed, info.minSpeed, info.maxSpeed);
          const bpm = formatBpm(speedMs);
        return (
          <div className="min-w-36" title={speedMs ? `${speedMs} ms` : undefined}>
            <div className="text-xs mb-1 whitespace-nowrap" style={{ color: "var(--text-secondary)" }}>
              {ds.speed}%{bpm ? ` · ${bpm} bpm` : ""}
            </div>
            <div
              className="w-full h-2 rounded-full"
              style={{ backgroundColor: "var(--bg-primary)", border: "1px solid var(--border-primary)" }}
            >
              <div
                className="h-full rounded-full transition-all"
                style={{
                  width: `${Math.max(0, Math.min(100, ds.speed))}%`,
                  backgroundColor: speedBarColor(ds.speed),
                }}
              />
            </div>
          </div>
        );
        }
      case "duration":
        if (ds.duration == null) return "-";
        if (Number(ds.duration) === 0) {
          return (
            <span
              className="px-2 py-0.5 text-xs rounded-full"
              style={{ backgroundColor: "rgba(88,156,250,0.18)", color: "var(--text-link)" }}
            >
              Single Run
            </span>
          );
        }
        {
          const allDurationValues = melodySettings?.duration_values || [];
          const nonZeroDurations = allDurationValues.filter((v) => Number(v) > 0);
          const idx = nonZeroDurations.indexOf(Number(ds.duration));
          const percent = idx >= 0 && nonZeroDurations.length > 0
            ? Math.round(((idx + 1) / nonZeroDurations.length) * 100)
            : 0;
          return (
            <div className="min-w-28">
              <div className="text-xs mb-1" style={{ color: "var(--text-secondary)" }}>
                {formatDurationVerbose(ds.duration)}
              </div>
              <div
                className="w-full h-2 rounded-full"
                style={{ backgroundColor: "var(--bg-primary)", border: "1px solid var(--border-primary)" }}
              >
                <div
                  className="h-full rounded-full transition-all"
                  style={{
                    width: `${Math.max(0, Math.min(100, percent))}%`,
                    backgroundColor: durationBarColor(percent),
                  }}
                />
              </div>
            </div>
          );
        }
      case "totalRunDuration":
        return ds.totalRunDuration ?? "-";
      case "pauseDuration":
        return ds.pauseDuration ?? "-";
      case "infiniteLoop":
        return (
          <span
            className="px-2 py-0.5 text-xs rounded-full"
            style={{
              backgroundColor: ds.infiniteLoop ? "var(--success-bg)" : "var(--bg-card-hover)",
              color: ds.infiniteLoop ? "var(--success-text)" : "var(--text-muted)",
            }}
          >
            {ds.infiniteLoop ? "Yes" : "No"}
          </span>
        );
      case "noteAssignments":
        return ds.noteAssignments?.length > 0 ? (
          <div className="flex flex-nowrap gap-1 whitespace-nowrap">
            {ds.noteAssignments.map((assignedBell, noteIdx) => (
              <div
                key={noteIdx}
                className="flex flex-col items-center rounded-md border"
                style={{
                  minWidth: "26px",
                  padding: "3px 3px",
                  backgroundColor: "var(--bg-card-hover)",
                  borderColor: "var(--border-primary)",
                }}
              >
                <span className="text-xs font-bold leading-tight" style={{ color: "var(--text-secondary)" }}>
                  {NOTE_LABELS[noteIdx]}
                </span>
                <div className="w-full my-0.5" style={{ height: "1px", backgroundColor: "var(--border-primary)" }} />
                <span className="text-xs leading-tight" style={{ color: "var(--text-muted)" }}>
                  {assignedBell > 0 ? assignedBell : "-"}
                </span>
              </div>
            ))}
          </div>
        ) : (
          "-"
        );
      case "binaryFile": {
        const resolved = resolveEffectiveBinary(row);
        const binaryUrl = resolved.url;
        const filename = resolved.filename;
        const totalNotes = info.totalNotes ?? 0;
        if (!binaryUrl) {
          return (
            <span className="inline-flex flex-col">
              <span style={{ color: "var(--text-muted)" }}>-</span>
              <span className="text-xs" style={{ color: "var(--text-muted)" }}>{totalNotes} active notes</span>
            </span>
          );
        }
        return (
          <span className="inline-flex flex-col">
            <span className="inline-flex items-center gap-2">
              <span className="text-xs" style={{ color: "var(--text-secondary)" }}>{filename || "binary.bsm"}</span>
              {resolved.missingArchetype && (
                <span
                  className="text-xs cursor-help"
                  style={{ color: "#f59e0b" }}
                  title="This binary does not exist in the Archetypes"
                >
                  ⚠
                </span>
              )}
            </span>
            <span className="mt-1 inline-flex gap-1.5">
              <button
                type="button"
                onClick={(e) => downloadBinary(e, row)}
                className="px-2 py-0.5 text-xs rounded-full"
                style={{ color: "var(--text-link)", backgroundColor: "rgba(88,156,250,0.14)" }}
                title={binaryUrl}
              >
                Download
              </button>
              <button
                type="button"
                onClick={(e) => openBinaryView(e, row)}
                className="px-2 py-0.5 text-xs rounded-full"
                style={{ color: "var(--text-secondary)", backgroundColor: "var(--bg-card-hover)" }}
              >
                View
              </button>
            </span>
            <span className="text-xs" style={{ color: "var(--text-muted)" }}>{totalNotes} active notes</span>
          </span>
        );
      }
      case "dateCreated":
        if (!metadata.dateCreated) return "-";
        {
          const parts = formatDateTwoLine(metadata.dateCreated);
          return (
            <span className="inline-flex flex-col leading-tight">
              <span>{parts.date}</span>
              <span className="text-xs" style={{ color: "var(--text-muted)" }}>{parts.time}</span>
            </span>
          );
        }
      case "dateEdited":
        return metadata.dateEdited ? (
          <span title={new Date(metadata.dateEdited).toLocaleString()}>
            {formatRelativeTime(metadata.dateEdited)}
          </span>
        ) : "-";
      case "createdBy":
        return metadata.createdBy || "-";
      case "lastEditedBy":
        return metadata.lastEditedBy || "-";
      case "isTrueRing":
        return (
          <span
            className="px-2 py-0.5 text-xs rounded-full"
            style={{
              backgroundColor: info.isTrueRing ? "var(--success-bg)" : "var(--bg-card-hover)",
              color: info.isTrueRing ? "var(--success-text)" : "var(--text-muted)",
            }}
          >
            {info.isTrueRing ? "Yes" : "No"}
          </span>
        );
      case "docId":
        return <span className="font-mono text-xs" style={{ color: "var(--text-muted)" }}>{row.id}</span>;
      case "pid":
        return row.pid || "-";
      default:
        return "-";
    }
  };

  // Build visible column list in user-defined order (description is rendered inside name)
  const activeColumns = visibleColumns
    .filter((key) => key !== "description")
    .map((key) => ALL_COLUMNS.find((c) => c.key === key))
    .filter(Boolean);

  const languages = melodySettings?.available_languages || ["en"];

  const selectClass = "px-3 py-2 rounded-md text-sm cursor-pointer border";

  return (
    <div className="w-full min-w-0 max-w-full">
      <div className="w-full min-w-0 relative" style={{ zIndex: 30 }}>
      <div className="flex items-center justify-between mb-4 w-full min-w-0 gap-3 flex-wrap">
        <h1 className="text-2xl font-bold" style={{ color: "var(--text-heading)" }}>Melody Library</h1>
        <div className="flex items-center gap-3 flex-wrap">
          <span className="text-sm whitespace-nowrap" style={{ color: "var(--text-muted)" }}>
            {hasAnyFilter
              ? `Filtered — ${displayRows.length} / ${allMelodyCount || melodies.length}`
              : `Showing all (${allMelodyCount || melodies.length})`}
          </span>
          {canEdit && (
            <button
              onClick={() => navigate("/melodies/new")}
              className="px-4 py-2 text-sm rounded-md transition-colors cursor-pointer"
              style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
            >
              Add Melody
            </button>
          )}
        </div>
      </div>

      <div className="mb-4 flex flex-wrap gap-3 items-center w-full min-w-0 relative" style={{ zIndex: 40 }}>
        <SearchBar
          onSearch={setSearch}
          placeholder="Search by name, description, or tags..."
          style={{ flex: "1 1 250px", minWidth: 250 }}
        />
        <select
          value={typeFilter}
          onChange={(e) => setTypeFilter(e.target.value)}
          className={selectClass}
        >
          <option value="">All Types</option>
          {MELODY_TYPES.filter(Boolean).map((t) => (
            <option key={t} value={t}>
              {t.charAt(0).toUpperCase() + t.slice(1)}
            </option>
          ))}
        </select>

        <select
          value={toneFilter}
          onChange={(e) => setToneFilter(e.target.value)}
          className={selectClass}
        >
          <option value="">All Tones</option>
          {MELODY_TONES.filter(Boolean).map((t) => (
            <option key={t} value={t}>
              {t.charAt(0).toUpperCase() + t.slice(1)}
            </option>
          ))}
        </select>

        <select
          value={statusFilter}
          onChange={(e) => setStatusFilter(e.target.value)}
          className={selectClass}
        >
          <option value="">All Statuses</option>
          <option value="published">Published (Live)</option>
          <option value="draft">Drafts</option>
        </select>

        <div className="relative" ref={creatorPickerRef} style={{ zIndex: 60 }}>
          <button
            type="button"
            onClick={() => setShowCreatorPicker((prev) => !prev)}
            className="px-3 py-2 rounded-md text-sm transition-colors cursor-pointer flex items-center gap-1.5 border"
            style={{ borderColor: "var(--border-primary)", color: "var(--text-secondary)" }}
          >
            Created By
            {createdByFilter.length > 0 ? ` (${createdByFilter.length})` : ""}
          </button>
          {showCreatorPicker && (
            <div
              className="absolute left-0 top-full mt-1 rounded-lg shadow-lg py-2 w-64 border max-h-64 overflow-auto"
              style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)", zIndex: 9999 }}
            >
              {allCreators.length === 0 ? (
                <p className="px-3 py-1.5 text-sm" style={{ color: "var(--text-muted)" }}>No creators found</p>
              ) : (
                allCreators.map((creator) => (
                  <label
                    key={creator}
                    className="flex items-center gap-2 px-3 py-1.5 text-sm cursor-pointer"
                    style={{ color: "var(--text-primary)" }}
                  >
                    <input
                      type="checkbox"
                      checked={createdByFilter.includes(creator)}
                      onChange={() => toggleCreator(creator)}
                      className="h-3.5 w-3.5 rounded cursor-pointer"
                    />
                    {creator}
                  </label>
                ))
              )}
              {createdByFilter.length > 0 && (
                <button
                  type="button"
                  onClick={() => setCreatedByFilter([])}
                  className="w-full text-left px-3 py-1.5 text-xs underline"
                  style={{ color: "var(--accent)", background: "none", border: "none" }}
                >
                  Clear selection
                </button>
              )}
            </div>
          )}
        </div>

        {languages.length > 1 && (
          <select
            value={displayLang}
            onChange={(e) => setDisplayLang(e.target.value)}
            className={selectClass}
          >
            {languages.map((l) => (
              <option key={l} value={l}>
                {getLanguageName(l)}
              </option>
            ))}
          </select>
        )}

        <select
          value={String(pageSize)}
          onChange={(e) => { setPageSize(Number(e.target.value)); setPage(1); }}
          className={selectClass}
          style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)", color: "var(--text-primary)" }}
        >
          <option value="10">10 / page</option>
          <option value="20">20 / page</option>
          <option value="50">50 / page</option>
          <option value="0">All</option>
        </select>

        <div style={{ zIndex: 60, position: "relative" }}>
          <MelodyColumnToggle
            visible={colPrefs.visible}
            orderedIds={colPrefs.orderedIds}
            onChange={handleColVisChange}
            onReorder={handleColReorder}
          />
        </div>
      </div>
      </div>

      {error && (
        <div
          className="text-sm rounded-md p-3 mb-4 border"
          style={{
            backgroundColor: "var(--danger-bg)",
            borderColor: "var(--danger)",
            color: "var(--danger-text)",
          }}
        >
          {error}
        </div>
      )}

      {loading ? (
        <div className="text-center py-8" style={{ color: "var(--text-muted)" }}>Loading...</div>
      ) : displayRows.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 melodies found.
        </div>
      ) : (
        <div
          className="rounded-lg border max-w-full overflow-hidden"
          style={{
            backgroundColor: "var(--bg-card)",
            borderColor: "var(--border-primary)",
          }}
        >
          <div className="w-full max-w-full overflow-x-auto relative z-0">
            <table className="w-full min-w-full text-sm">
              <thead>
                <tr style={{ backgroundColor: "var(--bg-primary)", borderBottom: "1px solid var(--border-primary)" }}>
                  {activeColumns.map((col) => (
                    <th
                      key={col.key}
                      className={`px-4 py-3 text-left font-medium ${
                        col.key === "color" ? "w-8 px-2" : ""
                      }`}
                      style={{ color: "var(--text-muted)" }}
                    >
                      {col.key === "color" ? "" : (
                        <div className="inline-flex items-center gap-1">
                          <span>{col.label}</span>
                          {SORTABLE_COLUMN_TO_KEY[col.key] ? (
                            <button
                              type="button"
                              onClick={(e) => {
                                e.stopPropagation();
                                handleSortClick(col.key);
                              }}
                              className="text-xs leading-none px-1 rounded cursor-pointer"
                              style={{
                                color: sortBy === SORTABLE_COLUMN_TO_KEY[col.key] ? "var(--accent)" : "var(--text-muted)",
                                backgroundColor: sortBy === SORTABLE_COLUMN_TO_KEY[col.key] ? "rgba(116,184,22,0.14)" : "transparent",
                              }}
                              title={`Sort by ${col.label}`}
                            >
                              {getSortIndicator(col.key)}
                            </button>
                          ) : null}
                        </div>
                      )}
                    </th>
                  ))}
                  {canEdit && (
                    <th className="px-4 py-3 text-right font-medium w-48" style={{ color: "var(--text-muted)" }} />
                  )}
                </tr>
              </thead>
              <tbody>
                {pagedRows.map((row, index) => (
                  <tr
                    key={row.id}
                    onClick={() => navigate(`/melodies/${row.id}`)}
                    className="cursor-pointer transition-colors"
                    style={{ borderBottom: "1px solid var(--border-secondary)", backgroundColor: index % 2 === 1 ? "var(--bg-row-alt)" : "transparent" }}
                    onMouseEnter={(e) => (e.currentTarget.style.backgroundColor = "var(--bg-card-hover)")}
                    onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = index % 2 === 1 ? "var(--bg-row-alt)" : "transparent")}
                  >
                    {activeColumns.map((col) => (
                      <td
                        key={col.key}
                        className={`px-4 py-3 ${
                          col.key === "color" ? "w-8 px-2" : ""
                        }`}
                        style={{ color: "var(--text-primary)" }}
                      >
                        {renderCellValue(col.key, row)}
                      </td>
                    ))}
                    {canEdit && (
                      <td className="px-4 py-3">
                        <div className="flex gap-2 justify-end" onClick={(e) => e.stopPropagation()}>
                          {row.status === "draft" ? (
                            <button
                              onClick={() => handlePublish(row)}
                              disabled={actionLoading === row.id}
                              className="text-xs cursor-pointer disabled:opacity-50 px-2 py-1 rounded-full"
                              style={{ color: "#22c55e", backgroundColor: "rgba(34,197,94,0.15)" }}
                            >
                              {actionLoading === row.id ? "..." : "Publish"}
                            </button>
                          ) : (
                            <button
                              onClick={() => setUnpublishTarget(row)}
                              disabled={actionLoading === row.id}
                              className="text-xs cursor-pointer disabled:opacity-50 px-2 py-1 rounded-full"
                              style={{ color: "#ea580c", backgroundColor: "rgba(234,88,12,0.16)" }}
                            >
                              Unpublish
                            </button>
                          )}
                          <button
                            onClick={() => navigate(`/melodies/${row.id}/edit`)}
                            className="text-xs cursor-pointer px-2 py-1 rounded-full"
                            style={{ color: "var(--text-link)", backgroundColor: "rgba(88,156,250,0.14)" }}
                          >
                            Edit
                          </button>
                          <button
                            onClick={() => setDeleteTarget(row)}
                            className="text-xs cursor-pointer px-2 py-1 rounded-full"
                            style={{ color: "var(--danger)", backgroundColor: "rgba(243,75,75,0.14)" }}
                          >
                            Delete
                          </button>
                        </div>
                      </td>
                    )}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          {pageSize > 0 && totalPages > 1 && (
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "10px 16px", borderTop: "1px solid var(--border-primary)" }}>
              <span style={{ fontSize: 12, color: "var(--text-muted)" }}>
                Page {safePage} of {totalPages} — {displayRows.length} total
              </span>
              <div style={{ display: "flex", alignItems: "center", gap: 4 }}>
                {[
                  { label: "«", onClick: () => setPage(1), disabled: safePage === 1 },
                  { label: "‹", onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: safePage === 1 },
                ].map(({ label, onClick, disabled }) => (
                  <button key={label} onClick={onClick} disabled={disabled}
                    style={{ padding: "3px 8px", fontSize: 12, borderRadius: 4, cursor: disabled ? "default" : "pointer", opacity: disabled ? 0.4 : 1, backgroundColor: "var(--bg-card-hover)", color: "var(--text-secondary)", border: "none" }}>
                    {label}
                  </button>
                ))}
                {Array.from({ length: totalPages }, (_, i) => i + 1)
                  .filter((p) => p === 1 || p === totalPages || Math.abs(p - safePage) <= 2)
                  .reduce((acc, p, idx, arr) => {
                    if (idx > 0 && p - arr[idx - 1] > 1) acc.push("…");
                    acc.push(p);
                    return acc;
                  }, [])
                  .map((p, idx) =>
                    p === "…" ? (
                      <span key={`e-${idx}`} style={{ padding: "0 4px", fontSize: 12, color: "var(--text-muted)" }}>…</span>
                    ) : (
                      <button key={p} onClick={() => setPage(p)}
                        style={{ padding: "3px 8px", fontSize: 12, borderRadius: 4, cursor: "pointer", fontWeight: p === safePage ? 700 : 400, backgroundColor: p === safePage ? "var(--accent)" : "var(--bg-card-hover)", color: p === safePage ? "var(--bg-primary)" : "var(--text-secondary)", border: "none" }}>
                        {p}
                      </button>
                    )
                  )}
                {[
                  { label: "›", onClick: () => setPage((p) => Math.min(totalPages, p + 1)), disabled: safePage === totalPages },
                  { label: "»", onClick: () => setPage(totalPages), disabled: safePage === totalPages },
                ].map(({ label, onClick, disabled }) => (
                  <button key={label} onClick={onClick} disabled={disabled}
                    style={{ padding: "3px 8px", fontSize: 12, borderRadius: 4, cursor: disabled ? "default" : "pointer", opacity: disabled ? 0.4 : 1, backgroundColor: "var(--bg-card-hover)", color: "var(--text-secondary)", border: "none" }}>
                    {label}
                  </button>
                ))}
              </div>
            </div>
          )}
        </div>
      )}

      <ConfirmDialog
        open={!!deleteTarget}
        title="Delete Melody"
        message={`Are you sure you want to delete "${getDisplayName(deleteTarget?.information?.name)}"? This will also delete any uploaded files.`}
        onConfirm={handleDelete}
        onCancel={() => setDeleteTarget(null)}
      />

      <ConfirmDialog
        open={!!unpublishTarget}
        title="Unpublish Melody"
        message={`This melody may be in use by devices. Unpublishing will remove "${getDisplayName(unpublishTarget?.information?.name)}" from Firestore and devices will no longer have access. The melody will be kept as a draft. Continue?`}
        onConfirm={handleUnpublish}
        onCancel={() => setUnpublishTarget(null)}
      />

      <BinaryTableModal
        open={!!viewRow}
        melody={viewRow || null}
        builtMelody={viewRow ? (builtMap[viewRow.id] || null) : null}
        files={viewRow ? { binary_url: resolveEffectiveBinary(viewRow).url } : null}
        archetypeCsv={viewRow?.information?.archetype_csv || null}
        onClose={() => setViewRow(null)}
      />
    </div>
  );
}


