// BotConfig management: table list + form detail view (CRUD).
// Fetches configs with populated references. Form loads dropdown data in parallel.

const { useState, useEffect, useRef } = React;

const srcObj = (c) => (c.source_group_id && typeof c.source_group_id === "object") ? c.source_group_id : null;
const srcName = (c) => srcObj(c)?.name || "";
const srcId = (c) => srcObj(c)?.platform_group_id || (typeof c.source_group_id === "string" || typeof c.source_group_id === "number" ? String(c.source_group_id) : "");
const webhookName = (c) => (c.webhook_bot_id && typeof c.webhook_bot_id === "object") ? (c.webhook_bot_id.name || "") : "";
const workerNames = (c) => (c.available_bots || []).map((b) => (b && typeof b === "object" ? (b.name || "") : "")).join(" ");
const targetNames = (c) => (c.target_groups || []).map((t) => (t && typeof t === "object" ? (t.name || "") : "")).join(" ");
const configLabel = (c) => c.description || `Config (${srcName(c) || srcId(c) || "—"})`;

const targetIds = (c) => (c.target_groups || []).map((t) => (
  t && typeof t === "object" ? String(t.platform_group_id || t.telegram_id || "") : ""
)).join(" ");

const CONFIG_SEARCH_FIELDS = [
  (c) => c.description,
  srcName,
  srcId,
  webhookName,
  workerNames,
  targetNames,
  targetIds,
  (c) => (c.active ? "active" : "disabled"),
];

const CONFIG_SORT_GETTERS = {
  description: (c) => configLabel(c),
  source: (c) => srcName(c),
  webhook: (c) => webhookName(c),
  workers: (c) => (c.available_bots || []).length,
  targets: (c) => (c.target_groups || []).length,
  status: (c) => (c.active ? 1 : 0),
  updated_at: (c) => modifiedAtMs(c),
};

function repId(value) {
  if (value && typeof value === "object") return String(value._id || "");
  return value == null ? "" : String(value);
}

function hydrateRepresentatives(config) {
  return (config?.target_representatives || []).map((block) => ({
    target_group_id: repId(block.target_group_id),
    bindings: (block.bindings || []).map((row) => ({
      source_user_id: String(row.source_user_id || ""),
      source_label: String(row.source_label || ""),
      worker_bot_id: repId(row.worker_bot_id),
    })),
  })).filter((block) => block.target_group_id);
}

function pruneRepresentatives(reps, targetIds, workerIds) {
  const targets = new Set((targetIds || []).map(String));
  const workers = new Set((workerIds || []).map(String));
  let droppedWorkers = 0;
  const next = [];
  for (const block of reps || []) {
    if (!targets.has(String(block.target_group_id))) continue;
    const bindings = [];
    for (const row of block.bindings || []) {
      if (row.worker_bot_id && !workers.has(String(row.worker_bot_id))) {
        droppedWorkers += 1;
        continue;
      }
      bindings.push(row);
    }
    if (bindings.length) next.push({ ...block, bindings });
  }
  return { next, droppedWorkers };
}

function representativesPayload(reps, targetIds) {
  const allowed = new Set((targetIds || []).map(String));
  return (reps || [])
    .filter((block) => allowed.has(String(block.target_group_id)) && (block.bindings || []).length)
    .map((block) => ({
      target_group_id: String(block.target_group_id),
      bindings: block.bindings.map((row) => {
        const binding = {
          source_user_id: String(row.source_user_id || "").trim(),
          worker_bot_id: String(row.worker_bot_id || ""),
        };
        const label = String(row.source_label || "").trim();
        if (label) binding.source_label = label;
        return binding;
      }),
    }));
}

function validateRepresentatives(reps) {
  const tele = /^[0-9]{1,20}$/;
  for (const block of reps || []) {
    const seen = new Set();
    if ((block.bindings || []).length > 5) return "At most 5 representatives per target.";
    for (const row of block.bindings || []) {
      const source = String(row.source_user_id || "").trim();
      const worker = String(row.worker_bot_id || "");
      if (!source || !worker) return "Each representative row needs a numeric teleID and a worker.";
      if (!tele.test(source)) return "Source teleID must be a numeric user id, not a username or group id.";
      if (seen.has(source)) return `Duplicate teleID ${source} on the same target.`;
      seen.add(source);
    }
  }
  return "";
}

function representativeCount(reps) {
  return (reps || []).reduce((sum, block) => sum + ((block.bindings || []).length), 0);
}

