// MDM SORP — Airtable-style grid (multi-view + toolbar + group + record detail).
//
// Usage: <AirtableGrid entity="deals" views={...} columns={...} rows={...} navigate={...} />
//
// Implements: Grid / Kanban / Gallery / Calendar views, search, filter (multi-cond),
// sort (multi), group-by w/ collapse, hide fields, row height, color by field,
// expanded sidesheet w/ tabs + comments + workflow stepper, inline edit affordance,
// linked-record pills, AI-suggested empty cells, primary-field expand button.

const { useState, useMemo, useEffect, useRef, useCallback } = React;

/* ============================================================
   FIELD TYPE REGISTRY — how a cell of each type renders
   ============================================================ */
const Field = {
  text: ({ v, mono }) => v ? <span className={"v " + (mono ? "mono" : "")}>{v}</span> : <span className="cell-empty">—</span>,

  primary: ({ v, row, onExpand, idStyle }) => (
    <div className="cell-primary">
      <button className="expand" title="Открыть карточку (⏎)" onClick={(e) => { e.stopPropagation(); onExpand?.(row); }}>
        <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg>
      </button>
      <span className={"title " + (idStyle ? "mono" : "")}>{v}</span>
    </div>
  ),

  select: ({ v, options }) => {
    const opt = (options || []).find(o => o.value === v) || (v ? { value: v, label: v, color: "gray" } : null);
    return opt ? <span className={"cell-pill sel-" + (opt.color || "gray")}><span className="cell-pill-dot"/>{opt.label}</span> : <span className="cell-empty">—</span>;
  },

  status_workflow: ({ v, options }) => {
    const idx = (options || []).findIndex(o => o.value === v);
    return (
      <div className="cell-stepper">
        {(options || []).map((o, i) => (
          <span key={o.value} className={"step " + (i < idx ? "done" : i === idx ? "current" : "")} title={o.label}/>
        ))}
        <span className="label">{options?.[idx]?.label || v || "—"}</span>
      </div>
    );
  },

  multi_select: ({ v, options }) => {
    if (!v || !v.length) return <span className="cell-empty">—</span>;
    return (
      <span style={{ display: "inline-flex", gap: 3, alignItems: "center" }}>
        {v.map(x => {
          const o = (options || []).find(opt => opt.value === x) || { value: x, label: x, color: "gray" };
          return <span key={x} className={"cell-pill sel-" + (o.color || "gray")}>{o.label}</span>;
        })}
      </span>
    );
  },

  linked: ({ v, multiple }) => {
    if (!v || (Array.isArray(v) && !v.length)) {
      return <span className="cell-ai-suggest"><Icon name="sparkle" size={10}/> <span className="v">ИИ предложит</span></span>;
    }
    const arr = Array.isArray(v) ? v : [v];
    return (
      <span className="cell-linked">
        {arr.slice(0, 2).map((item, i) => (
          <a key={i} className="chip-link" onClick={(e) => e.stopPropagation()}>
            {item.flag && <Flag code={item.flag}/>}
            <span className="key">{item.id}</span>
            <span>{item.label}</span>
          </a>
        ))}
        {arr.length > 2 && <span className="more">+{arr.length - 2}</span>}
      </span>
    );
  },

  currency: ({ v, ccy = "USD" }) => v == null ? <span className="cell-empty">—</span> : (
    <span className="cell-currency">
      <span className="v">{fmtMoney(v, ccy).replace(/[^\d., ]/g, "").trim()}</span>
      <span className="ccy">{ccy}</span>
    </span>
  ),

  percent: ({ v }) => v == null ? <span className="cell-empty">—</span> : (
    <span style={{ fontVariantNumeric: "tabular-nums", fontWeight: 500 }}>{v}%</span>
  ),

  number: ({ v }) => v == null ? <span className="cell-empty">—</span> : (
    <span style={{ fontVariantNumeric: "tabular-nums", fontWeight: 500 }}>{v}</span>
  ),

  date: ({ v, refDate }) => {
    if (!v) return <span className="cell-empty">—</span>;
    const today = new Date(refDate || "2026-05-27");
    const d = new Date(v);
    const days = Math.round((d - today) / 86400000);
    const cls = days < 0 ? "overdue" : days <= 7 ? "soon" : "";
    const rel = days < 0 ? `просрочка ${-days}д` : days === 0 ? "сегодня" : days <= 14 ? `+${days}д` : null;
    return <span className={"cell-date " + cls}>{v}{rel && <span className="rel">{rel}</span>}</span>;
  },

  progress: ({ v }) => {
    const pct = Math.round((v?.done || 0) / Math.max(1, (v?.total || 1)) * 100);
    const tone = pct === 100 ? "" : pct > 60 ? "" : pct > 20 ? "warn" : "bad";
    return (
      <span className={"cell-progress " + tone}>
        <span className="bar"><i style={{ width: pct + "%" }}/></span>
        <span className="num">{v?.done}/{v?.total}</span>
      </span>
    );
  },

  systems: ({ v }) => (
    <span className="cell-systems">
      <span className={"sys amo " + (v?.amo ? "" : "missing")} title="AmoCRM">Am</span>
      <span className={"sys asana " + (v?.asana ? "" : "missing")} title="Asana">As</span>
      <span className={"sys drive " + (v?.drive ? "" : "missing")} title="Google Drive">Dr</span>
      <span className={"sys onec " + (v?.onec ? "" : "missing")} title="1С">1C</span>
    </span>
  ),

  users: ({ v }) => (
    <span style={{ display: "inline-flex", gap: -2, marginLeft: 0 }}>
      {(v || []).slice(0, 3).map((u, i) => (
        <span key={i} style={{ marginLeft: i ? -6 : 0, border: "2px solid white", borderRadius: 999 }}>
          <Avatar name={u} size={20}/>
        </span>
      ))}
    </span>
  ),

  flag: ({ v, sub }) => (
    <span className="cell-with-badge">
      <Flag code={v}/>
      <span className="v">{sub || v?.toUpperCase()}</span>
    </span>
  ),

  sanctions: ({ v }) => <SanctionsChip status={v}/>,

  badge: ({ v, raw }) => v ? <FieldBadge src={v} conf={0.9} raw={raw}/> : <span className="cell-empty">—</span>,
};

