// Subscriptions screen — supports 4 view modes:
//   'tabs'    → split into Domains/Servers tabs (flat tables)
//   'all'     → grouped by client, both kinds
//   'domains' → grouped by client, domains only
//   'servers' → grouped by client, servers only
//
// Sort + status filters apply consistently across all four.
// Default sort: expired → newest.

const FIELD_LABELS = {
  name: 'Domain name', registrar: 'Registrar', start: 'Start date', expiry: 'Expiry date',
  autoRenew: 'Auto-renew', billedCost: 'Billed to client', purchasedCost: 'Purchased cost', clientId: 'Client',
  serverId: 'Hosted on', role: 'Role', groupId: 'Group',
  label: 'Server label', provider: 'Provider', ip: 'IP address', panelDomain: 'Panel domain',
  os: 'OS', cpu: 'CPU (vCPU)', ram: 'RAM (GB)', disk: 'Disk (GB)', region: 'Region',
  type: 'Server type',
};

const renderFieldValue = (entity, field, value, clients, servers) => {
  if (value == null || value === '') return '\u2014';
  if (field === 'clientId') {
    const c = clients.find(c => c.id === value);
    return c ? c.name : value;
  }
  if (field === 'serverId') {
    const s = (servers || []).find(s => s.id === value);
    if (!s) return '\u2014';
    const sc = clients.find(c => c.id === s.clientId);
    return `${s.label}${sc ? ` · ${sc.name}` : ''} · ${s.type === 'shared' ? 'Shared' : 'Individual'}`;
  }
  if (field === 'autoRenew') return value ? 'On' : 'Off';
  if (field === 'role') return value === 'frontend' ? 'Frontend' : value === 'backend' ? 'Backend' : '—';
  if (field === 'type') return value === 'shared' ? 'Shared' : 'Individual';
  if (field === 'billedCost' || field === 'purchasedCost') return window.fmtINR(value);
  if (field === 'expiry' || field === 'start') return window.fmtDate(value);
  if (field === 'cpu') return `${value} vCPU`;
  if (field === 'ram') return `${value} GB`;
  if (field === 'disk') return `${value} GB`;
  return String(value);
};

function withPending(entity, kind, approvals) {
  const pending = approvals.find(a => a.kind === 'edit' && a.entity === kind && a.entityId === entity.id);
  return { entity, pending, effective: { ...entity, ...(pending?.changes || {}) } };
}

function sortRows(rows, sort, clients) {
  const dir = sort.dir === 'asc' ? 1 : -1;
  return [...rows].sort((A, B) => {
    const a = A.effective, b = B.effective;
    if (sort.field === 'expiryStatus') {
      const sa = window.expiryStatus(a.expiry);
      const sb = window.expiryStatus(b.expiry);
      if (sa.sort !== sb.sort) return (sa.sort - sb.sort) * dir;
      return (sa.n - sb.n) * dir;
    }
    let va = a[sort.field], vb = b[sort.field];
    if (sort.field === 'clientId') {
      va = (clients.find(c => c.id === va) || {}).name || '';
      vb = (clients.find(c => c.id === vb) || {}).name || '';
    }
    if (typeof va === 'number' && typeof vb === 'number') return (va - vb) * dir;
    return String(va || '').localeCompare(String(vb || '')) * dir;
  });
}

function filterRows(rows, query, statusFilter, clientFilter, providerFilter, kind) {
  const q = query.trim().toLowerCase();
  return rows.filter(r => {
    const e = r.effective;
    if (q) {
      const blob = (kind === 'domain'
        ? [e.name, e.registrar]
        : [e.label, e.provider, e.ip, e.os]).join(' ').toLowerCase();
      if (!blob.includes(q)) return false;
    }
    if (statusFilter !== 'all') {
      const s = window.expiryStatus(e.expiry).tone;
      const map = { expired: 'red', soon: 'amber', active: 'green' };
      if (s !== map[statusFilter]) return false;
    }
    if (clientFilter && e.clientId !== clientFilter) return false;
    if (providerFilter) {
      const v = kind === 'domain' ? e.registrar : e.provider;
      if (v !== providerFilter) return false;
    }
    return true;
  });
}