const RepresentativeNickRow = ({ row, sourceGroupId, poolBots, onPatch, onRemove }) => {
  const [looking, setLooking] = useState(false);
  const timer = useRef(null);
  const requestId = useRef(0);

  useEffect(() => () => clearTimeout(timer.current), []);

  const lookup = (teleId, force) => {
    const id = String(teleId || "").trim();
    if (!/^[0-9]{1,20}$/.test(id)) return;
    clearTimeout(timer.current);
    timer.current = setTimeout(async () => {
      const ticket = ++requestId.current;
      setLooking(true);
      try {
        const data = await BotConfigApi.resolveSourceNick({
          tele_id: id,
          source_group_id: sourceGroupId || "",
        });
        if (ticket !== requestId.current) return;
        const label = data && data.found ? String(data.label || "") : "";
        if (!label) return;
        onPatch((current) => {
          if (String(current.source_user_id || "").trim() !== id) return {};
          const keepManual = current.source_label && current.source_label !== current.source_label_auto;
          if (keepManual) return { source_label_auto: label };
          return { source_label: label, source_label_auto: label };
        });
      } catch (e) {
        // Manual name stays. A miss is not an error toast.
      } finally {
        if (ticket === requestId.current) setLooking(false);
      }
    }, force ? 0 : 400);
  };

  useEffect(() => {
    if (row.source_user_id && !row.source_label) lookup(row.source_user_id, false);
  }, []);

  return (
    <div style={{ display: "flex", gap: 8, alignItems: "center", marginTop: 8 }}>
      <input
        className="input mono"
        style={{ width: 140, flex: "0 0 140px" }}
        value={row.source_user_id}
        placeholder="teleID"
        inputMode="numeric"
        onChange={(e) => {
          const nextId = e.target.value;
          const idChanged = nextId.trim() !== String(row.source_user_id || "").trim();
          onPatch({
            source_user_id: nextId,
            ...(idChanged ? { source_label: "", source_label_auto: "" } : {}),
          });
          lookup(nextId, false);
        }}
        onBlur={() => lookup(row.source_user_id, true)}
      />
      <input
        className="input"
        style={{ width: 160, flex: "0 0 160px" }}
        value={row.source_label || ""}
        placeholder={looking ? "Đang tìm…" : "Tên / @username"}
        onChange={(e) => onPatch({ source_label: e.target.value, source_label_auto: "" })}
      />
      <div style={{ flex: 1, minWidth: 0 }}>
        <SearchableSelect
          items={poolBots}
          value={row.worker_bot_id}
          onChange={(id) => onPatch({ worker_bot_id: id })}
          placeholder="Chọn worker"
          searchPlaceholder="Tìm worker…"
          getSub={(b) => b.phone}
          getLabel={(b) => (isErrorStatusBot(b) ? `${b.name || "Bot"} · Error` : (b.name || ""))}
          searchFields={["name", "phone", "status"]}
          emptyText="Chưa có worker trong pool."
          noneLabel="Chọn worker"
        />
      </div>
      <button type="button" className="btn ghost sm" aria-label="Xóa người đại diện" onClick={onRemove}>
        <I.Trash />
      </button>
    </div>
  );
};

const RepresentativeTarget = ({ targetId, targetName, sourceGroupId, rows, poolBots, onChange }) => {
  const list = rows || [];
  const setRow = (index, patch) => {
    onChange((latest) => (latest || []).map((row, i) => {
      if (i !== index) return row;
      const nextPatch = typeof patch === "function" ? patch(row) : patch;
      return { ...row, ...nextPatch };
    }));
  };
  return (
    <div style={{ marginTop: 14 }}>
      <div className="field-label">{targetName}</div>
      {list.length > 0 && (
        <div className="muted" style={{ display: "flex", gap: 8, marginTop: 8, fontSize: 11 }}>
          <span style={{ width: 140, flex: "0 0 140px" }}>teleID</span>
          <span style={{ width: 160, flex: "0 0 160px" }}>Tên</span>
          <span>Worker</span>
        </div>
      )}
      {list.map((row, index) => (
        <RepresentativeNickRow
          key={`${targetId}-${index}`}
          row={row}
          sourceGroupId={sourceGroupId}
          poolBots={poolBots}
          onPatch={(patch) => setRow(index, patch)}
          onRemove={() => onChange((latest) => (latest || []).filter((_, i) => i !== index))}
        />
      ))}
      <button
        type="button"
        className="btn ghost sm"
        style={{ marginTop: 8 }}
        disabled={list.length >= 5}
        title={list.length >= 5 ? "Tối đa 5 nick mỗi nhóm" : "Thêm người đại diện"}
        onClick={() => onChange([...list, { source_user_id: "", source_label: "", worker_bot_id: "" }])}
      >
        +
      </button>
    </div>
  );
};