/* ============================================================
   AIRTABLE GRID — main container with all views & toolbar
   ============================================================ */
function AirtableGrid({ entity, defaultView = "grid", views, columns, rows, onRowOpen, primaryField, rowColorField, navigate }) {
  const [view, setView] = useState(defaultView);
  const [viewIdx, setViewIdx] = useState(0);

  // Each saved "view" has its own state (filter/sort/group/hidden/height/color)
  const [viewStates, setViewStates] = useState(() =>
    views.map(v => ({
      kind: v.kind || "grid",
      filter: v.filter || [],
      sort: v.sort || [],
      groupBy: v.groupBy || null,
      hidden: v.hidden || [],
      height: v.height || "medium",
      colorBy: v.colorBy || null,
      search: "",
    }))
  );

  const vs = viewStates[viewIdx] || {};
  const setVS = (patch) => setViewStates(s => s.map((x, i) => i === viewIdx ? { ...x, ...patch } : x));
  const activeView = views[viewIdx] || views[0];

  useEffect(() => { setView(viewStates[viewIdx]?.kind || "grid"); }, [viewIdx, viewStates]);

  // Apply filter, sort, search
  const processed = useMemo(() => {
    let out = rows;
    if (vs.search) {
      const q = vs.search.toLowerCase();
      out = out.filter(r =>
        Object.values(r).some(v => typeof v === "string" && v.toLowerCase().includes(q))
      );
    }
    for (const f of vs.filter || []) {
      out = out.filter(r => {
        const val = r[f.field];
        if (f.op === "is") return val === f.value;
        if (f.op === "not") return val !== f.value;
        if (f.op === "contains") return String(val || "").toLowerCase().includes(String(f.value).toLowerCase());
        if (f.op === "empty") return !val;
        if (f.op === "notempty") return !!val;
        if (f.op === "lt") return val < f.value;
        if (f.op === "gt") return val > f.value;
        return true;
      });
    }
    if (vs.sort?.length) {
      out = [...out].sort((a, b) => {
        for (const s of vs.sort) {
          const av = a[s.field], bv = b[s.field];
          const cmp = av == null ? 1 : bv == null ? -1 : av < bv ? -1 : av > bv ? 1 : 0;
          if (cmp !== 0) return s.dir === "desc" ? -cmp : cmp;
        }
        return 0;
      });
    }
    return out;
  }, [rows, vs.filter, vs.sort, vs.search]);

  const visibleColumns = columns.filter(c => !vs.hidden.includes(c.id));

  // Open record detail
  const [expanded, setExpanded] = useState(null);

  const filterCount = (vs.filter || []).length;
  const sortCount = (vs.sort || []).length;
  const hideCount = (vs.hidden || []).length;

  return (
    <div style={{ display: "flex", flexDirection: "column", flex: 1, minHeight: 0 }}>
      <AirtableViewTabs views={views} viewIdx={viewIdx} setViewIdx={setViewIdx}/>
      <AirtableToolbar
        vs={vs} setVS={setVS} columns={columns} entity={entity}
        filterCount={filterCount} sortCount={sortCount} hideCount={hideCount}
        view={view} groupBy={vs.groupBy} groupBysAvailable={columns.filter(c => c.type === "select" || c.type === "linked" || c.type === "users")}
      />

      {/* Active filters / sorts shown as chips */}
      {(filterCount > 0 || sortCount > 0 || vs.groupBy) && (
        <div className="at-active">
          {vs.filter.map((f, i) => {
            const col = columns.find(c => c.id === f.field);
            const opt = (col?.options || []).find(o => o.value === f.value);
            return (
              <span key={i} className="clause">
                <span className="conj">{i === 0 ? "где" : "и"}</span>
                <span className="field">{col?.label || f.field}</span>
                <span className="op">{f.op === "is" ? "=" : f.op === "not" ? "≠" : f.op === "contains" ? "содержит" : f.op === "empty" ? "пусто" : f.op === "notempty" ? "не пусто" : f.op}</span>
                {f.value != null && f.op !== "empty" && f.op !== "notempty" && <span className="val">{opt?.label || f.value}</span>}
                <span className="x" onClick={() => setVS({ filter: vs.filter.filter((_, j) => j !== i) })}>×</span>
              </span>
            );
          })}
          {vs.sort.map((s, i) => {
            const col = columns.find(c => c.id === s.field);
            return (
              <span key={"s" + i} className="clause">
                <span className="conj">{i === 0 && filterCount === 0 ? "сорт." : "потом"}</span>
                <span className="field">{col?.label}</span>
                <span className="op">{s.dir === "desc" ? "↓" : "↑"}</span>
                <span className="x" onClick={() => setVS({ sort: vs.sort.filter((_, j) => j !== i) })}>×</span>
              </span>
            );
          })}
          {vs.groupBy && (
            <span className="clause">
              <span className="conj">группа</span>
              <span className="field">{columns.find(c => c.id === vs.groupBy)?.label}</span>
              <span className="x" onClick={() => setVS({ groupBy: null })}>×</span>
            </span>
          )}
        </div>
      )}

      {/* Active view */}
      {view === "grid" && <GridView columns={visibleColumns} rows={processed} groupBy={vs.groupBy} height={vs.height} colorBy={vs.colorBy} onExpand={r => setExpanded(r)} primaryField={primaryField} navigate={navigate}/>}
      {view === "kanban" && <KanbanView rows={processed} stackBy={activeView.stackBy} stackOptions={columns.find(c => c.id === activeView.stackBy)?.options || []} onExpand={r => setExpanded(r)} primaryField={primaryField}/>}
      {view === "gallery" && <GalleryView rows={processed} onExpand={r => setExpanded(r)} columns={columns} primaryField={primaryField}/>}
      {view === "calendar" && <CalendarView rows={processed} dateField={activeView.dateField} columns={columns} onExpand={r => setExpanded(r)} primaryField={primaryField}/>}

      {/* Expanded record sheet */}
      <Drawer open={!!expanded} onClose={() => setExpanded(null)} width={760}>
        {expanded && <RecordSheet row={expanded} columns={columns} primaryField={primaryField} onClose={() => setExpanded(null)} onOpenFull={onRowOpen} navigate={navigate} entity={entity}/>}
      </Drawer>
    </div>
  );
}