// ── Reusable row renderers (used by both tabs view and grouped view) ──
const DomainRow = ({ row, clients, servers, onEdit, onView, onRenew, onDelete, role, showClient = true, toast, onReload }) => {
  const { entity, pending, effective } = row;
  const client = clients.find(c => c.id === effective.clientId);
  const server = (servers || []).find(s => s.id === effective.serverId);
  const serverClient = server && clients.find(c => c.id === server.clientId);
  const crossClient = server && serverClient && serverClient.id !== effective.clientId;
  const [running, setRunning] = React.useState(false);
  const [notifying, setNotifying] = React.useState(false);
  const _toast = toast || (() => {});
  const _reload = onReload || (async () => {});

  const runHealth = async () => {
    setRunning(true);
    try {
      const r = await api.healthChecks.run('domain', entity.id);
      const ms = r && r.responseTimeMs != null ? ` · ${r.responseTimeMs}ms` : '';
      _toast(`Status: ${r?.status || 'unknown'}${ms}`, r?.status === 'down' ? 'danger' : undefined);
      await _reload();
    } catch (err) {
      _toast(err.message || 'Check failed', 'danger');
    } finally {
      setRunning(false);
    }
  };
  const notifyClient = async () => {
    if (!client) return;
    if (!client.email) { _toast('Set client email first', 'danger'); return; }
    if (!window.confirm(`Send expiry notification email to ${client.name} (${client.email})?`)) return;
    setNotifying(true);
    try {
      const r = await api.clients.notifyExpiry(client.id, { items: [{ kind: 'domain', id: entity.id }] });
      _toast(`Notification sent (${r?.sent ?? r?.count ?? 1})`);
    } catch (err) {
      _toast(err.message || 'Notify failed', 'danger');
    } finally {
      setNotifying(false);
    }
  };

  return (
    <tr className={pending ? 'has-pending' : ''}>
      <td>
        <div className="cell-primary" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <Icon name="globe" size={14} stroke={2} />
          {effective.healthCheckEnabled && (
            <button
              type="button"
              onClick={runHealth}
              disabled={running}
              title={running ? 'Checking…' : (effective.lastStatus === 'up' ? `Up · click to re-check (${window.fmtRelative(effective.lastCheckedAt)})` : effective.lastStatus === 'down' ? `DOWN · click to re-check (${window.fmtRelative(effective.lastCheckedAt)})` : 'Not checked yet · click to check')}
              style={{ display: 'inline-block', width: 12, height: 12, padding: 0, border: 'none', borderRadius: '50%', cursor: running ? 'wait' : 'pointer', background: running ? '#9ca3af' : effective.lastStatus === 'up' ? '#16a34a' : effective.lastStatus === 'down' ? '#dc2626' : '#9ca3af', opacity: running ? 0.6 : 1, flexShrink: 0 }}
            />
          )}
          {effective.name}
          {effective.role === 'frontend' && <span className="badge blue" style={{ fontSize: 9, padding: '1px 5px' }}>FE</span>}
          {effective.role === 'backend'  && <span className="badge" style={{ fontSize: 9, padding: '1px 5px', background: 'var(--purple, #7c3aed)', color: '#fff', border: 'none' }}>BE</span>}
          {pending && <span className="badge amber" style={{ fontSize: 10 }}>Pending</span>}
        </div>
        <div className="cell-sub">
          {effective.registrar}
          {effective.groupId && <span style={{ color: 'var(--text-muted)' }}> · {effective.groupId}</span>}
        </div>
      </td>
      {showClient && <td>{client?.name || '—'}</td>}
      <td>
        {server ? (
          <>
            <div className="cell-primary" style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
              <Icon name="server" size={12} stroke={2} style={{ color: 'var(--text-muted)' }}/>
              {server.label}
              {server.type === 'shared' && <span className="badge" style={{ fontSize: 10 }}>Shared</span>}
            </div>
            <div className="cell-sub">
              {crossClient ? <span style={{ color: 'var(--amber-text)' }}>via {serverClient.name}</span> : <span className="mono">{server.ip}</span>}
            </div>
          </>
        ) : (
          <span style={{ color: 'var(--text-muted)', fontSize: 12 }}>External</span>
        )}
      </td>
      <td className="mono">{window.fmtDate(effective.start)}</td>
      <td>
        <div className="cell-primary mono">{window.fmtDate(effective.expiry)}</div>
        <div className="cell-sub"><StatusBadge iso={effective.expiry} /></div>
      </td>
      <td>{effective.autoRenew ? <span className="badge green dot">On</span> : <span className="badge">Off</span>}</td>
      <td className="mono">{window.fmtINR(effective.billedCost)}</td>
      <td className="mono" style={{ color: 'var(--text-secondary)' }}>{window.fmtINR(effective.purchasedCost)}</td>
      <td title={effective.updatedBy ? `by ${effective.updatedBy}` : ''}>
        <div className="mono" style={{ fontSize: 12 }}>{window.fmtRelative(effective.updatedAt)}</div>
        {effective.updatedBy && <div className="cell-sub">by {effective.updatedBy}</div>}
      </td>
      <td>
        <div className="row-actions">
          <button className="btn sm ghost" onClick={() => onView(entity)}><Icon name="eye" size={13}/></button>
          <button className="btn sm ghost" onClick={() => onEdit(entity)}><Icon name="edit" size={13}/></button>
          {role === 'admin' && <button className="btn sm ghost" onClick={() => onRenew(entity)} title="Mark renewed"><Icon name="refresh" size={13}/></button>}
          {role === 'admin' && (
            <button className="btn sm ghost" onClick={notifyClient} disabled={notifying} title={client?.email ? `Notify ${client.name} (${client.email})` : 'Set client email first'}>
              <Icon name="mail" size={13}/>
            </button>
          )}
          {role === 'admin' && <button className="btn sm ghost" onClick={() => {
            if (window.confirm(`Delete domain "${effective.name}"? This cannot be undone.`)) {
              onDelete(entity);
            }
          }} title="Delete domain"><Icon name="x" size={13}/></button>}
        </div>
      </td>
    </tr>
  );
};