// ─── Config form (create/edit) ───────────────────────────────────────────
const ConfigForm = ({ config, onBack, onDeleted, stats }) => {
  const toast = useToast();
  const confirm = useConfirm();

  // Load dropdown data in parallel
  const { data: allBots } = useApi(BotApi.list, []);
  const { data: allSources } = useApi(SourceGroupApi.list, []);
  const { data: allTargets } = useApi(TargetGroupApi.list, []);

  const [form, setForm] = useState({
    _id: config?._id,
    source_group_id: config?.source_group_id?._id || config?.source_group_id || "",
    webhook_bot_id: config?.webhook_bot_id?._id || config?.webhook_bot_id || "",
    available_bots: (config?.available_bots || []).map((b) => b._id || b),
    target_groups: (config?.target_groups || []).map((t) => t._id || t),
    description: config?.description || "",
    active: config?.active !== false,
    target_representatives: hydrateRepresentatives(config),
  });
  const [saving, setSaving] = useState(false);
  const [receipt, setReceipt] = useState(null);
  const isNew = !form._id;

  const set = (k, v) => setForm((f) => ({ ...f, [k]: v }));

  const handleSave = async () => {
    if (!form.source_group_id) { toast.error("Source group is required"); return; }
    if (!form.webhook_bot_id) { toast.error("Webhook bot is required"); return; }
    if (form.available_bots.length === 0) { toast.error("At least 1 worker bot is required"); return; }
    if (form.target_groups.length === 0) { toast.error("At least 1 target group is required"); return; }
    const reps = representativesPayload(form.target_representatives, form.target_groups);
    const repError = validateRepresentatives(reps);
    if (repError) { toast.error(repError); return; }
    const blockedBots = errorStatusBotNames(bots, [form.webhook_bot_id, ...form.available_bots]);
    if (blockedBots.length) {
      toast.error(`Error bots cannot be selected: ${blockedBots.join(", ")}. Remove them first.`);
      return;
    }

    // Check system limit for create operations
    if (isNew && !checkSystemLimit(stats?.limits, 'configs', toast)) {
      return;
    }

    setSaving(true);
    try {
      const result = await BotConfigApi.save({ ...form, target_representatives: reps });
      setForm((current) => ({ ...current, _id: result.data?._id || current._id }));
      setReceipt(result);
      toast.info?.('Đã lưu. Kiểm tra trạng thái áp dụng bên dưới.');
    } catch (e) {
      toast.error(e.message);
    } finally {
      setSaving(false);
    }
  };

  const handleDelete = async () => {
    const ok = await confirm("Delete config", "Delete this config? This cannot be undone.");
    if (!ok) return;
    try {
      const result = await BotConfigApi.delete(form._id);
      toast.info('Đã xóa cấu hình. Đang kiểm tra runner ngừng định tuyến.');
      onDeleted(result, form._id);
    } catch (e) {
      toast.error(e.message);
    }
  };

  const bots = allBots || [];
  const sources = allSources || [];
  const targets = allTargets || [];

  return (
    <div className="page">
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12, fontSize: 12 }}>
        <button className="btn ghost sm" onClick={onBack}><I.ChevronLeft /> Configs</button>
        {!isNew && <><span className="muted">/</span><span className="mono muted">{form._id}</span></>}
      </div>

      <div className="page-head sticky-title">
        <div>
          <h1 className="page-title">{isNew ? "New Config" : (form.description || "Edit Config")}</h1>
          <div className="page-sub">Configure source → webhook → workers → targets routing</div>
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          {!isNew && <button className="btn danger" onClick={handleDelete}><I.Trash /> Delete</button>}
          <button className="btn primary" onClick={handleSave} disabled={saving}>
            {saving ? "Saving…" : "Save"}
          </button>
        </div>
      </div>

      <ApplyStatusPanel type="config" resourceId={form._id} receipt={receipt} />
      <div className="split">
        {/* Left column */}
        <div className="stack">
          <Card title="Description">
            <div className="field">
              <label className="field-label">Config name / description</label>
              <input className="input" value={form.description} onChange={(e) => set("description", e.target.value)} placeholder="e.g. Trading VN → 5 targets" />
            </div>
            <div className="field">
              <label className="field-label" style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                <span>Active</span>
                <input type="checkbox" checked={form.active} onChange={(e) => set("active", e.target.checked)} />
              </label>
            </div>
          </Card>

          <Card title="Source group" subtitle="Where messages come from">
            <div className="field">
              <label className="field-label">Source group</label>
              <SearchableSelect
                items={sources}
                value={form.source_group_id}
                onChange={(id) => set("source_group_id", id)}
                placeholder="— Select source group —"
                searchPlaceholder="Search name or Telegram ID…"
                getSub={(s) => s.platform_group_id}
                searchFields={["name", "platform_group_id", "telegram_id", "platform"]}
                emptyText="No source groups. Add a source first."
                noneLabel="— Select source group —"
              />
            </div>
          </Card>

          <Card title="Webhook bot" subtitle="Receives source events and dispatches to workers">
            <div className="field">
              <label className="field-label">Webhook bot</label>
              <SearchableSelect
                items={botsForConfigSelect(bots, form.webhook_bot_id)}
                value={form.webhook_bot_id}
                onChange={(id) => set("webhook_bot_id", id)}
                placeholder="— Select webhook bot —"
                searchPlaceholder="Search webhook bots…"
                getSub={(b) => b.phone}
                getLabel={(b) => (isErrorStatusBot(b) ? `${b.name || "Bot"} · Error` : (b.name || ""))}
                searchFields={["name", "phone", "status"]}
                emptyText="No bots available. Add bots first."
                noneLabel="— Select webhook bot —"
              />
              <div className="field-help">Receives source events. Independent of worker bots — webhook A + worker B is allowed. Error bots are hidden.</div>
              {errorStatusBotNames(bots, form.webhook_bot_id).length > 0 && (
                <div className="field-help" style={{ color: "var(--red-fg)" }}>
                  {errorStatusBotNames(bots, form.webhook_bot_id).join(", ")} is in error and cannot stay selected.
                </div>
              )}
            </div>
          </Card>

          <Card title="Worker bots" subtitle="Pool of sender bots (load balanced)">
            <SearchableSelect
              multiple
              items={botsForConfigSelect(bots, form.available_bots)}
              value={form.available_bots}
              onChange={(ids) => {
                const pruned = pruneRepresentatives(form.target_representatives, form.target_groups, ids);
                if (pruned.droppedWorkers > 0) {
                  const n = pruned.droppedWorkers;
                  toast.info(`Removed ${n} representative row${n === 1 ? "" : "s"} because that worker left the pool.`);
                }
                setForm((f) => ({
                  ...f,
                  available_bots: ids,
                  target_representatives: pruneRepresentatives(f.target_representatives, f.target_groups, ids).next,
                }));
              }}
              placeholder="Select worker bots"
              searchPlaceholder="Search worker bots…"
              getSub={(b) => b.phone}
              getLabel={(b) => (isErrorStatusBot(b) ? `${b.name || "Bot"} · Error` : (b.name || ""))}
              searchFields={["name", "phone", "status"]}
              emptyText="No bots available. Add bots first."
            />
            <div className="field-help">Error bots are hidden and cannot be selected.</div>
            {errorStatusBotNames(bots, form.available_bots).length > 0 && (
              <div className="field-help" style={{ color: "var(--red-fg)" }}>
                Remove error bots before save: {errorStatusBotNames(bots, form.available_bots).join(", ")}.
              </div>
            )}
          </Card>

          <Card title="Target groups" subtitle="Where forwarded messages land">
            <SearchableSelect
              multiple
              items={targets}
              value={form.target_groups}
              onChange={(ids) => {
                setForm((f) => ({
                  ...f,
                  target_groups: ids,
                  target_representatives: pruneRepresentatives(f.target_representatives, ids, f.available_bots).next,
                }));
              }}
              placeholder="Select target groups"
              searchPlaceholder="Search name or Telegram ID…"
              getSub={(t) => t.platform_group_id}
              searchFields={["name", "platform_group_id", "telegram_id", "platform"]}
              emptyText="No target groups. Add target groups first."
            />
          </Card>

          {form.target_groups.length > 0 && (
            <Card title="Người đại diện" subtitle="Ghim nick nguồn vào một worker cho từng nhóm đích">
              <div className="field-help">teleID là số. Tên tự điền nếu hệ thống đã thấy nick này, không thấy thì nhập tay — vẫn sửa được. Tin copy tới nhóm đích này luôn đi qua worker đã chọn. Giữ nguồn không đổi. Worker không gửi được thì bỏ qua nhóm đích đó.</div>
              {form.target_groups.map((targetId) => {
                const target = targets.find((t) => String(t._id) === String(targetId));
                const block = (form.target_representatives || []).find((item) => String(item.target_group_id) === String(targetId));
                const poolBots = form.available_bots
                  .map((id) => bots.find((b) => String(b._id) === String(id)))
                  .filter(Boolean);
                return (
                  <RepresentativeTarget
                    key={String(targetId)}
                    targetId={targetId}
                    sourceGroupId={form.source_group_id}
                    targetName={target?.name || String(targetId)}
                    rows={block?.bindings || []}
                    poolBots={poolBots}
                    onChange={(update) => {
                      setForm((f) => {
                        const current = ((f.target_representatives || []).find((item) => String(item.target_group_id) === String(targetId)) || {}).bindings || [];
                        const bindings = typeof update === "function" ? update(current) : update;
                        const rest = (f.target_representatives || []).filter((item) => String(item.target_group_id) !== String(targetId));
                        const next = bindings.length
                          ? [...rest, { target_group_id: String(targetId), bindings }]
                          : rest;
                        return { ...f, target_representatives: next };
                      });
                    }}
                  />
                );
              })}
            </Card>
          )}
        </div>

        {/* Right column — summary */}
        <div className="stack">
          <Card title="Summary">
            <div style={{ display: "flex", flexDirection: "column", gap: 8, fontSize: 13 }}>
              <div style={{ display: "flex", justifyContent: "space-between" }}>
                <span className="muted">Source</span>
                <span>{sources.find((s) => String(s._id) === String(form.source_group_id))?.name || "—"}</span>
              </div>
              <div style={{ display: "flex", justifyContent: "space-between" }}>
                <span className="muted">Webhook bot</span>
                <span>{bots.find((b) => String(b._id) === String(form.webhook_bot_id))?.name || "—"}</span>
              </div>
              <div style={{ display: "flex", justifyContent: "space-between" }}>
                <span className="muted">Workers</span>
                <span className="tnum">{form.available_bots.length}</span>
              </div>
              <div style={{ display: "flex", justifyContent: "space-between" }}>
                <span className="muted">Targets</span>
                <span className="tnum">{form.target_groups.length}</span>
              </div>
              <div style={{ display: "flex", justifyContent: "space-between" }}>
                <span className="muted">Representatives</span>
                <span className="tnum">{representativeCount(form.target_representatives)}</span>
              </div>
              <div style={{ display: "flex", justifyContent: "space-between" }}>
                <span className="muted">Status</span>
                <StatusBadge status={form.active ? "active" : "disabled"} />
              </div>
            </div>
          </Card>
        </div>
      </div>
    </div>
  );
};

