// Cluster command macros — a named button for a command you would otherwise
// retype.
//
// Twelve slots, fixed. A list you can grow needs add/remove/reorder controls and
// a decision about what an empty row means; twelve boxes you fill in need
// neither, and nobody has thirteen cluster commands they use daily. A slot with
// no command is simply not drawn, so the toolbar is as long as the operator made
// it and no longer.
//
// Stored through writeUiPref like every other portable preference, so the
// buttons travel with data/ rather than living in one browser profile.

import { writeUiPrefDebounced } from '@/lib/uiPref';

export type ClusterMacro = {
  label: string; // what the button says
  cmd: string;   // what is sent to the master server
};

export const CLUSTER_MACRO_COUNT = 12;
export const clusterMacrosKey = 'opslog.clusterMacros';

export const emptyClusterMacros = (): ClusterMacro[] =>
  Array.from({ length: CLUSTER_MACRO_COUNT }, () => ({ label: '', cmd: '' }));

// loadClusterMacros always returns exactly CLUSTER_MACRO_COUNT entries, whatever
// was stored: a saved list from a build with fewer slots must not leave the
// editor rendering undefined rows.
export function loadClusterMacros(): ClusterMacro[] {
  const out = emptyClusterMacros();
  try {
    const raw = localStorage.getItem(clusterMacrosKey);
    if (!raw) return out;
    const v = JSON.parse(raw);
    if (!Array.isArray(v)) return out;
    for (let i = 0; i < CLUSTER_MACRO_COUNT && i < v.length; i++) {
      out[i] = {
        label: String(v[i]?.label ?? '').slice(0, 24),
        cmd: String(v[i]?.cmd ?? '').slice(0, 120),
      };
    }
  } catch { /* a corrupt preference is not worth failing the panel over */ }
  return out;
}

// Debounced, because this is called on every keystroke in twenty-four text
// boxes. The local cache is written at once — it is what everything reads back
// — and only the database write waits for the typing to stop. A round trip into
// Go per character is what "the letters appear after I have moved on" was.
export function saveClusterMacros(macros: ClusterMacro[]): void {
  writeUiPrefDebounced(clusterMacrosKey, JSON.stringify(macros));
}

// visibleClusterMacros drops the slots that would send nothing. The COMMAND is
// what decides: a slot with a label and no command is a button that lies, and a
// command with no label still has something to show — its own text.
export function visibleClusterMacros(macros: ClusterMacro[]): ClusterMacro[] {
  return macros
    .filter((m) => m.cmd.trim() !== '')
    .map((m) => ({ label: m.label.trim() || m.cmd.trim(), cmd: m.cmd.trim() }));
}