const ServerRow = ({ row, clients, onEdit, onView, onRenew, onDelete, role, showClient = true, toast, onReload, onOpenPm2 }) => {
  const { entity, pending, effective } = row;
  const client = clients.find(c => c.id === effective.clientId);
  const [running, setRunning] = React.useState(false);
  const _toast = toast || (() => {});
  const _reload = onReload || (async () => {});
  const runHealth = async () => {
    setRunning(true);
    try {
      const r = await api.healthChecks.run('server', entity.id);
      const ms = r && r.responseTimeMs != null ? ` · ${r.responseTimeMs}ms` : '';
      _toast(`Status: ${r?.status || 'unknown'}${ms}`, r?.status === 'down' ? 'danger' : undefined);
      await _reload();
    } catch (err) {
      _toast(err.message || 'Check failed', 'danger');
    } finally {
      setRunning(false);
    }
  };
  return (
    <tr className={pending ? 'has-pending' : ''}>
      <td>
        <div className="cell-primary" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <Icon name="server" size={14} stroke={2} />
          {effective.healthCheckEnabled && (
            <button
              type="button"
              onClick={runHealth}
              disabled={running}
              title={running ? 'Checking…' : (effective.lastStatus === 'up' ? `Up · click to re-check (${window.fmtRelative(effective.lastCheckedAt)})` : effective.lastStatus === 'down' ? `DOWN · click to re-check (${window.fmtRelative(effective.lastCheckedAt)})` : 'Not checked yet · click to check')}
              style={{ display: 'inline-block', width: 12, height: 12, padding: 0, border: 'none', borderRadius: '50%', cursor: running ? 'wait' : 'pointer', background: running ? '#9ca3af' : effective.lastStatus === 'up' ? '#16a34a' : effective.lastStatus === 'down' ? '#dc2626' : '#9ca3af', opacity: running ? 0.6 : 1, flexShrink: 0 }}
            />
          )}
          {effective.label}
          {pending && <span className="badge amber" style={{ fontSize: 10 }}>Pending</span>}
        </div>
        <div className="cell-sub mono">{effective.ip}</div>
        {effective.panelDomain && (
          <div className="cell-sub" style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
            <Icon name="key" size={10} stroke={2.2} style={{ color: 'var(--text-muted)' }}/>
            <a href={`https://${effective.panelDomain}`} target="_blank" rel="noreferrer" style={{ color: 'var(--brand-text)', textDecoration: 'none' }}>{effective.panelDomain}</a>
          </div>
        )}
      </td>
      <td>
        <div>{effective.provider}</div>
        <div className="cell-sub">{effective.region}</div>
      </td>
      <td>
        {effective.type === 'shared'
          ? <span className="badge blue"><Icon name="clients" size={10} stroke={2.2}/> Shared</span>
          : <span className="badge"><Icon name="shield" size={10} stroke={2.2}/> Individual</span>}
      </td>
      {showClient && <td>{client?.name || '—'}</td>}
      <td>
        <div className="cell-sub">{effective.cpu} vCPU · {effective.ram} GB</div>
        <div className="cell-sub">{effective.disk} GB</div>
      </td>
      <td className="mono">{window.fmtDate(effective.start)}</td>
      <td>
        <div className="cell-primary mono">{window.fmtDate(effective.expiry)}</div>
        <div className="cell-sub"><StatusBadge iso={effective.expiry} /></div>
      </td>
      <td>{effective.autoRenew ? <span className="badge green dot">On</span> : <span className="badge">Off</span>}</td>
      <td className="mono">
        {window.fmtINR(effective.billedCost)}
      </td>
      <td className="mono" style={{ color: 'var(--text-secondary)' }}>
        {window.fmtINR(effective.purchasedCost)}
      </td>
      <td title={effective.updatedBy ? `by ${effective.updatedBy}` : ''}>
        <div className="mono" style={{ fontSize: 12 }}>{window.fmtRelative(effective.updatedAt)}</div>
        {effective.updatedBy && <div className="cell-sub">by {effective.updatedBy}</div>}
      </td>
      <td>
        <div className="row-actions">
          <button className="btn sm ghost" onClick={() => onView(entity)}><Icon name="eye" size={13}/></button>
          <button className="btn sm ghost" onClick={() => onEdit(entity)}><Icon name="edit" size={13}/></button>
          {role === 'admin' && effective.sshConfigured && (
            <button className="btn sm ghost" onClick={() => onOpenPm2 && onOpenPm2(entity)} title="PM2 process manager">
              <Icon name="terminal" size={13}/>
            </button>
          )}
          {role === 'admin' && <button className="btn sm ghost" onClick={() => onRenew(entity)} title="Mark renewed"><Icon name="refresh" size={13}/></button>}
          {role === 'admin' && <button className="btn sm ghost" onClick={() => {
            if (window.confirm(`Delete server "${effective.label}"? This cannot be undone.`)) {
              onDelete(entity);
            }
          }} title="Delete server"><Icon name="x" size={13}/></button>}
        </div>
      </td>
    </tr>
  );
};

