// ─────────────────────────────────────────────
// DO_Shared.jsx
// Shared constants, styles, and utility functions
// Used by all DO_Fields_*.jsx and DO_DetailView.jsx
// Load FIRST in index.html before other DO_ files
// ─────────────────────────────────────────────

// ── Inject global CSS (number spinner, etc.) ─
(function injectGlobalStyles() {
  if (document.getElementById('do-global-style')) return;
  const s = document.createElement('style');
  s.id = 'do-global-style';
  s.textContent = `
    input[type=number]::-webkit-inner-spin-button,
    input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
    input[type=number] { -moz-appearance: textfield; }
  `;
  document.head.appendChild(s);
})();

// ── Global config — set by server via index.html template ──
// window.API_BASE, APP_ID, APP_DATA, APP_DENSITY, APP_CONTAINER
// are injected by server.js renderApp() before these scripts load.
// DO NOT hardcode these values here.
const API_BASE = window.API_BASE || 'https://data.dataobjects.com';
const APP_ID   = window.APP_ID   || null;
const APP_DATA = window.APP_DATA || 'east';

// ── Colors ───────────────────────────────────
const BORDER = "#e2e8f0";
const BG     = "#f1f5f9";
const TEXT   = "#1e293b";
const LABEL  = "#64748b";
const CHECK  = "#2563a8";
const NAVY   = "#1a3a5c";