/* ============================================================
   VIEW TABS
   ============================================================ */
function AirtableViewTabs({ views, viewIdx, setViewIdx }) {
  const ICONS = { grid: "table", kanban: "kanban", gallery: "gallery", calendar: "calendar", timeline: "timeline" };
  return (
    <div className="at-views">
      {views.map((v, i) => (
        <button key={i} className={"at-view-tab " + (viewIdx === i ? "active" : "")} onClick={() => setViewIdx(i)}>
          <Icon name={ICONS[v.kind] || "table"} size={13}/>
          <span>{v.name}</span>
          {v.count != null && <span className="badge">{v.count}</span>}
        </button>
      ))}
      <span className="spacer"/>
      <button className="new-view"><Icon name="plus" size={11}/> Новый view</button>
    </div>
  );
}

/* ============================================================
   TOOLBAR (Hide / Filter / Group / Sort / Color / Height / Search / Share)
   ============================================================ */
function AirtableToolbar({ vs, setVS, columns, view, filterCount, sortCount, hideCount, entity }) {
  const [open, setOpen] = useState(null); // 'hide' | 'filter' | 'group' | 'sort' | 'color' | 'height'
  const close = () => setOpen(null);

  return (
    <div className="at-toolbar">
      <ToolButton label="Поля" icon="eye" count={hideCount} active={open === "hide"} onClick={() => setOpen(o => o === "hide" ? null : "hide")}>
        {open === "hide" && <HideFieldsPopover columns={columns} hidden={vs.hidden} setHidden={h => setVS({ hidden: h })} close={close}/>}
      </ToolButton>

      <ToolButton label="Фильтр" icon="filter" count={filterCount} active={open === "filter"} onClick={() => setOpen(o => o === "filter" ? null : "filter")}>
        {open === "filter" && <FilterPopover columns={columns} filters={vs.filter} setFilters={f => setVS({ filter: f })} close={close}/>}
      </ToolButton>

      <ToolButton label="Группа" icon="group" count={vs.groupBy ? 1 : 0} active={open === "group"} onClick={() => setOpen(o => o === "group" ? null : "group")}>
        {open === "group" && <GroupPopover columns={columns} groupBy={vs.groupBy} setGroupBy={g => setVS({ groupBy: g })} close={close}/>}
      </ToolButton>

      <ToolButton label="Сорт." icon="sort" count={sortCount} active={open === "sort"} onClick={() => setOpen(o => o === "sort" ? null : "sort")}>
        {open === "sort" && <SortPopover columns={columns} sort={vs.sort} setSort={s => setVS({ sort: s })} close={close}/>}
      </ToolButton>

      <ToolButton label="Цвет" icon="palette" count={vs.colorBy ? 1 : 0} active={open === "color"} onClick={() => setOpen(o => o === "color" ? null : "color")}>
        {open === "color" && <ColorPopover columns={columns} colorBy={vs.colorBy} setColorBy={c => setVS({ colorBy: c })} close={close}/>}
      </ToolButton>

      <ToolButton label="Высота" icon="rows" active={open === "height"} onClick={() => setOpen(o => o === "height" ? null : "height")}>
        {open === "height" && <HeightPopover height={vs.height} setHeight={h => setVS({ height: h })} close={close}/>}
      </ToolButton>

      <div className="sep"/>

      <div className="at-search">
        <Icon name="search" size={13} className="ic"/>
        <input placeholder="Поиск во view" value={vs.search} onChange={(e) => setVS({ search: e.target.value })}/>
      </div>

      <span style={{ flex: 1 }}/>

      <button className="at-tool">
        <Icon name="sparkle" size={13} className="ic"/> ИИ-обогащение
      </button>
      <button className="at-tool">
        <Icon name="upload" size={13} className="ic"/> Импорт
      </button>
      <button className="at-tool">
        <Icon name="download" size={13} className="ic"/> Экспорт
      </button>
      <button className="at-tool">
        <Icon name="link" size={13} className="ic"/> Поделиться
      </button>
    </div>
  );
}

function ToolButton({ label, icon, count, active, onClick, children }) {
  return (
    <div style={{ position: "relative" }}>
      <button className={"at-tool " + ((count || active) ? "has-value" : "")} onClick={onClick}>
        <Icon name={icon} size={13} className="ic"/>
        <span>{label}</span>
        {count > 0 && <span className="count">{count}</span>}
      </button>
      {children}
    </div>
  );
}

/* ============================================================
   POPOVERS: hide / filter / sort / group / color / height
   ============================================================ */