const DomainsTable = ({ rows, clients, role, servers, onEdit, onView, onRenew, onDelete, sort, onSort, showClient, groupByProject, toast, onReload }) => {
  const colCount = showClient ? 10 : 9;
  const rowProps = { clients, servers, role, onEdit, onView, onRenew, onDelete, showClient, toast, onReload };

  const renderRows = () => {
    if (!groupByProject) {
      return rows.map(row => <DomainRow key={row.entity.id} row={row} {...rowProps} />);
    }

    // Partition into ungrouped + per-groupId clusters
    const grouped = {};
    const ungrouped = [];
    rows.forEach(row => {
      const g = row.effective.groupId;
      if (g) { (grouped[g] = grouped[g] || []).push(row); }
      else ungrouped.push(row);
    });

    return (
      <>
        {ungrouped.map(row => <DomainRow key={row.entity.id} row={row} {...rowProps} />)}
        {Object.entries(grouped).map(([groupId, groupRows]) => (
          <React.Fragment key={groupId}>
            <tr style={{ background: 'var(--bg-subtle)', borderTop: '2px solid var(--border)' }}>
              <td colSpan={colCount} style={{ padding: '4px 14px', fontSize: 11, color: 'var(--text-muted)', fontWeight: 600, letterSpacing: '0.03em' }}>
                <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
                  <Icon name="link" size={10} stroke={2.5} /> {groupId}
                </span>
              </td>
            </tr>
            {groupRows.map(row => <DomainRow key={row.entity.id} row={row} {...rowProps} />)}
          </React.Fragment>
        ))}
      </>
    );
  };

  return (
    <table className="data">
      <thead>
        <tr>
          <Th label="Domain" field="name" sort={sort} onSort={onSort} />
          {showClient && <Th label="Client" field="clientId" sort={sort} onSort={onSort} />}
          <Th label="Hosted on" field="serverId" sort={sort} onSort={onSort} />
          <Th label="Started" field="start" sort={sort} onSort={onSort} />
          <Th label="Expiry" field="expiryStatus" sort={sort} onSort={onSort} />
          <Th label="Auto-renew" field="autoRenew" sort={sort} onSort={onSort} />
          <Th label="Billed" field="billedCost" sort={sort} onSort={onSort} />
          <Th label="Purchased" field="purchasedCost" sort={sort} onSort={onSort} />
          <Th label="Updated" field="updatedAt" sort={sort} onSort={onSort} />
          <Th label="" field="actions" noSort />
        </tr>
      </thead>
      <tbody>
        {rows.length === 0
          ? <tr><td colSpan={colCount}><Empty title="No domains match" body="Try adjusting filters or search." /></td></tr>
          : renderRows()}
      </tbody>
    </table>
  );
};

const ServersTable = ({ rows, clients, role, onEdit, onView, onRenew, onDelete, sort, onSort, showClient, toast, onReload, onOpenPm2 }) => (
  <table className="data">
    <thead>
      <tr>
        <Th label="Label" field="label" sort={sort} onSort={onSort} />
        <Th label="Provider" field="provider" sort={sort} onSort={onSort} />
        <Th label="Type" field="type" sort={sort} onSort={onSort} />
        {showClient && <Th label="Client" field="clientId" sort={sort} onSort={onSort} />}
        <Th label="Specs" field="ram" sort={sort} onSort={onSort} noSort />
        <Th label="Started" field="start" sort={sort} onSort={onSort} />
        <Th label="Expiry" field="expiryStatus" sort={sort} onSort={onSort} />
        <Th label="Auto-renew" field="autoRenew" sort={sort} onSort={onSort} />
        <Th label="Billed" field="billedCost" sort={sort} onSort={onSort} />
        <Th label="Purchased" field="purchasedCost" sort={sort} onSort={onSort} />
        <Th label="Updated" field="updatedAt" sort={sort} onSort={onSort} />
        <Th label="" field="actions" noSort />
      </tr>
    </thead>
    <tbody>
      {rows.length === 0
        ? <tr><td colSpan={showClient ? 12 : 11}><Empty title="No servers match" body="Try adjusting filters or search." /></td></tr>
        : rows.map(row => (
          <ServerRow key={row.entity.id} row={row} clients={clients} role={role}
            onEdit={onEdit} onView={onView} onRenew={onRenew} onDelete={onDelete} showClient={showClient}
            toast={toast} onReload={onReload} onOpenPm2={onOpenPm2} />
        ))}
    </tbody>
  </table>
);