// ── Shared styles ────────────────────────────
const S = {
  page:       { fontFamily: "'Segoe UI', sans-serif", background: BG, minHeight: "100vh", fontSize: "15px" },
  nav:        { background: NAVY, color: "#fff", display: "flex", alignItems: "center", padding: "0 24px", height: 52, gap: 12, boxShadow: "0 2px 8px rgba(0,0,0,0.2)", position: "sticky", top: 0, zIndex: 100 },
  navLogo:    { fontSize: 20, fontWeight: 900, letterSpacing: "-0.5px" },
  navCenter:  { display: "flex", alignItems: "center", gap: 8, flex: 1 },
  navTable:   { fontSize: 14, fontWeight: 700 },
  navSep:     { color: "rgba(255,255,255,0.3)" },
  navRecord:  { fontSize: 13, color: "rgba(255,255,255,0.65)" },
  navRight:   { display: "flex", alignItems: "center", gap: 6 },
  navCounter: { fontSize: 11, color: "rgba(255,255,255,0.5)", padding: "0 8px" },
  navDivider: { color: "rgba(255,255,255,0.2)", margin: "0 4px" },
  navBtn:     { background: "rgba(255,255,255,0.1)", border: "1px solid rgba(255,255,255,0.15)", color: "#fff", padding: "5px 12px", borderRadius: 4, cursor: "pointer", fontSize: 12, fontFamily: "inherit" },
  newBadge:       { fontSize: 10, fontWeight: 700, background: "#22c55e", color: "#fff", padding: "2px 8px", borderRadius: 10, letterSpacing: "0.05em" },
  saveModeTag:    { fontSize: 10, color: "rgba(255,255,255,0.4)", padding: "2px 8px", border: "1px solid rgba(255,255,255,0.15)", borderRadius: 10 },
  saveBtn:        { background: "#f59e0b", border: "none", color: "#1a1a1a", padding: "6px 18px", borderRadius: 4, cursor: "pointer", fontSize: 13, fontWeight: 700, fontFamily: "inherit" },
  saveBtnSaved:   { background: "#22c55e", color: "#fff" },
  saveBtnSaving:  { background: "#94a3b8", cursor: "wait" },
  saveBtnDisabled:{ background: "#94a3b8", cursor: "default", opacity: 0.6 },
  newBar:     { background: "#dcfce7", color: "#166534", fontSize: 12, fontWeight: 600, padding: "6px 24px", borderBottom: "1px solid #bbf7d0" },
  dirtyBar:   { background: "#fef3c7", color: "#92400e", fontSize: 12, fontWeight: 600, padding: "6px 24px", borderBottom: "1px solid #fde68a" },
  errorBar:   { background: "#fee2e2", color: "#991b1b", fontSize: 12, fontWeight: 600, padding: "8px 24px", display: "flex", alignItems: "center", justifyContent: "space-between" },
  errorClose: { background: "none", border: "none", cursor: "pointer", color: "#991b1b", fontSize: 16 },
  container:  { margin: "20px auto" },
  row:        { display: "flex", background: "#fff", border: `1px solid ${BORDER}`, borderRadius: 6, marginBottom: 3 },
  col:        { boxSizing: "border-box", minWidth: 0 },
  emptyCol:   { color: "#cbd5e1", fontSize: 12 },
  fieldWrap:  { marginBottom: 14 },
  label:      { display: "block", fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.07em", color: LABEL, marginBottom: 4 },
  input:      { width: "100%", boxSizing: "border-box", border: `1px solid ${BORDER}`, borderRadius: 5, padding: "6px 10px", fontSize: "1em", color: TEXT, background: "#fafbfc", outline: "none", fontFamily: "inherit", transition: "border-color 0.15s", display: "block" },
  inputNumber:{ width: "100%", boxSizing: "border-box", border: `1px solid ${BORDER}`, borderRadius: 5, padding: "6px 10px", fontSize: "1em", color: TEXT, background: "#fafbfc", outline: "none", fontFamily: "inherit", transition: "border-color 0.15s", display: "block" },
  fieldHint:  { fontSize: 10, color: LABEL, marginTop: 3 },
  fieldAffix: { fontSize: 13, color: LABEL, whiteSpace: "nowrap" },
  optionGroup:{ display: "flex", gap: 10, flexWrap: "wrap", paddingTop: 4 },
  optionLabel:{ display: "flex", alignItems: "center", gap: 6, cursor: "pointer", userSelect: "none" },
  optionText: { fontSize: "0.93em", color: TEXT },
  customCheck:{ width: 16, height: 16, border: `2px solid ${BORDER}`, background: "#fafbfc", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, transition: "all 0.15s", cursor: "pointer" },
  customCheckOn:{ background: CHECK, borderColor: CHECK },
  checkMark:  { color: "#fff", fontSize: 11, fontWeight: 700, lineHeight: 1 },
  radioDot:   { width: 6, height: 6, borderRadius: "50%", background: "#fff", display: "block" },
  textObj:    { padding: "10px 14px", borderRadius: 5, fontSize: "1em", fontWeight: 600, marginBottom: 12 },
  unknown:    { fontSize: 11, color: "#f87171", fontStyle: "italic", padding: "4px 0" },
  meta:       { display: "flex", gap: 20, flexWrap: "wrap", marginTop: 12, padding: "8px 12px", background: "#fff", border: `1px solid ${BORDER}`, borderRadius: 6, fontSize: 11, color: LABEL },
  skeletonWrap: { display: "flex", flexDirection: "column", gap: 3 },
  skeletonRow:  { display: "flex", background: "#fff", border: `1px solid ${BORDER}`, borderRadius: 6 },
  skeletonCol:  { padding: "14px 16px", borderRight: `1px solid ${BORDER}` },
  skeletonLabel:{ height: 10, background: "#e2e8f0", borderRadius: 4, marginBottom: 8, width: "40%" },
  skeletonInput:{ height: 34, background: "#f1f5f9", borderRadius: 5 },
  searchPanel:{ background: "#fff", border: "1px solid #e2e8f0", borderRadius: 8, padding: "12px 16px", margin: "0 0 8px 0" },
  searchRow:  { display: "flex", gap: 8, alignItems: "center" },
  searchInput:{ flex: 1, border: "1px solid #e2e8f0", borderRadius: 5, padding: "6px 10px", fontSize: 13, fontFamily: "inherit", outline: "none" },
  searchBtn:  { background: "#1a3a5c", color: "#fff", border: "none", borderRadius: 5, padding: "6px 14px", cursor: "pointer", fontSize: 12, fontFamily: "inherit", whiteSpace: "nowrap" },
  searchNote: { fontSize: 11, color: "#94a3b8", marginTop: 8 },
  logPanel:   { marginTop: 16, background: "#0f172a", borderRadius: 8, overflow: "hidden" },
  logSummary: { color: "#64748b", fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.08em", padding: "10px 16px", cursor: "pointer" },
  logBody:    { padding: "0 16px 16px" },
  logEntry:   { color: "#7dd3fc", fontSize: 11, lineHeight: 1.8, borderBottom: "1px solid #1e293b", padding: "2px 0" },
};

// ── Density scale ────────────────────────────
const DENSITY_SCALE = {
  S:  { colPad: '8px 10px',  rowGap: 2,  labelSize: '9px',  inputPad: '4px 8px',  fieldGap: 8  },
  M:  { colPad: '14px 16px', rowGap: 3,  labelSize: '10px', inputPad: '6px 10px', fieldGap: 14 },
  L:  { colPad: '18px 20px', rowGap: 6,  labelSize: '11px', inputPad: '8px 12px', fieldGap: 18 },
  XL: { colPad: '24px 28px', rowGap: 10, labelSize: '12px', inputPad: '10px 14px',fieldGap: 24 },
};

// ── Responsive helpers ───────────────────────
function useWindowWidth() {
  const [width, setWidth] = React.useState(window.innerWidth);
  React.useEffect(() => {
    const handler = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
  }, []);
  return width;
}

function getBreakpoint(w) {
  if (w < 576)  return 'xs';
  if (w < 768)  return 'sm';
  if (w < 992)  return 'md';
  if (w < 1200) return 'lg';
  return 'xl';
}

function responsiveWidth(colWidth, bp) {
  const w = parseInt(colWidth) || 12;
  if (bp === 'xs') return '100%';
  if (bp === 'sm') return '100%';
  if (bp === 'md') {
    if (w >= 7) return '100%';
    if (w >= 4) return '50%';
    return '33.333%';
  }
  return `${Math.round((w / 12) * 100)}%`;
}

const toWidth = (w) => `${(parseInt(w) / 12) * 100}%`;

// ── Layout builders ──────────────────────────
function buildLayout(objects) {
  const rows = {};
  objects
    .filter(o => o.group === "layout" && o.type === "row")
    .forEach(o => {
      // Key by layout+row to avoid collision when multiple tabs share row numbers
      const key = `${o.layout || ''}__${parseInt(o.row)}`;
      rows[key] = { ...o, cols: {} };
    });
  objects
    .filter(o => o.group === "layout" && o.type === "col")
    .forEach(o => {
      const key = `${o.layout || ''}__${parseInt(o.row)}`;
      const c   = parseInt(o.col);
      if (rows[key]) rows[key].cols[c] = { ...o, objects: [] };
    });
  objects
    .filter(o => o.group === "object" && o.type !== "valuelist")
    .forEach(o => {
      const key = `${o.layout || ''}__${parseInt(o.row)}`;
      const c   = parseInt(o.col);
      if (rows[key]?.cols[c]) rows[key].cols[c].objects.push(o);
    });
  Object.values(rows).forEach(row =>
    Object.values(row.cols).forEach(col =>
      col.objects.sort((a, b) => parseInt(a.sort) - parseInt(b.sort))
    )
  );
  // Sort by row number within each layout, layouts sorted by layout id
  return Object.values(rows)
    .sort((a, b) => {
      const la = String(a.layout || ''), lb = String(b.layout || '');
      if (la !== lb) return la.localeCompare(lb);
      return parseInt(a.row) - parseInt(b.row);
    })
    .map(row => ({
      ...row,
      cols: Object.values(row.cols).sort((a, b) => parseInt(a.col) - parseInt(b.col))
    }));
}

function buildValueListMap(objects) {
  const map = {};
  objects
    .filter(o => o.group === "valuelist" || o.type === "valuelist")
    .forEach(o => {
      map[o.name]                       = o.options || [];
      map[(o.name || "").toLowerCase()] = o.options || [];
    });
  return map;
}

function buildAppSettings(objects) {
  const s = objects.find(o => o.group === "app" && o.type === "settings");
  return {
    save:    s?.save    || "manual",
    timeout: s?.timeout || 3000,
  };
}

function buildDefaultLayout(allObjects, table) {
  const fields = allObjects.filter(
    o => o.group === "schema" && o.type === "field" && o.table === table && o.status !== "0"
  );
  if (fields.length === 0) return null;
  fields.sort((a, b) => {
    const na = parseInt((a.field || "").replace(/\D/g,'')) || 0;
    const nb = parseInt((b.field || "").replace(/\D/g,'')) || 0;
    return na - nb;
  });
  return [{
    row: 1,
    cols: [{
      col: 1, width: "12",
      objects: fields.map((f, i) => ({
        id: f.id || f.field, group: "object", type: "input",
        field: f.field, label: f.name || f.label || f.field,
        labelpos: "top", labelalign: "left", sort: String(i + 1),
        table, height: "3",
      }))
    }]
  }];
}

function resolveOptions(obj, valueListMap) {
  let raw = [];
  if (obj.source === "local" && obj.valuelist) {
    raw = valueListMap[obj.valuelist]
       || valueListMap[(obj.valuelist || "").toLowerCase()]
       || [];
  } else {
    raw = obj.options || [];
  }
  return raw.map(o => {
    if (typeof o !== 'string') return o;
    const pipeIdx = o.indexOf(' | ');
    if (pipeIdx !== -1) return { label: o.slice(0, pipeIdx).trim(), value: o.slice(pipeIdx + 3).trim() };
    return { label: o, value: o };
  });
}

// ── Theme accessor ────────────────────────────
function getTheme() { return window.__DO_THEME__ || {}; }

// ── Shared field label component ─────────────
function FieldLabel({ obj }) {
  if (!obj.label) return null;
  const theme = getTheme();
  return (
    <label style={{
      ...S.label,
      fontSize:  theme.label_size  || '10px',
      color:     theme.label_color || '#64748b',
      textAlign: obj.labelalign    || 'left',
    }}>
      {obj.label}
    </label>
  );
}

// ─────────────────────────────────────────────
// Icon — inline SVG Lucide icons (no CDN needed)
// Usage: <Icon name="plus" size={15} />
// ─────────────────────────────────────────────
const ICON_PATHS = {
  'plus':          [<line key="h" x1="12" y1="5" x2="12" y2="19"/>,<line key="v" x1="5" y1="12" x2="19" y2="12"/>],
  'check':         [<polyline key="p" points="20 6 9 17 4 12"/>],
  'x':             [<line key="a" x1="18" y1="6" x2="6" y2="18"/>,<line key="b" x1="6" y1="6" x2="18" y2="18"/>],
  'trash-2':       [<polyline key="t" points="3 6 5 6 21 6"/>,<path key="b" d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>,<line key="l1" x1="10" y1="11" x2="10" y2="17"/>,<line key="l2" x1="14" y1="11" x2="14" y2="17"/>],
  'upload':        [<path key="p" d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>,<polyline key="q" points="17 8 12 3 7 8"/>,<line key="l" x1="12" y1="3" x2="12" y2="15"/>],
  'download':      [<path key="p" d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>,<polyline key="q" points="7 10 12 15 17 10"/>,<line key="l" x1="12" y1="15" x2="12" y2="3"/>],
  'edit-2':        [<path key="p" d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"/>],
  'search':        [<circle key="c" cx="11" cy="11" r="8"/>,<line key="l" x1="21" y1="21" x2="16.65" y2="16.65"/>],
  'eye':           [<path key="p" d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>,<circle key="c" cx="12" cy="12" r="3"/>],
  'eye-off':       [<path key="p" d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/>,<path key="q" d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/>,<line key="l" x1="1" y1="1" x2="23" y2="23"/>],
  'log-in':        [<path key="p" d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/>,<polyline key="q" points="10 17 15 12 10 7"/>,<line key="l" x1="15" y1="12" x2="3" y2="12"/>],
  'log-out':       [<path key="p" d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>,<polyline key="q" points="16 17 21 12 16 7"/>,<line key="l" x1="21" y1="12" x2="9" y2="12"/>],
  'file':          [<path key="p" d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>,<polyline key="q" points="13 2 13 9 20 9"/>],
  'image':         [<rect key="r" x="3" y="3" width="18" height="18" rx="2" ry="2"/>,<circle key="c" cx="8.5" cy="8.5" r="1.5"/>,<polyline key="p" points="21 15 16 10 5 21"/>],
  'save':          [<path key="p" d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>,<polyline key="q" points="17 21 17 13 7 13 7 21"/>,<polyline key="r" points="7 3 7 8 15 8"/>],
  'refresh-cw':    [<polyline key="p" points="23 4 23 10 17 10"/>,<polyline key="q" points="1 20 1 14 7 14"/>,<path key="r" d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>],
  'chevron-left':  [<polyline key="p" points="15 18 9 12 15 6"/>],
  'chevron-right': [<polyline key="p" points="9 18 15 12 9 6"/>],
  'grid':          [<rect key="a" x="3" y="3" width="7" height="7"/>,<rect key="b" x="14" y="3" width="7" height="7"/>,<rect key="c" x="14" y="14" width="7" height="7"/>,<rect key="d" x="3" y="14" width="7" height="7"/>],
  'list':          [<line key="a" x1="8" y1="6" x2="21" y2="6"/>,<line key="b" x1="8" y1="12" x2="21" y2="12"/>,<line key="c" x1="8" y1="18" x2="21" y2="18"/>,<line key="d" x1="3" y1="6" x2="3.01" y2="6"/>,<line key="e" x1="3" y1="12" x2="3.01" y2="12"/>,<line key="f" x1="3" y1="18" x2="3.01" y2="18"/>],
  'external-link': [<path key="p" d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>,<polyline key="q" points="15 3 21 3 21 9"/>,<line key="l" x1="10" y1="14" x2="21" y2="3"/>],
  'copy':          [<rect key="r" x="9" y="9" width="13" height="13" rx="2" ry="2"/>,<path key="p" d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>],
};

function Icon({ name, size=15 }) {
  const paths = ICON_PATHS[name];
  if (!paths) return <span style={{ fontSize:size*0.7 }}>•</span>;
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
         stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
         style={{ display:'block', flexShrink:0 }}>
      {paths}
    </svg>
  );
}

// ─────────────────────────────────────────────
// IconBtn — consistent icon button
// Matches file upload button exactly:
//   background:#fff  border:1.5px solid #cbd5e1
//   borderRadius:5   padding:4px 8px
//   color:#475569
//
// Props: icon, onClick, title, disabled, danger, active, size
// ─────────────────────────────────────────────
function IconBtn({ icon, onClick, title, disabled=false, danger=false, active=false, size=15 }) {
  return (
    <button onClick={onClick} disabled={disabled} title={title}
      style={{
        background:    '#fff',
        border:        `1.5px solid ${danger ? '#fecaca' : active ? '#bfdbfe' : '#cbd5e1'}`,
        borderRadius:  5,
        padding:       '4px 8px',
        cursor:        disabled ? 'default' : 'pointer',
        display:       'flex',
        alignItems:    'center',
        justifyContent:'center',
        color:         danger ? '#dc2626' : active ? '#2563eb' : '#475569',
        lineHeight:    1,
        opacity:       disabled ? 0.45 : 1,
        fontFamily:    'inherit',
        flexShrink:    0,
      }}>
      <Icon name={icon} size={size} />
    </button>
  );
}