// ─── Config list screen ──────────────────────────────────────────────────
const ConfigsList = ({ onOpen, onNew, deletedReceipt, atLimit = false, listQuery = "", onQuery }) => {
  const { data: configs, loading, error, refetch } = useApi(BotConfigApi.list, []);
  const [statusFilter, setStatusFilter] = useState("all");
  const { sortKey, sortDir, toggleSort } = useListSort("updated_at", "desc");

  const filtered = (configs || []).filter((c) =>
    (statusFilter === "all" || (statusFilter === "active" ? c.active : !c.active)) &&
    matchQuery(c, listQuery, CONFIG_SEARCH_FIELDS)
  );
  const visible = sortItems(filtered, { key: sortKey, dir: sortDir, getters: CONFIG_SORT_GETTERS });

  if (error) return (
    <div className="page">
      <Empty icon={I.AlertTriangle} title="Failed to load configs" sub={error} action={<button className="btn" onClick={refetch}><I.Refresh /> Retry</button>} />
    </div>
  );

  return (
    <div className="page">
      <div className="page-head sticky-title">
        <div>
          <h1 className="page-title">Bot Configs</h1>
          <div className="page-sub">
            {loading ? "Loading…" : `${visible.length} configs · source → webhook → workers → targets.`}
          </div>
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          <button className="btn" onClick={refetch}><I.Refresh /> Refresh</button>
          <button className="btn primary" onClick={onNew} disabled={atLimit} title={atLimit ? 'Config system limit reached' : undefined}><I.Plus /> New config</button>
        </div>
      </div>

      {deletedReceipt && <ApplyStatusPanel type="config" resourceId={deletedReceipt.id} receipt={deletedReceipt.result} deleted />}
      <div className="toolbar">
        <div className="chip-group">
          {[
            { id: "all", label: "All" },
            { id: "active", label: "Active" },
            { id: "disabled", label: "Disabled" },
          ].map((s) => (
            <button key={s.id} className={`chip ${statusFilter === s.id ? "active" : ""}`} onClick={() => setStatusFilter(s.id)}>
              {s.label}
            </button>
          ))}
        </div>
        <ListSearch value={listQuery} onChange={onQuery} placeholder="Search configs…" />
      </div>

      <div className="card flush">
        <div style={{ overflow: "auto" }}>
          <table className="table">
            <thead>
              <tr>
                <SortableTh label="Description" column="description" sortKey={sortKey} sortDir={sortDir} onToggle={toggleSort} />
                <SortableTh label="Source" column="source" sortKey={sortKey} sortDir={sortDir} onToggle={toggleSort} />
                <SortableTh label="Webhook" column="webhook" sortKey={sortKey} sortDir={sortDir} onToggle={toggleSort} />
                <SortableTh label="Workers" column="workers" className="num" align="right" sortKey={sortKey} sortDir={sortDir} onToggle={toggleSort} />
                <SortableTh label="Targets" column="targets" className="num" align="right" sortKey={sortKey} sortDir={sortDir} onToggle={toggleSort} />
                <SortableTh label="Status" column="status" sortKey={sortKey} sortDir={sortDir} onToggle={toggleSort} />
                <SortableTh label="Modified Date" column="updated_at" sortKey={sortKey} sortDir={sortDir} onToggle={toggleSort} />
              </tr>
            </thead>
            <tbody>
              {loading ? (
                <SkeletonRows cols={7} rows={5} />
              ) : (configs || []).length === 0 ? (
                <tr>
                  <td colSpan={7}>
                    <Empty icon={I.Workflow} title="No configs yet" sub="Create a config to start routing messages." action={<button className="btn primary" onClick={onNew} disabled={atLimit} title={atLimit ? 'Config system limit reached' : undefined}><I.Plus /> New config</button>} />
                  </td>
                </tr>
              ) : visible.length === 0 ? (
                <tr>
                  <td colSpan={7}>
                    <Empty icon={I.Search} title="No configs match" sub="Clear search or filters to see all configs." />
                  </td>
                </tr>
              ) : (
                visible.map((c) => (
                  <tr key={c._id} style={{ cursor: "pointer" }} onClick={() => onOpen(c._id)}>
                    <td className="clip" style={{ fontWeight: 500 }}>{configLabel(c)}</td>
                    <td>
                      <div>{srcName(c) || "—"}</div>
                      <div className="mono muted" style={{ fontSize: 11 }}>{srcId(c) || "—"}</div>
                    </td>
                    <td>{webhookName(c) || "—"}</td>
                    <td className="num tnum">{(c.available_bots || []).length}</td>
                    <td className="num tnum">{(c.target_groups || []).length}</td>
                    <td><StatusBadge status={c.active ? "active" : "disabled"} /></td>
                    <td className="muted tnum">{formatModifiedDate(c)}</td>
                  </tr>
                ))
              )}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
};

// ─── Configs router (list ↔ detail) ─────────────────────────────────────
const ConfigsScreen = ({ listQuery = "", onQuery, stats }) => {
  const [view, setView] = useState("list"); // "list" | "new" | configId
  const [deletedReceipt, setDeletedReceipt] = useState(null);
  const onDeleted = (result, id) => { setDeletedReceipt({ result, id }); setView("list"); };
  const { data: config, loading } = useApi(
    () => (view !== "list" && view !== "new" ? BotConfigApi.get(view) : Promise.resolve(null)),
    [view]
  );

  if (view === "list") {
    return <ConfigsList onOpen={(id) => setView(id)} onNew={() => setView("new")} deletedReceipt={deletedReceipt} atLimit={isSystemAtLimit(stats?.limits, 'configs')} listQuery={listQuery} onQuery={onQuery} />;
  }
  if (view === "new") {
    return <ConfigForm config={null} onDeleted={onDeleted} onBack={() => setView("list")} stats={stats} />;
  }
  if (loading) return <div className="page"><div className="muted" style={{ paddingTop: 40, textAlign: "center" }}>Loading config…</div></div>;
  return <ConfigForm config={config} onDeleted={onDeleted} onBack={() => setView("list")} stats={stats} />;
};

window.ConfigsScreen = ConfigsScreen;