// ── PM2 Modal ─────────────────────────────────────────────────────────────────
const Pm2Modal = ({ server, onClose, toast }) => {
  const [processes, setProcesses] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [actionRunning, setActionRunning] = React.useState(null); // 'appName:action'
  const [output, setOutput] = React.useState(null); // { appName, text }
  const _toast = toast || (() => {});

  const load = React.useCallback(async () => {
    setLoading(true);
    try {
      const r = await api.pm2.action(server.id, 'jlist', null);
      setProcesses(r.processes || []);
    } catch (err) {
      _toast(err.message || 'Failed to load processes', 'danger');
    } finally {
      setLoading(false);
    }
  }, [server.id]);

  React.useEffect(() => { load(); }, [load]);

  const runAction = async (action, appName) => {
    const key = `${appName}:${action}`;
    setActionRunning(key);
    setOutput(null);
    try {
      const r = await api.pm2.action(server.id, action, appName);
      if (action === 'show' || action === 'logs') {
        const lines = (r.output || '').trim().split('\n').slice(-80).join('\n');
        setOutput({ appName, action, text: lines || '(no output)' });
      } else {
        _toast(`${action} ${appName}: OK`);
        await load();
      }
    } catch (err) {
      _toast(err.message || `${action} failed`, 'danger');
    } finally {
      setActionRunning(null);
    }
  };

  const dotColor = (status) => status === 'online' ? '#16a34a' : status === 'stopped' ? '#9ca3af' : '#dc2626';

  return (
    <div style={{ position: 'fixed', inset: 0, zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.5)' }}
      onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div style={{ background: 'var(--bg)', borderRadius: 10, width: '100%', maxWidth: 640, maxHeight: '80vh', overflow: 'auto', boxShadow: '0 20px 60px rgba(0,0,0,.3)', display: 'flex', flexDirection: 'column' }}>
        <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', position: 'sticky', top: 0, background: 'var(--bg)', zIndex: 1 }}>
          <div>
            <div style={{ fontWeight: 600, fontSize: 14 }}>PM2 — {server.label}</div>
            <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{server.ip}</div>
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            <button className="btn sm" onClick={load} disabled={loading} title="Refresh">
              <Icon name="refresh" size={12}/> {loading ? '…' : 'Refresh'}
            </button>
            <button className="btn sm ghost" onClick={onClose}><Icon name="x" size={13}/></button>
          </div>
        </div>
        <div style={{ padding: '12px 20px', flex: 1 }}>
          {loading ? (
            <div style={{ padding: 24, textAlign: 'center', color: 'var(--text-muted)' }}>Loading processes…</div>
          ) : processes.length === 0 ? (
            <div style={{ padding: 24, textAlign: 'center', color: 'var(--text-muted)' }}>No processes found.</div>
          ) : (
            processes.map(p => {
              const isRunning = (key) => actionRunning === `${p.name}:${key}`;
              return (
                <div key={p.id} style={{ padding: '10px 0', borderBottom: '1px solid var(--border)' }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
                    <span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: '50%', background: dotColor(p.status), flexShrink: 0 }}/>
                    <span style={{ fontWeight: 600, fontSize: 13 }}>{p.name}</span>
                    <span className="badge" style={{ textTransform: 'capitalize', fontSize: 10 }}>{p.status}</span>
                    <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>↺ {p.restarts} · up {p.uptime} · CPU {p.cpu}% · {p.mem}</span>
                  </div>
                  <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                    {['restart', 'start', 'stop'].map(a => (
                      <button key={a} className="btn sm" disabled={!!actionRunning} onClick={() => runAction(a, p.name)}
                        style={{ textTransform: 'capitalize' }}>
                        {isRunning(a) ? '…' : a}
                      </button>
                    ))}
                    {['show', 'logs'].map(a => (
                      <button key={a} className="btn sm ghost" disabled={!!actionRunning} onClick={() => runAction(a, p.name)}
                        style={{ textTransform: 'capitalize' }}>
                        {isRunning(a) ? '…' : a}
                      </button>
                    ))}
                  </div>
                </div>
              );
            })
          )}
          {output && (
            <div style={{ marginTop: 12 }}>
              <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 6, color: 'var(--text-muted)' }}>
                {output.action} · {output.appName}
                <button className="btn sm ghost" onClick={() => setOutput(null)} style={{ marginLeft: 8, float: 'right' }}>
                  <Icon name="x" size={11}/>
                </button>
              </div>
              <pre style={{ background: 'var(--bg-subtle)', border: '1px solid var(--border)', borderRadius: 6, padding: '10px 12px', fontSize: 11, overflowX: 'auto', whiteSpace: 'pre-wrap', wordBreak: 'break-all', maxHeight: 320, overflowY: 'auto' }}>{output.text}</pre>
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

const SubscriptionsScreen = (props) => {
  const {
    domains, servers, clients, approvals, role,
    onEdit, onView, onAdd, onRenew, onDelete, grouping, search, onSetGrouping,
    onManageProviders,
    toast, reloadAll,
    kind: scopedKind,   // 'domain' | 'server' | undefined (combined)
  } = props;
  const [pm2Server, setPm2Server] = React.useState(null);
  const safeToast = toast || (() => {});
  const safeReload = reloadAll || (async () => {});
  const [tab, setTab] = React.useState('domains');
  React.useEffect(() => {
    if (scopedKind === 'domain') setTab('domains');
    if (scopedKind === 'server') setTab('servers');
  }, [scopedKind]);
  const [sort, setSort] = React.useState({ field: 'expiryStatus', dir: 'asc' });
  const [statusFilter, setStatusFilter] = React.useState(props.initialStatusFilter || 'all');
  const [clientFilter, setClientFilter] = React.useState(props.initialClientFilter || '');
  React.useEffect(() => {
    if (props.initialStatusFilter !== undefined) setStatusFilter(props.initialStatusFilter || 'all');
    if (props.initialClientFilter !== undefined) setClientFilter(props.initialClientFilter || '');
  }, [props.filterNonce]);
  const [providerFilter, setProviderFilter] = React.useState('');

  const onSort = (field) => {
    setSort(prev => prev.field === field ? { field, dir: prev.dir === 'asc' ? 'desc' : 'asc' } : { field, dir: 'asc' });
  };

  const domainRows = domains.map(d => withPending(d, 'domain', approvals));
  const serverRows = servers.map(s => withPending(s, 'server', approvals));

  const filteredDomains = filterRows(domainRows, search, statusFilter, clientFilter, providerFilter, 'domain');
  const filteredServers = filterRows(serverRows, search, statusFilter, clientFilter, providerFilter, 'server');
  const sortedDomains = sortRows(filteredDomains, sort, clients);
  const sortedServers = sortRows(filteredServers, sort, clients);

  const registrars = [...new Set(domains.map(d => d.registrar))].sort();
  const providers = [...new Set(servers.map(s => s.provider))].sort();

  // ── Build filter dropdown options ─────────────────────────────
  const statusOptions = [
    { value: 'all',     label: 'All statuses', icon: 'archive' },
    { value: 'expired', label: 'Expired',      swatch: '#dc2626' },
    { value: 'soon',    label: 'Expiring ≤ 30 days', swatch: '#d97706' },
    { value: 'active',  label: 'Active',       swatch: '#16a34a' },
  ];
  const clientOptions = [
    { value: '', label: 'All clients', icon: 'clients' },
    ...clients.map(c => ({ value: c.id, label: c.name, meta: c.tag })),
  ];
  const providerOptions = (kind) => [
    { value: '', label: kind === 'domain' ? 'All registrars' : 'All providers', icon: kind === 'domain' ? 'globe' : 'server' },
    ...(kind === 'domain' ? registrars : providers).map(p => ({ value: p, label: p })),
  ];
  const sortOptions = [
    { value: 'expiryStatus:asc',  label: 'Expired → Newest', meta: 'Default', icon: 'sort' },
    { value: 'expiryStatus:desc', label: 'Newest → Expired', icon: 'sort' },
    { divider: true },
    { value: 'updatedAt:desc',    label: 'Recently updated', icon: 'history' },
    { value: 'updatedAt:asc',     label: 'Least recently updated', icon: 'history' },
    { divider: true },
    { value: 'billedCost:desc',   label: 'Billed (high → low)', icon: 'cash' },
    { value: 'billedCost:asc',    label: 'Billed (low → high)', icon: 'cash' },
    { value: 'purchasedCost:desc', label: 'Purchased (high → low)', icon: 'cash' },
    { value: 'purchasedCost:asc', label: 'Purchased (low → high)', icon: 'cash' },
    { divider: true },
    { value: 'name:asc',          label: 'Domain name A → Z', icon: 'globe' },
    { value: 'label:asc',         label: 'Server label A → Z', icon: 'server' },
    { value: 'clientId:asc',      label: 'Client A → Z', icon: 'clients' },
  ];

  const viewOptions = [
    { value: 'tabs',    label: 'Tabs',          icon: 'archive', section: null },
    { value: 'all',     label: 'Grouped · All', icon: 'clients' },
    { value: 'domains', label: 'Grouped · Domains only', icon: 'globe' },
    { value: 'servers', label: 'Grouped · Servers only', icon: 'server' },
  ];

  // ── Shared filter bar (used everywhere) ───────────────────────
  const FilterBar = ({ kind }) => (
    <div className="filters">
      <StyledSelect
        value={statusFilter} onChange={setStatusFilter}
        options={statusOptions}
        icon="filter"
        leadingTone="brand"
      />
      {grouping === 'tabs' && (
        <StyledSelect
          value={clientFilter} onChange={setClientFilter}
          options={clientOptions}
          icon="clients"
          clearable
        />
      )}
      {grouping !== 'tabs' && clientFilter && (
        <StyledSelect
          value={clientFilter} onChange={setClientFilter}
          options={clientOptions}
          icon="clients"
          clearable
        />
      )}
      {kind && (
        <StyledSelect
          value={providerFilter} onChange={setProviderFilter}
          options={providerOptions(kind)}
          icon={kind === 'domain' ? 'globe' : 'server'}
          clearable
        />
      )}
      <div style={{ flex: 1 }}></div>
      <StyledSelect
        label="Sort"
        value={sort.field + ':' + sort.dir}
        onChange={(v) => { const [f, d] = v.split(':'); setSort({ field: f, dir: d }); }}
        options={sortOptions}
        icon="sort"
        align="right"
        minWidth={220}
      />
    </div>
  );

  // ── Effective kind / view depending on scope ────────────────
  // When scopedKind is set, we lock to that kind. The view dropdown shrinks
  // to "Flat / Grouped by client" instead of the 4-mode picker.
  const isScoped = scopedKind === 'domain' || scopedKind === 'server';

  // Scoped views simplify the grouping vocabulary to just two values:
  //   tabs    → flat list (no client grouping)
  //   grouped → grouped by client
  // For backwards compat with the existing `grouping` state we map both
  // 'all'/'domains'/'servers' to "grouped".
  const effectiveGrouping = isScoped
    ? (grouping === 'tabs' ? 'tabs' : 'grouped')
    : grouping;

  // ── Top page head (shared) ────────────────────────────────────
  const scopedTitle = scopedKind === 'domain' ? 'Domain Management' : scopedKind === 'server' ? 'Server Management' : 'Subscriptions';
  const scopedSub = scopedKind === 'domain'
    ? `${domains.length} domain${domains.length === 1 ? '' : 's'} \u00b7 ${approvals.filter(a => a.entity === 'domain').length} pending`
    : scopedKind === 'server'
    ? `${servers.length} server${servers.length === 1 ? '' : 's'} \u00b7 ${approvals.filter(a => a.entity === 'server').length} pending`
    : `${domains.length} domains \u00b7 ${servers.length} servers`;

  const scopedViewOptions = [
    { value: 'tabs',    label: 'Flat list',        icon: 'archive' },
    { value: 'grouped', label: 'Grouped by client', icon: 'clients' },
  ];

  const addKind = isScoped ? scopedKind
    : (grouping === 'servers' || (grouping === 'tabs' && tab === 'servers') ? 'server' : 'domain');

  // Mini summary for scoped pages
  const scopedExpired = isScoped
    ? (scopedKind === 'domain' ? domains : servers).filter(x => window.daysUntil(x.expiry) < 0).length
    : 0;
  const scopedSoon = isScoped
    ? (scopedKind === 'domain' ? domains : servers).filter(x => { const n = window.daysUntil(x.expiry); return n >= 0 && n <= 30; }).length
    : 0;

  const head = (
    <>
      <div className="page-head">
        <div>
          <h1>{scopedTitle}</h1>
          <div className="sub">
            {scopedSub}
            {isScoped && scopedExpired > 0 && <> · <span style={{ color: 'var(--red-text)', fontWeight: 500 }}>{scopedExpired} expired</span></>}
            {isScoped && scopedSoon > 0 && <> · <span style={{ color: 'var(--amber-text)', fontWeight: 500 }}>{scopedSoon} expiring soon</span></>}
          </div>
        </div>
        <div className="page-actions">
          <StyledSelect
            label="View"
            value={effectiveGrouping}
            onChange={(v) => onSetGrouping(v)}
            options={isScoped ? scopedViewOptions : viewOptions}
            icon={isScoped ? (effectiveGrouping === 'tabs' ? 'archive' : 'clients') : 'dashboard'}
            align="right"
            minWidth={isScoped ? 180 : 220}
          />
          {isScoped && role === 'admin' && (
            <button className="btn" onClick={onManageProviders} title={`Manage ${scopedKind === 'domain' ? 'registrars' : 'providers'}`}>
              <Icon name="archive" size={13}/> Manage {scopedKind === 'domain' ? 'registrars' : 'providers'}
            </button>
          )}
          <button className="btn brand" onClick={() => onAdd(addKind)}>
            <Icon name="plus" size={13}/> Add {isScoped ? scopedKind : 'subscription'}
          </button>
        </div>
      </div>
    </>
  );

  // ── Grouped variants ──────────────────────────────────────────
  if (effectiveGrouping !== 'tabs') {
    // For scoped pages, "grouped" means grouped-by-client filtered to that kind
    const includeDomains = isScoped ? scopedKind === 'domain'
      : (grouping === 'all' || grouping === 'domains');
    const includeServers = isScoped ? scopedKind === 'server'
      : (grouping === 'all' || grouping === 'servers');

    return (
      <>
        {head}
        <FilterBar kind={isScoped ? scopedKind : (grouping === 'domains' ? 'domain' : grouping === 'servers' ? 'server' : null)} />

        {clients.map(c => {
          const ds = includeDomains ? sortedDomains.filter(r => r.effective.clientId === c.id) : [];
          const ss = includeServers ? sortedServers.filter(r => r.effective.clientId === c.id) : [];
          if (!ds.length && !ss.length) return null;
          return (
            <div key={c.id} className="client-group">
              <div className="client-group-head">
                <Avatar name={c.name} size={22} />
                <h3>{c.name}</h3>
                <span className="chip">
                  {includeDomains && includeServers
                    ? `${ds.length + ss.length} items`
                    : includeDomains ? `${ds.length} domain${ds.length === 1 ? '' : 's'}`
                    : `${ss.length} server${ss.length === 1 ? '' : 's'}`}
                </span>
                <span className="sub">{c.tag}</span>
              </div>

              {includeDomains && ds.length > 0 && (
                <div className="subgroup">
                  <div className="subgroup-head"><Icon name="globe" size={12} stroke={2}/> Domains · {ds.length}</div>
                  <div className="table-wrap">
                    <DomainsTable rows={ds} clients={clients} servers={servers} role={role} sort={sort} onSort={onSort}
                      onEdit={onEdit} onView={onView} onRenew={onRenew} onDelete={onDelete} showClient={false} groupByProject={true}
                      toast={safeToast} onReload={safeReload} />
                  </div>
                </div>
              )}

              {includeServers && ss.length > 0 && (
                <div className="subgroup">
                  <div className="subgroup-head"><Icon name="server" size={12} stroke={2}/> Servers · {ss.length}</div>
                  <div className="table-wrap">
                    <ServersTable rows={ss} clients={clients} role={role} sort={sort} onSort={onSort}
                      onEdit={onEdit} onView={onView} onRenew={onRenew} onDelete={onDelete} showClient={false}
                      toast={safeToast} onReload={safeReload} onOpenPm2={setPm2Server} />
                  </div>
                </div>
              )}
            </div>
          );
        })}

        {(() => {
          const totalShown = clients.reduce((sum, c) =>
            sum + (includeDomains ? sortedDomains.filter(r => r.effective.clientId === c.id).length : 0)
                + (includeServers ? sortedServers.filter(r => r.effective.clientId === c.id).length : 0)
          , 0);
          if (totalShown === 0) {
            return <Empty
              icon="archive"
              title="Nothing to show"
              body="No subscriptions match the current filters."
            />;
          }
          return null;
        })()}
        {pm2Server && <Pm2Modal server={pm2Server} toast={safeToast} onClose={() => setPm2Server(null)} />}
      </>
    );
  }

  // ── Tabs / Flat variant ───────────────────────────────────────
  // When scoped, render just the single-kind table (no tab UI).
  if (isScoped) {
    return (
      <>
        {head}
        <FilterBar kind={scopedKind} />
        <div className="card">
          <div className="table-wrap">
            {scopedKind === 'domain' ? (
              <DomainsTable rows={sortedDomains} clients={clients} servers={servers} role={role} sort={sort} onSort={onSort}
                onEdit={onEdit} onView={onView} onRenew={onRenew} onDelete={onDelete} showClient={true}
                toast={safeToast} onReload={safeReload} />
            ) : (
              <ServersTable rows={sortedServers} clients={clients} role={role} sort={sort} onSort={onSort}
                onEdit={onEdit} onView={onView} onRenew={onRenew} onDelete={onDelete} showClient={true}
                toast={safeToast} onReload={safeReload} onOpenPm2={setPm2Server} />
            )}
          </div>
        </div>
        {pm2Server && <Pm2Modal server={pm2Server} toast={safeToast} onClose={() => setPm2Server(null)} />}
      </>
    );
  }

  return (
    <>
      {head}

      <div className="tabs">
        <button className={`tab ${tab === 'domains' ? 'active' : ''}`} onClick={() => setTab('domains')}>
          <Icon name="globe" size={14} stroke={2}/> Domains <span className="count">{filteredDomains.length}</span>
        </button>
        <button className={`tab ${tab === 'servers' ? 'active' : ''}`} onClick={() => setTab('servers')}>
          <Icon name="server" size={14} stroke={2}/> Servers <span className="count">{filteredServers.length}</span>
        </button>
      </div>

      <FilterBar kind={tab === 'domains' ? 'domain' : 'server'} />

      <div className="card">
        <div className="table-wrap">
          {tab === 'domains' ? (
            <DomainsTable rows={sortedDomains} clients={clients} servers={servers} role={role} sort={sort} onSort={onSort}
              onEdit={onEdit} onView={onView} onRenew={onRenew} onDelete={onDelete} showClient={true}
              toast={safeToast} onReload={safeReload} />
          ) : (
            <ServersTable rows={sortedServers} clients={clients} role={role} sort={sort} onSort={onSort}
              onEdit={onEdit} onView={onView} onRenew={onRenew} onDelete={onDelete} showClient={true}
              toast={safeToast} onReload={safeReload} onOpenPm2={setPm2Server} />
          )}
        </div>
      </div>
      {pm2Server && <Pm2Modal server={pm2Server} toast={safeToast} onClose={() => setPm2Server(null)} />}
    </>
  );
};

window.SubscriptionsScreen = SubscriptionsScreen;
window.Pm2Modal = Pm2Modal;
window.FIELD_LABELS = FIELD_LABELS;
window.renderFieldValue = renderFieldValue;
window.DomainsTable = DomainsTable;
window.ServersTable = ServersTable;