function HideFieldsPopover({ columns, hidden, setHidden, close }) {
  return (
    <div className="at-popover" style={{ width: 280 }}>
      <h4>Поля во view ({columns.length - hidden.length}/{columns.length})</h4>
      {columns.map(c => {
        const on = !hidden.includes(c.id);
        return (
          <div key={c.id} className={"field-toggle " + (on ? "on" : "")} onClick={() => setHidden(on ? [...hidden, c.id] : hidden.filter(x => x !== c.id))}>
            <span className="sw"/>
            <span className="nm">{c.label}</span>
            <span className="ftype">{c.type}</span>
          </div>
        );
      })}
      <div style={{ display: "flex", gap: 6, marginTop: 8, paddingTop: 8, borderTop: "1px solid var(--hairline)" }}>
        <button className="btn ghost sm" onClick={() => setHidden([])}>Показать все</button>
        <button className="btn ghost sm" onClick={() => setHidden(columns.filter(c => !c.required).map(c => c.id))}>Скрыть все</button>
      </div>
    </div>
  );
}

function FilterPopover({ columns, filters, setFilters, close }) {
  const filterable = columns.filter(c => c.filterable !== false);
  return (
    <div className="at-popover" style={{ width: 460 }}>
      <h4>Показывать записи, где</h4>
      {filters.length === 0 && <div style={{ color: "var(--fg-3)", fontSize: 12, padding: "6px 0 10px" }}>Фильтров нет. Добавьте условие.</div>}
      {filters.map((f, i) => {
        const col = columns.find(c => c.id === f.field) || filterable[0];
        return (
          <div key={i} className="pop-row">
            <span style={{ width: 28, color: "var(--fg-3)", fontSize: 11 }}>{i === 0 ? "где" : "и"}</span>
            <select value={f.field} onChange={(e) => setFilters(filters.map((x, j) => j === i ? { ...x, field: e.target.value } : x))}>
              {filterable.map(c => <option key={c.id} value={c.id}>{c.label}</option>)}
            </select>
            <select value={f.op} onChange={(e) => setFilters(filters.map((x, j) => j === i ? { ...x, op: e.target.value } : x))}>
              <option value="is">равно</option>
              <option value="not">не равно</option>
              <option value="contains">содержит</option>
              <option value="empty">пусто</option>
              <option value="notempty">не пусто</option>
              <option value="lt">меньше</option>
              <option value="gt">больше</option>
            </select>
            {f.op !== "empty" && f.op !== "notempty" && (
              col.options ?
                <select value={f.value || ""} onChange={(e) => setFilters(filters.map((x, j) => j === i ? { ...x, value: e.target.value } : x))}>
                  <option value="">—</option>
                  {col.options.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
                </select> :
                <input value={f.value || ""} onChange={(e) => setFilters(filters.map((x, j) => j === i ? { ...x, value: e.target.value } : x))} placeholder="значение"/>
            )}
            <span style={{ flex: 1 }}/>
            <span className="x" onClick={() => setFilters(filters.filter((_, j) => j !== i))}>×</span>
          </div>
        );
      })}
      <button className="pop-add" onClick={() => setFilters([...filters, { field: filterable[0].id, op: "is", value: filterable[0].options?.[0]?.value || "" }])}>
        <Icon name="plus" size={11}/> Добавить условие
      </button>
    </div>
  );
}

function SortPopover({ columns, sort, setSort, close }) {
  const sortable = columns.filter(c => c.sortable !== false);
  return (
    <div className="at-popover" style={{ width: 360 }}>
      <h4>Сортировать по</h4>
      {sort.length === 0 && <div style={{ color: "var(--fg-3)", fontSize: 12, padding: "6px 0 10px" }}>Сортировки нет.</div>}
      {sort.map((s, i) => (
        <div key={i} className="pop-row">
          <select value={s.field} onChange={(e) => setSort(sort.map((x, j) => j === i ? { ...x, field: e.target.value } : x))}>
            {sortable.map(c => <option key={c.id} value={c.id}>{c.label}</option>)}
          </select>
          <select value={s.dir} onChange={(e) => setSort(sort.map((x, j) => j === i ? { ...x, dir: e.target.value } : x))}>
            <option value="asc">↑ возр.</option>
            <option value="desc">↓ убыв.</option>
          </select>
          <span className="x" onClick={() => setSort(sort.filter((_, j) => j !== i))}>×</span>
        </div>
      ))}
      <button className="pop-add" onClick={() => setSort([...sort, { field: sortable[0].id, dir: "asc" }])}>
        <Icon name="plus" size={11}/> Добавить уровень
      </button>
    </div>
  );
}

function GroupPopover({ columns, groupBy, setGroupBy, close }) {
  const groupable = columns.filter(c => ["select", "linked", "users"].includes(c.type));
  return (
    <div className="at-popover" style={{ width: 240 }}>
      <h4>Группировать по</h4>
      <div className={"height-row " + (!groupBy ? "active" : "")} onClick={() => { setGroupBy(null); close(); }}>
        <span>Без группировки</span>
      </div>
      {groupable.map(c => (
        <div key={c.id} className={"height-row " + (groupBy === c.id ? "active" : "")} onClick={() => { setGroupBy(c.id); close(); }}>
          <Icon name={c.type === "select" ? "list" : "parties"} size={12}/>
          <span>{c.label}</span>
        </div>
      ))}
    </div>
  );
}

function ColorPopover({ columns, colorBy, setColorBy, close }) {
  const colorable = columns.filter(c => c.type === "select" || c.type === "sanctions");
  return (
    <div className="at-popover" style={{ width: 240 }}>
      <h4>Цвет строк по</h4>
      <div className={"color-row " + (!colorBy ? "active" : "")} onClick={() => { setColorBy(null); close(); }}>Без цвета</div>
      {colorable.map(c => (
        <div key={c.id} className={"color-row " + (colorBy === c.id ? "active" : "")} onClick={() => { setColorBy(c.id); close(); }}>
          {c.label}
        </div>
      ))}
    </div>
  );
}

function HeightPopover({ height, setHeight, close }) {
  const opts = [
    { id: "short", l: "Низкая", lines: [2] },
    { id: "medium", l: "Средняя", lines: [2, 2] },
    { id: "tall", l: "Высокая", lines: [2, 2, 2] },
    { id: "xtall", l: "Очень высокая", lines: [2, 2, 2, 2] },
  ];
  return (
    <div className="at-popover" style={{ width: 200 }}>
      <h4>Высота строк</h4>
      {opts.map(o => (
        <div key={o.id} className={"height-row " + (height === o.id ? "active" : "")} onClick={() => { setHeight(o.id); close(); }}>
          <span className="preview">{o.lines.map((_, i) => <span key={i}/>)}</span>
          <span>{o.l}</span>
        </div>
      ))}
    </div>
  );
}

/* ============================================================
   GRID VIEW
   ============================================================ */
function GridView({ columns, rows, groupBy, height = "medium", colorBy, onExpand, primaryField, navigate }) {
  const [collapsed, setCollapsed] = useState({});
  const groups = useMemo(() => {
    if (!groupBy) return [{ key: "_all", rows }];
    const map = new Map();
    rows.forEach(r => {
      const k = (Array.isArray(r[groupBy]) ? r[groupBy][0]?.id || r[groupBy][0]?.label : r[groupBy]) || "—";
      if (!map.has(k)) map.set(k, []);
      map.get(k).push(r);
    });
    return [...map.entries()].map(([k, rs]) => ({ key: k, rows: rs }));
  }, [rows, groupBy]);

  const groupCol = columns.find(c => c.id === groupBy);
  const colorCol = columns.find(c => c.id === colorBy);

  return (
    <div className="grid-wrap">
      <table className={"dg row-" + height}>
        <thead>
          <tr>
            <th className="rownum"></th>
            {columns.map(c => (
              <th key={c.id} className={c.type === "number" || c.type === "currency" || c.type === "percent" ? "num" : ""} style={{ width: c.width }}>
                {c.label}
              </th>
            ))}
            <th style={{ width: 60 }}></th>
          </tr>
        </thead>
        <tbody>
          {groups.map(g => {
            const isCollapsed = collapsed[g.key];
            const opt = groupCol?.options?.find(o => o.value === g.key);
            const sumValue = g.rows.reduce((s, r) => s + (r.value_usd || r.value || 0), 0);
            return (
              <React.Fragment key={g.key}>
                {groupBy && (
                  <tr className={"group-row " + (isCollapsed ? "collapsed" : "")} onClick={() => setCollapsed(c => ({ ...c, [g.key]: !c[g.key] }))}>
                    <td colSpan={columns.length + 2}>
                      <div className="grow">
                        <span className="caret"><Icon name="chevron_down" size={12}/></span>
                        {opt && <span className="gbar" style={{ background: colorOf(opt.color) }}/>}
                        <span className="gtitle">{opt?.label || g.key}</span>
                        <span className="gcount">· {g.rows.length} {pluralize(g.rows.length)}</span>
                        <span className="gsummary">
                          {sumValue > 0 && <span>сумма: <strong>{fmtMoney(sumValue, "USD")}</strong></span>}
                        </span>
                      </div>
                    </td>
                  </tr>
                )}
                {!isCollapsed && g.rows.map((r, ri) => {
                  const rowColor = colorCol && colorCol.options ? colorOf(colorCol.options.find(o => o.value === r[colorBy])?.color) : "transparent";
                  return (
                    <tr key={r.id || ri} onClick={() => onExpand(r)} style={{ "--row-color": rowColor }}>
                      <td className="rownum">{ri + 1}</td>
                      {columns.map(c => {
                        const Cell = Field[c.type] || Field.text;
                        const v = c.accessor ? c.accessor(r) : r[c.id];
                        return (
                          <td key={c.id} className={(c.type === "number" || c.type === "currency" || c.type === "percent") ? "num" : ""} style={{ width: c.width }}>
                            {c.id === primaryField
                              ? <Cell v={v} row={r} options={c.options} onExpand={onExpand} idStyle={c.idStyle}/>
                              : <Cell v={v} row={r} options={c.options} ccy={c.ccy} multiple={c.multiple} mono={c.mono}/>}
                          </td>
                        );
                      })}
                      <td>
                        <span className="cell-actions">
                          <button className="btn-mini" title="Открыть карточку" onClick={(e) => { e.stopPropagation(); onExpand(r); }}><Icon name="arrow_up_right" size={11}/></button>
                          <button className="btn-mini" title="Обогатить ИИ"><Icon name="sparkle" size={11}/></button>
                          <button className="btn-mini" title="Найти дубли"><Icon name="merge" size={11}/></button>
                          <button className="btn-mini" title="Ещё"><Icon name="more" size={11}/></button>
                        </span>
                      </td>
                    </tr>
                  );
                })}
              </React.Fragment>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

function pluralize(n) {
  if (n % 10 === 1 && n % 100 !== 11) return "запись";
  if ([2,3,4].includes(n % 10) && ![12,13,14].includes(n % 100)) return "записи";
  return "записей";
}
function colorOf(c) {
  return ({ gray: "#7A7468", red: "#D63D4A", orange: "#C77A1F", yellow: "#D6B900", green: "#2F7D5B", teal: "#1F6862", blue: "#2C5C8C", purple: "#6E45E2", pink: "#C84571", ink: "#14171C" })[c] || "#7A7468";
}

/* ============================================================
   KANBAN VIEW
   ============================================================ */
function KanbanView({ rows, stackBy, stackOptions, onExpand, primaryField }) {
  const stacks = useMemo(() => {
    const map = new Map();
    rows.forEach(r => {
      const k = r[stackBy] || "_none";
      if (!map.has(k)) map.set(k, []);
      map.get(k).push(r);
    });
    return stackOptions.map(o => ({ ...o, rows: map.get(o.value) || [] })).concat(
      map.has("_none") ? [{ value: "_none", label: "—", color: "gray", rows: map.get("_none") }] : []
    );
  }, [rows, stackBy, stackOptions]);

  return (
    <div className="at-kanban">
      <div className="at-kanban-row">
        {stacks.map(s => (
          <div key={s.value} className="at-stack">
            <div className="at-stack-head">
              <span className="swatch" style={{ background: colorOf(s.color) }}/>
              <span className="name">{s.label}</span>
              <span className="count">{s.rows.length}</span>
              <span className="sum">{s.rows.reduce((sum, r) => sum + (r.value_usd || 0), 0) ? "$" + Math.round(s.rows.reduce((sum, r) => sum + (r.value_usd || 0), 0) / 1000) + "k" : ""}</span>
              <button className="add" title="Добавить запись"><Icon name="plus" size={12}/></button>
            </div>
            <div className="at-stack-cards">
              {s.rows.map(r => (
                <div key={r.id} className="at-card" style={{ "--card-color": colorOf(s.color) }} onClick={() => onExpand(r)}>
                  <span className="id">{r.id}</span>
                  <div className="ttl">{r[primaryField]}</div>
                  <div className="row num">
                    {r.value_usd && <span>${(r.value_usd / 1000).toFixed(0)}k</span>}
                    {r.margin_pct && <span style={{ color: "var(--fg-3)" }}>· {r.margin_pct}%</span>}
                  </div>
                  <div className="meta">
                    {r.customer && <Chip tone="outline">{r._customer?.short}</Chip>}
                    {r.sanctions && <SanctionsChip status={r.sanctions}/>}
                  </div>
                  {r.items_done != null && (
                    <>
                      <div className="row" style={{ color: "var(--fg-3)", fontSize: 11, marginTop: 8 }}>
                        <span>{r.items_done}/{r.items_count} позиций</span>
                        <span style={{ marginLeft: "auto" }}>{r.deadline}</span>
                      </div>
                      <div className="bar"><i style={{ width: (r.items_done / Math.max(1, r.items_count) * 100) + "%" }}/></div>
                    </>
                  )}
                </div>
              ))}
              <button style={{ background: "transparent", border: "1px dashed var(--hairline-strong)", borderRadius: 8, padding: "8px", color: "var(--fg-3)", cursor: "pointer", fontSize: 12 }}>
                + Добавить запись
              </button>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ============================================================
   GALLERY VIEW
   ============================================================ */
function GalleryView({ rows, columns, onExpand, primaryField }) {
  const stageCol = columns.find(c => c.id === "stage");
  return (
    <div className="at-gallery">
      {rows.map(r => {
        const opt = stageCol?.options?.find(o => o.value === r.stage);
        return (
          <div key={r.id} className="at-gallery-card" onClick={() => onExpand(r)}
               style={{ "--cover": `linear-gradient(135deg, ${colorOf(opt?.color || "gray")}22, ${colorOf(opt?.color || "gray")}10)` }}>
            <div className="cover">
              <span className="id">{r.id}</span>
            </div>
            <div className="body">
              <div className="ttl">{r[primaryField]}</div>
              <div className="meta">
                {opt && <span className={"cell-pill sel-" + opt.color}>{opt.label}</span>}
                {r.sanctions && <SanctionsChip status={r.sanctions}/>}
                {r._customer && <Chip tone="outline">{r._customer.short}</Chip>}
              </div>
              <div className="stats">
                <div><strong>${(r.value_usd / 1000).toFixed(0)}k</strong>сумма</div>
                <div><strong>{r.margin_pct}%</strong>маржа</div>
                <div><strong>{r.items_done}/{r.items_count}</strong>позиций</div>
                <div><strong>{r.deadline?.slice(5)}</strong>дедлайн</div>
              </div>
            </div>
          </div>
        );
      })}
    </div>
  );
}

/* ============================================================
   CALENDAR VIEW (month)
   ============================================================ */
function CalendarView({ rows, dateField, columns, onExpand }) {
  // Start at month with most events
  const [refMonth, setRefMonth] = useState(() => {
    const counts = new Map();
    rows.forEach(r => {
      const dt = r[dateField];
      if (dt) {
        const k = dt.slice(0, 7);
        counts.set(k, (counts.get(k) || 0) + 1);
      }
    });
    const top = [...counts.entries()].sort((a, b) => b[1] - a[1])[0];
    return top ? top[0] : "2026-06";
  });

  const [yearStr, monthStr] = refMonth.split("-");
  const year = +yearStr, month = +monthStr - 1;
  const firstDay = new Date(year, month, 1).getDay();
  const startOffset = (firstDay + 6) % 7;
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const cells = [];
  for (let i = 0; i < startOffset; i++) cells.push(null);
  for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(year, month, d));
  while (cells.length % 7) cells.push(null);

  const eventsByDate = useMemo(() => {
    const m = new Map();
    rows.forEach(r => {
      const dt = r[dateField];
      if (!dt) return;
      if (!m.has(dt)) m.set(dt, []);
      m.get(dt).push(r);
    });
    return m;
  }, [rows, dateField]);

  const stageCol = columns.find(c => c.id === "stage");
  const today = new Date("2026-05-27"); today.setHours(0,0,0,0);
  const monthEvents = rows.filter(r => r[dateField]?.startsWith(refMonth));
  const monthName = new Date(year, month).toLocaleString("ru-RU", { month: "long", year: "numeric" });

  const shift = (n) => {
    const d = new Date(year, month + n);
    setRefMonth(d.toISOString().slice(0, 7));
  };

  return (
    <div style={{ flex: 1, minHeight: 0, overflow: "auto", background: "var(--bg-surface)" }}>
      <div style={{ padding: "14px 20px", borderBottom: "1px solid var(--hairline)", display: "flex", alignItems: "center", gap: 12 }}>
        <h3 style={{ margin: 0, fontSize: 15, fontWeight: 600, textTransform: "capitalize" }}>{monthName}</h3>
        <span className="muted" style={{ fontSize: 12 }}>· {monthEvents.length} дедлайнов</span>
        <span style={{ flex: 1 }}/>
        <button className="at-tool" onClick={() => shift(-1)}><Icon name="chevron_right" size={12} style={{ transform: "rotate(180deg)" }}/></button>
        <button className="at-tool" onClick={() => setRefMonth("2026-05")}>Сегодня</button>
        <button className="at-tool" onClick={() => shift(1)}><Icon name="chevron_right" size={12}/></button>
      </div>
      <div className="at-cal">
        {["Пн","Вт","Ср","Чт","Пт","Сб","Вс"].map(d => <div key={d} className="at-cal-h">{d}</div>)}
        {cells.map((d, i) => {
          if (!d) return <div key={i} className="at-cal-d muted"/>;
          const k = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")}`;
          const evts = eventsByDate.get(k) || [];
          const isToday = d.getTime() === today.getTime();
          return (
            <div key={i} className={"at-cal-d " + (isToday ? "today" : "")}>
              <div className="dn">{d.getDate()}</div>
              {evts.map(e => {
                const opt = stageCol?.options?.find(o => o.value === e.stage);
                const c = colorOf(opt?.color);
                return (
                  <span key={e.id} className="at-cal-evt"
                        style={{ "--evt-bg": c + "22", "--evt-fg": c }}
                        onClick={() => onExpand(e)}>
                    {e.id} · {(e.title || "").slice(0, 24)}
                  </span>
                );
              })}
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ============================================================
   RECORD SHEET (expanded detail with tabs + comments + stepper)
   ============================================================ */
function RecordSheet({ row, columns, primaryField, onClose, onOpenFull, navigate, entity }) {
  const [tab, setTab] = useState("fields");
  const stageCol = columns.find(c => c.id === "stage");
  const stageOpt = stageCol?.options?.find(o => o.value === row.stage);
  const groups = [
    { id: "ident", title: "Идентификация", fields: columns.filter(c => ["primary", "name", "title", "id"].some(k => c.id.includes(k))) },
    { id: "rel", title: "Связи", fields: columns.filter(c => c.type === "linked") },
    { id: "money", title: "Финансы", fields: columns.filter(c => ["currency", "percent"].includes(c.type)) },
    { id: "sched", title: "Сроки и состав", fields: columns.filter(c => ["date", "progress", "status_workflow"].includes(c.type)) },
    { id: "meta", title: "Метаданные", fields: columns.filter(c => ["sanctions", "systems", "users", "select", "multi_select"].includes(c.type)) },
  ].map(g => ({ ...g, fields: g.fields.filter(f => f) })).filter(g => g.fields.length);

  return (
    <div className="at-sheet" style={{ display: "flex", flexDirection: "column", height: "100%" }}>
      <div className="drawer-head" style={{ position: "relative", paddingBottom: 8 }}>
        <div style={{ display: "flex", alignItems: "flex-start", gap: 12 }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 11, color: "var(--fg-3)", display: "flex", gap: 8, alignItems: "center", marginBottom: 6 }}>
              <span className="mono" style={{ color: "var(--sorp-red)", fontWeight: 700 }}>{row.id}</span>
              <span>·</span>
              <span>обновлено {row.updated || "2 мин назад"}</span>
              <span>·</span>
              <button className="btn ghost sm" style={{ height: 22, padding: "0 6px" }}><Icon name="copy" size={11}/> Копировать ID</button>
            </div>
            <div className="title" style={{ fontSize: 20, fontWeight: 600, lineHeight: 1.25, letterSpacing: "-0.01em" }}>{row[primaryField]}</div>
            <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: 10 }}>
              {stageOpt && <span className={"cell-pill sel-" + stageOpt.color} style={{ fontSize: 12, padding: "3px 11px" }}>{stageOpt.label}</span>}
              {row.sanctions && <SanctionsChip status={row.sanctions}/>}
              {row._customer && <Chip tone="outline"><Flag code={row._customer.country}/> {row._customer.short}</Chip>}
              {row.value_usd && <Chip tone="outline">${(row.value_usd / 1000).toFixed(0)}k · маржа {row.margin_pct}%</Chip>}
              {row.deadline && <Chip tone="outline">дедлайн {row.deadline}</Chip>}
            </div>
          </div>
          <button className="btn ghost sm" onClick={onClose}><Icon name="x" size={13}/></button>
        </div>

        {/* Workflow stepper */}
        {stageCol && stageCol.options && (
          <div className="at-sheet-stepper">
            {stageCol.options.map((o, i) => {
              const curIdx = stageCol.options.findIndex(opt => opt.value === row.stage);
              return (
                <div key={o.value} className={"step " + (i < curIdx ? "done" : i === curIdx ? "current" : "")}>
                  {i < curIdx && <Icon name="check" size={11} style={{ marginRight: 4, verticalAlign: -1 }}/>}
                  {o.label}
                </div>
              );
            })}
          </div>
        )}
      </div>

      <div className="sheet-actions">
        <button className="btn primary sm"><Icon name="check" size={12}/> Подтвердить этап</button>
        <button className="btn sm"><Icon name="sparkle" size={12}/> Обогатить ИИ</button>
        <button className="btn sm"><Icon name="merge" size={12}/> Найти дубли</button>
        <button className="btn sm"><Icon name="shield" size={12}/> Скрининг</button>
        <button className="btn sm"><Icon name="file" size={12}/> Сгенерировать КП</button>
        <span style={{ flex: 1 }}/>
        <button className="btn ghost sm" onClick={() => { onOpenFull?.(row); onClose(); }}>
          Полная карточка <Icon name="arrow_up_right" size={11}/>
        </button>
      </div>

      <div className="sheet-tabs">
        {[
          { id: "fields", l: "Поля", n: columns.length },
          { id: "comments", l: "Комментарии", n: 3 },
          { id: "activity", l: "Активность", n: 42 },
          { id: "linked", l: "Связи", n: 12 },
        ].map(t => (
          <button key={t.id} className={"t " + (tab === t.id ? "active" : "")} onClick={() => setTab(t.id)}>
            {t.l} <span className="count">{t.n}</span>
          </button>
        ))}
      </div>

      <div style={{ flex: 1, minHeight: 0, overflow: "auto", padding: "0 0 24px" }}>
        {tab === "fields" && (
          <div className="sheet-fields">
            {groups.map(g => (
              <div key={g.id}>
                <div className="group-title">{g.title}</div>
                {g.fields.map(c => {
                  const Cell = Field[c.type] || Field.text;
                  const v = c.accessor ? c.accessor(row) : row[c.id];
                  return (
                    <div key={c.id} className="frow">
                      <div className="lbl">{c.label}</div>
                      <div className="val">
                        <Cell v={v} row={row} options={c.options} ccy={c.ccy} multiple={c.multiple}/>
                      </div>
                      <div className="actions">
                        <button className="icon-mini"><Icon name="pencil" size={11}/></button>
                        <button className="icon-mini"><Icon name="history" size={11}/></button>
                      </div>
                    </div>
                  );
                })}
              </div>
            ))}
          </div>
        )}
        {tab === "comments" && (
          <div className="sheet-fields">
            {[
              { who: "Сергей К.", when: "вчера 16:42", text: "Заказчик попросил перенести дедлайн на +2 недели — записал в Asana." },
              { who: "Анна Л.", when: "2 дня назад", text: "@Денис П. посмотри маржу по поз. 03 — Schaffler по новой цене 14.6%, низковато." },
              { who: "ИИ Claude", when: "3 дня назад", text: "Найден возможный дубль: p_2003 «Schaffler M.E.» совпадает по TRN с p_2002. Открыть ревью." },
            ].map((c, i) => (
              <div key={i} className="at-comment">
                <Avatar name={c.who} size={28}/>
                <div className="body">
                  <span className="who">{c.who}</span><span className="when">{c.when}</span>
                  <div className="text">{c.text.replace(/@(\S+)/g, (m, n) => `__M__${n}__M__`).split(/__M__/).map((p, i) => i % 2 === 1 ? <span key={i} className="mention">@{p}</span> : p)}</div>
                </div>
              </div>
            ))}
            <div style={{ marginTop: 16, padding: "10px 12px", border: "1px solid var(--hairline-strong)", borderRadius: 8, display: "flex", gap: 8, alignItems: "center" }}>
              <Avatar name="Е. Соколова" size={24}/>
              <input placeholder="Напишите комментарий или @упомяните коллегу…" style={{ flex: 1, border: 0, outline: "none", fontSize: 13, background: "transparent", fontFamily: "var(--font-sans)" }}/>
              <button className="btn primary sm">Отправить</button>
            </div>
          </div>
        )}
        {tab === "activity" && (
          <div className="sheet-fields">
            {[
              { who: "Е. Соколова", what: "stage", from: "Поиск поставщиков", to: "Спецификация", when: "14:08", src: "manual" },
              { who: "ИИ Claude", what: "items[03].supplier", from: "—", to: "Schaeffler Middle East FZE", when: "12:18", src: "ai", conf: 0.94 },
              { who: "Service", what: "amo_id", from: "—", to: "47821", when: "03:14", src: "amo" },
              { who: "Д. Петров", what: "deadline", from: "2026-07-04", to: "2026-07-18", when: "вчера 16:42", src: "manual" },
            ].map((e, i) => (
              <div key={i} style={{ display: "flex", gap: 10, padding: "10px 0", borderBottom: "1px solid var(--hairline)", alignItems: "center", fontSize: 12.5 }}>
                <Avatar name={e.who} size={22}/>
                <div style={{ minWidth: 140 }}>{e.who}</div>
                <span className="mono muted" style={{ minWidth: 120 }}>{e.what}</span>
                <span className="muted" style={{ minWidth: 100, textDecoration: "line-through" }}>{e.from}</span>
                <Icon name="arrow_right" size={11} className="muted"/>
                <strong style={{ flex: 1 }}>{e.to}</strong>
                <FieldBadge src={e.src} conf={e.conf || 1}/>
                <span className="muted" style={{ fontSize: 11 }}>{e.when}</span>
              </div>
            ))}
          </div>
        )}
        {tab === "linked" && (
          <div className="sheet-fields">
            <div className="group-title">Связанные записи · 12</div>
            {[
              { type: "Документы", n: 8, hint: "8 файлов в Drive" },
              { type: "Контактные лица", n: 4, hint: "Mohammed Al-Fardan, Priya Ramesh, ещё 2" },
              { type: "Платежи", n: 3, hint: "104k USD на Caspian, 2 ожидают" },
              { type: "Задачи Asana", n: 8, hint: "5 готовы, 3 в работе" },
              { type: "Поставщики", n: 3, hint: "SKF · Schaeffler · Wuxi" },
            ].map((l, i) => (
              <div key={i} className="frow">
                <div className="lbl">{l.type}</div>
                <div className="val">
                  <strong>{l.n}</strong>
                  <span className="muted">— {l.hint}</span>
                </div>
                <div className="actions"><button className="icon-mini"><Icon name="arrow_up_right" size={11}/></button></div>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

/* ============================================================
   Add missing icons used here
   ============================================================ */
const _additionalIcons = {};
window.AirtableGrid = AirtableGrid;
window.AirtableField = Field;
