/* global React, ReactDOM, Button, Icon, Pill, StatusDot, Select, useApiResource, apiFetch, formatDateTime, EmptyState, useToast, ConfirmDialog, useAuth, requirePaidAction, canUseTenantAction, tenantActionDisabledReason */
const { useEffect: useEffectAlerts, useRef: useRefAlerts, useState: useStateAlerts, useMemo: useMemoAlerts } = React;
const SLACK_LOGO_SRC = '/frontend/assets/slack_icon.svg.png';
const EMAIL_TARGET_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const SLACK_REFRESH_FAILED_MESSAGE = 'Slack connected, but Armature could not refresh the connection status. Refresh the page to see the latest Slack workspace.';
const SLACK_INSTALL_REFRESH_MAX_ATTEMPTS = 6;
const SLACK_INSTALL_REFRESH_RETRY_MS = 750;
const SLACK_INSTALL_BASELINE_STORAGE_KEY = 'armature:slack-install-baseline';
const SETTINGS_ALERTS_PATH = '/settings/alerts';

const ALERT_DESTINATIONS = [
  {
    key: 'slack',
    label: 'Slack',
    icon: 'plug',
    targetHeader: 'Slack target',
    emptyTitle: 'No Slack alert rules configured',
  },
  {
    key: 'email',
    label: 'Email',
    icon: 'mail',
    targetHeader: 'Email target',
    emptyTitle: 'No email alert rules configured',
  },
  {
    key: 'incidentio',
    label: 'incident.io',
    icon: 'alert',
    targetHeader: 'incident.io target',
    emptyTitle: 'No incident.io alert rules configured',
    hidden: true,
  },
  {
    key: 'pagerduty',
    label: 'PagerDuty',
    icon: 'bell',
    targetHeader: 'PagerDuty target',
    emptyTitle: 'No PagerDuty alert rules configured',
    hidden: true,
  },
];
const VISIBLE_ALERT_DESTINATIONS = ALERT_DESTINATIONS.filter((destination) => !destination.hidden);

const TRIGGERED_WINDOW_MS = 24 * 60 * 60 * 1000;
const DEFAULT_ALERT_LIMIT_POLICY = { cooldownMinutes: 60, dailyCap: 24 };

function clampInteger(value, min, max, fallback) {
  const next = Number(value);
  if (!Number.isInteger(next)) return fallback;
  return Math.max(min, Math.min(max, next));
}

function effectiveLimitPolicy(rule) {
  return {
    cooldownMinutes: rule?.limitPolicy?.cooldownMinutes ?? DEFAULT_ALERT_LIMIT_POLICY.cooldownMinutes,
    dailyCap: rule?.limitPolicy?.dailyCap ?? DEFAULT_ALERT_LIMIT_POLICY.dailyCap,
  };
}

function deriveRuleStatus(rule) {
  if (!rule.isActive) return 'paused';
  if (!rule.lastTriggeredAt) return 'healthy';
  const lastMs = new Date(rule.lastTriggeredAt).getTime();
  if (Number.isNaN(lastMs)) return 'healthy';
  if (Date.now() - lastMs <= TRIGGERED_WINDOW_MS) return 'triggered';
  return 'healthy';
}

function formatRelative(iso) {
  if (!iso) return 'never';
  const ms = Date.now() - new Date(iso).getTime();
  if (Number.isNaN(ms)) return 'never';
  if (ms < 60_000) return 'just now';
  if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m ago`;
  if (ms < 86_400_000) return `${Math.round(ms / 3_600_000)}h ago`;
  return `${Math.round(ms / 86_400_000)}d ago`;
}

function describeRuleStatus(rule) {
  const status = deriveRuleStatus(rule);
  if (status === 'triggered') {
    return { tone: 'warn', label: `Fired ${formatRelative(rule.lastTriggeredAt)}` };
  }
  if (status === 'paused') {
    return { tone: 'muted', label: 'Paused — won’t fire' };
  }
  if (rule.lastTriggeredAt) {
    return { tone: 'ok', label: `Last fired ${formatRelative(rule.lastTriggeredAt)}` };
  }
  return { tone: 'ok', label: 'Watching — never fired' };
}

function activeSlackIntegrations(data) {
  return (data?.integrations || []).filter(
    (integration) => integration?.type === 'slack' && integration?.isActive !== false,
  );
}

function hasActiveSlackIntegration(data) {
  return activeSlackIntegrations(data).length > 0;
}

function slackIntegrationIdentity(integration) {
  const metadata = integration?.metadata || {};
  const workspaceId = metadata.team?.id || metadata.enterprise?.id || '';
  return [
    String(integration?.id || ''),
    String(workspaceId || ''),
    String(integration?.updatedAt || ''),
  ].join(':');
}

function slackIntegrationSignature(data) {
  return JSON.stringify(activeSlackIntegrations(data).map(slackIntegrationIdentity).sort());
}

function readSlackInstallBaseline() {
  try {
    return window.sessionStorage?.getItem(SLACK_INSTALL_BASELINE_STORAGE_KEY) || null;
  } catch (_error) {
    return null;
  }
}

function writeSlackInstallBaseline(signature) {
  try {
    window.sessionStorage?.setItem(SLACK_INSTALL_BASELINE_STORAGE_KEY, signature);
  } catch (_error) {}
}

function clearStoredSlackInstallBaseline() {
  try {
    window.sessionStorage?.removeItem(SLACK_INSTALL_BASELINE_STORAGE_KEY);
  } catch (_error) {}
}

// useSlackInstallController encapsulates the Slack OAuth popup/postMessage flow
// previously bundled inside AlertsPage. Both SettingsAlertsPage (Settings tab)
// and AlertRulesForTargetModal need to react to install success/failure to
// refresh the integrations list and surface user-facing status; this hook gives
// them the same machinery without duplicating ~120 lines of state and refs.
function useSlackInstallController({ alertsResource, navigate, queryString, redirectPath }) {
  const auth = useAuth();
  const [slackConnecting, setSlackConnecting] = useStateAlerts(false);
  const [message, setMessage] = useStateAlerts('');
  const slackPopupPollRef = useRefAlerts(null);
  const slackRefreshRetryRef = useRefAlerts(null);
  const slackInstallBaselineRef = useRefAlerts(null);
  const handledSlackInstallQueryRef = useRefAlerts('');

  function clearSlackPopupPoll() {
    if (slackPopupPollRef.current && typeof window.clearInterval === 'function') {
      window.clearInterval(slackPopupPollRef.current);
    }
    slackPopupPollRef.current = null;
  }

  function clearSlackRefreshRetry() {
    if (slackRefreshRetryRef.current && typeof window.clearTimeout === 'function') {
      window.clearTimeout(slackRefreshRetryRef.current);
    }
    slackRefreshRetryRef.current = null;
  }

  function storeSlackInstallBaseline() {
    const signature = slackIntegrationSignature(alertsResource.data);
    slackInstallBaselineRef.current = signature;
    writeSlackInstallBaseline(signature);
  }

  function currentSlackInstallBaseline() {
    if (slackInstallBaselineRef.current !== null) return slackInstallBaselineRef.current;
    slackInstallBaselineRef.current = readSlackInstallBaseline();
    return slackInstallBaselineRef.current;
  }

  function clearSlackInstallBaseline() {
    slackInstallBaselineRef.current = null;
    clearStoredSlackInstallBaseline();
  }

  function slackInstallRefreshComplete(data) {
    const baseline = currentSlackInstallBaseline();
    if (baseline !== null) return slackIntegrationSignature(data) !== baseline;
    return hasActiveSlackIntegration(data);
  }

  function scheduleSlackRefreshRetry(attempt) {
    if (attempt >= SLACK_INSTALL_REFRESH_MAX_ATTEMPTS || typeof window.setTimeout !== 'function') {
      clearSlackInstallBaseline();
      setMessage(SLACK_REFRESH_FAILED_MESSAGE);
      return;
    }
    slackRefreshRetryRef.current = window.setTimeout(() => {
      slackRefreshRetryRef.current = null;
      void refreshAlertsAfterSlackInstall(attempt + 1);
    }, SLACK_INSTALL_REFRESH_RETRY_MS);
  }

  async function refreshAlertsAfterSlackInstall(attempt = 1) {
    try {
      const nextData = await alertsResource.reload();
      if (slackInstallRefreshComplete(nextData)) {
        clearSlackRefreshRetry();
        clearSlackInstallBaseline();
        return;
      }
      scheduleSlackRefreshRetry(attempt);
    } catch (_error) {
      scheduleSlackRefreshRetry(attempt);
    }
  }

  function startSlackInstallRefresh() {
    clearSlackRefreshRetry();
    void refreshAlertsAfterSlackInstall();
  }

  function watchSlackInstallPopup(popup) {
    if (!popup || typeof window.setInterval !== 'function') return false;
    clearSlackPopupPoll();
    slackPopupPollRef.current = window.setInterval(() => {
      if (!popup.closed) return;
      clearSlackPopupPoll();
      setSlackConnecting(false);
      startSlackInstallRefresh();
    }, 500);
    return true;
  }

  useEffectAlerts(() => {
    const params = new URLSearchParams(queryString || '');
    const slackInstall = params.get('slackInstall');
    if (!slackInstall || handledSlackInstallQueryRef.current === queryString) return;
    handledSlackInstallQueryRef.current = queryString;
    if (slackInstall === 'success') {
      setMessage('Slack workspace connected.');
      startSlackInstallRefresh();
      if (typeof navigate === 'function' && redirectPath) navigate(redirectPath, { replace: true });
    } else if (slackInstall === 'failed') {
      clearSlackRefreshRetry();
      clearSlackInstallBaseline();
      setSlackConnecting(false);
      setMessage('Slack workspace installation failed. Retry the setup flow.');
      if (typeof navigate === 'function' && redirectPath) navigate(redirectPath, { replace: true });
    }
  }, [queryString]);

  useEffectAlerts(() => {
    function onMessage(event) {
      if (event.origin !== window.location.origin) return;
      if (event.data?.type === 'armature:slack-installed') {
        clearSlackPopupPoll();
        setSlackConnecting(false);
        setMessage('Slack workspace connected.');
        startSlackInstallRefresh();
      } else if (event.data?.type === 'armature:slack-install-failed') {
        clearSlackPopupPoll();
        clearSlackRefreshRetry();
        clearSlackInstallBaseline();
        setSlackConnecting(false);
        setMessage('Slack workspace installation failed. Retry the setup flow.');
      }
    }
    window.addEventListener('message', onMessage);
    return () => window.removeEventListener('message', onMessage);
  }, [alertsResource.reload]);

  useEffectAlerts(() => () => {
    clearSlackPopupPoll();
    clearSlackRefreshRetry();
  }, []);

  async function connectSlack() {
    if (!requirePaidAction(auth, navigate, { requiredRole: 'editor' })) return;
    setMessage('');
    setSlackConnecting(true);
    clearSlackRefreshRetry();
    storeSlackInstallBaseline();
    let watchingPopup = false;
    try {
      const result = await apiFetch('/api/alerts/slack/setup/start', { method: 'POST' });
      const popup = window.open(result.redirectUrl, 'armature-slack-install', 'width=920,height=760');
      if (!popup) {
        window.location.href = result.redirectUrl;
        return;
      }
      watchingPopup = watchSlackInstallPopup(popup);
    } catch (error) {
      clearSlackInstallBaseline();
      setMessage(error.message);
    } finally {
      if (!watchingPopup) setSlackConnecting(false);
    }
  }

  return { slackConnecting, message, setMessage, connectSlack };
}

function AlertsSummaryStrip({ rows }) {
  const counts = rows.reduce((acc, r) => {
    const s = deriveRuleStatus(r);
    acc[s] = (acc[s] || 0) + 1;
    return acc;
  }, { triggered: 0, healthy: 0, paused: 0 });
  const total = rows.length;
  if (total === 0) return null;
  return (
    <div className="alerts-summary-strip">
      <div className="alerts-summary-cell">
        <span className="alerts-summary-value">{total}</span>
        <span className="alerts-summary-label">rule{total === 1 ? '' : 's'}</span>
      </div>
      <span className="alerts-summary-sep" aria-hidden="true">·</span>
      <div className={`alerts-summary-cell ${counts.triggered > 0 ? 'tone-bad' : ''}`}>
        <StatusDot tone={counts.triggered > 0 ? 'bad' : 'neutral'} />
        <span className="alerts-summary-value">{counts.triggered}</span>
        <span className="alerts-summary-label">firing</span>
      </div>
      <div className={`alerts-summary-cell ${counts.healthy > 0 ? 'tone-ok' : ''}`}>
        <StatusDot tone={counts.healthy > 0 ? 'ok' : 'neutral'} />
        <span className="alerts-summary-value">{counts.healthy}</span>
        <span className="alerts-summary-label">healthy</span>
      </div>
      <div className="alerts-summary-cell">
        <StatusDot tone="neutral" />
        <span className="alerts-summary-value">{counts.paused}</span>
        <span className="alerts-summary-label">paused</span>
      </div>
    </div>
  );
}

// SettingsAlertsPage owns channel configuration (Slack/Email) AND full rule
// CRUD for the workspace. Tool monitors and Workflows still let you manage
// rules in-context (via AlertRulesForTargetModal), but Settings → Alerts is the
// global home — list every rule, create one with a target picker, edit/pause/
// delete inline.
function SettingsAlertsPage({ navigate, queryString = '' }) {
  const alerts = useApiResource('/api/alerts');
  const workflows = useApiResource('/api/workflows');
  const toolMonitors = useApiResource('/api/tool-monitors');
  const rows = alerts.data?.rows || [];
  const integrations = alerts.data?.integrations || [];
  const slackIntegrations = integrations.filter((integration) => integration.type === 'slack');
  const alertsInitialLoading = alerts.loading && !alerts.data;
  const workflowRows = workflows.data?.rows || [];
  const toolMonitorRows = toolMonitors.data?.rows || [];
  const auth = useAuth();
  const toast = useToast();
  const canManageAlerts = canUseTenantAction(auth);
  const alertActionDisabledReason = tenantActionDisabledReason(auth);
  const slack = useSlackInstallController({
    alertsResource: alerts,
    navigate,
    queryString,
    redirectPath: SETTINGS_ALERTS_PATH,
  });
  const integrationByType = integrations.reduce((acc, integration) => {
    if (!acc[integration.type] && integration.isActive !== false) {
      acc[integration.type] = integration;
    }
    return acc;
  }, {});

  // Editor state — mirrors AlertRulesForTargetModal, but with a user-picked
  // target (no lockedTarget). Kept inline here rather than extracted to a hook
  // so the Settings flow stays readable end-to-end; both surfaces use the same
  // sub-components (SlackAlertPanel, EmailAlertPanel, RuleRow, etc.).
  const [activeDestination, setActiveDestination] = useStateAlerts('slack');
  const [editingId, setEditingId] = useStateAlerts('');
  const [name, setName] = useStateAlerts('');
  const [targetKind, setTargetKind] = useStateAlerts('workflow');
  const [workflowId, setWorkflowId] = useStateAlerts('');
  const [toolMonitorId, setToolMonitorId] = useStateAlerts('');
  const [integrationId, setIntegrationId] = useStateAlerts('');
  const [channel, setChannel] = useStateAlerts('');
  const [channelMode, setChannelMode] = useStateAlerts('select');
  const [channels, setChannels] = useStateAlerts([]);
  const [channelsLoading, setChannelsLoading] = useStateAlerts(false);
  const [channelsError, setChannelsError] = useStateAlerts('');
  const [enabled, setEnabled] = useStateAlerts(true);
  const [scoreThreshold, setScoreThreshold] = useStateAlerts(3);
  const [triggerMode, setTriggerMode] = useStateAlerts('consecutive');
  const [consecutiveRequired, setConsecutiveRequired] = useStateAlerts(2);
  const [cooldownMinutes, setCooldownMinutes] = useStateAlerts(60);
  const [dailyCap, setDailyCap] = useStateAlerts(24);
  const [saving, setSaving] = useStateAlerts(false);
  const [formMessage, setFormMessage] = useStateAlerts('');
  const [deletingRuleId, setDeletingRuleId] = useStateAlerts('');
  const [confirmDeleteRule, setConfirmDeleteRule] = useStateAlerts(null);
  const isEditorOpen = Boolean(editingId);

  function resetForm() {
    setEditingId('');
    setName('');
    setTargetKind('workflow');
    setWorkflowId('');
    setToolMonitorId('');
    setIntegrationId('');
    setChannel('');
    setChannelMode('select');
    setEnabled(true);
    setScoreThreshold(3);
    setTriggerMode('consecutive');
    setConsecutiveRequired(2);
    setCooldownMinutes(DEFAULT_ALERT_LIMIT_POLICY.cooldownMinutes);
    setDailyCap(DEFAULT_ALERT_LIMIT_POLICY.dailyCap);
  }

  function applyLimitPolicyForTarget(nextTargetKind, nextTargetId) {
    const matchingRule = rows.find((rule) => (
      nextTargetKind === 'tool-monitor'
        ? rule.toolMonitorId === nextTargetId
        : rule.workflowId === nextTargetId
    ));
    const policy = matchingRule ? effectiveLimitPolicy(matchingRule) : DEFAULT_ALERT_LIMIT_POLICY;
    setCooldownMinutes(policy.cooldownMinutes);
    setDailyCap(policy.dailyCap);
  }

  function updateTargetKind(nextTargetKind) {
    setTargetKind(nextTargetKind);
    setWorkflowId('');
    setToolMonitorId('');
    if (editingId === 'new') applyLimitPolicyForTarget(nextTargetKind, '');
  }

  function updateWorkflowId(nextWorkflowId) {
    setWorkflowId(nextWorkflowId);
    if (editingId === 'new') applyLimitPolicyForTarget('workflow', nextWorkflowId);
  }

  function updateToolMonitorId(nextToolMonitorId) {
    setToolMonitorId(nextToolMonitorId);
    if (editingId === 'new') applyLimitPolicyForTarget('tool-monitor', nextToolMonitorId);
  }

  function beginCreate() {
    if (!requirePaidAction(auth, navigate, { requiredRole: 'editor' })) return;
    resetForm();
    setActiveDestination('slack');
    setEditingId('new');
    setFormMessage('');
  }

  function beginEdit(rule) {
    if (!canManageAlerts) return;
    setActiveDestination(rule.target?.type || 'slack');
    setEditingId(rule.id);
    setName(rule.name || '');
    setTargetKind(rule.toolMonitorId ? 'tool-monitor' : 'workflow');
    setWorkflowId(rule.workflowId || '');
    setToolMonitorId(rule.toolMonitorId || '');
    setIntegrationId(rule.target?.integrationId || '');
    setChannel(rule.target?.channel || '');
    setChannelMode(rule.target?.channel?.startsWith('#') ? 'manual' : 'select');
    setEnabled(Boolean(rule.isActive));
    setScoreThreshold(rule.scoreThreshold ?? 3);
    setTriggerMode(rule.triggerMode ?? 'consecutive');
    setConsecutiveRequired(rule.consecutiveRequired ?? 2);
    const policy = effectiveLimitPolicy(rule);
    setCooldownMinutes(policy.cooldownMinutes);
    setDailyCap(policy.dailyCap);
    setFormMessage('');
  }

  function closeEditor() {
    resetForm();
    setFormMessage('');
  }

  useEffectAlerts(() => {
    if (!isEditorOpen) return;
    if (activeDestination !== 'slack') return;
    if (slackIntegrations.length === 1 && !integrationId) {
      setIntegrationId(slackIntegrations[0].id);
      return;
    }
    if (integrationId && !slackIntegrations.some((integration) => integration.id === integrationId)) {
      setIntegrationId('');
      setChannel('');
    }
  }, [isEditorOpen, activeDestination, integrations, integrationId]);

  useEffectAlerts(() => {
    if (!isEditorOpen) return undefined;
    let canceled = false;
    async function loadChannels() {
      if (activeDestination !== 'slack' || !integrationId) {
        setChannels([]);
        setChannelsError('');
        setChannelsLoading(false);
        return;
      }
      setChannelsLoading(true);
      setChannelsError('');
      try {
        const data = await apiFetch(`/api/alerts/slack/channels?integrationId=${encodeURIComponent(integrationId)}`);
        if (canceled) return;
        setChannels(data.channels || []);
      } catch (error) {
        if (canceled) return;
        setChannels([]);
        setChannelsError(error.message || 'Could not load Slack channels');
      } finally {
        if (!canceled) setChannelsLoading(false);
      }
    }
    loadChannels();
    return () => { canceled = true; };
  }, [isEditorOpen, activeDestination, integrationId]);

  const integrationsReady = slackIntegrations.length > 0;
  const selectedChannelExists = channels.some((slackChannel) => slackChannel.id === channel);
  const selectedSlackIntegrationId = integrationId || (slackIntegrations.length === 1 ? slackIntegrations[0].id : '');
  const targetObjectSelected = targetKind === 'tool-monitor' ? Boolean(toolMonitorId) : Boolean(workflowId);
  const targetObjectMissingLabel = targetKind === 'tool-monitor' ? 'Tool monitor' : 'Workflow';
  const missingSlackRuleFields = [];
  if (!name.trim()) missingSlackRuleFields.push('Name');
  if (!targetObjectSelected) missingSlackRuleFields.push(targetObjectMissingLabel);
  if (!selectedSlackIntegrationId) missingSlackRuleFields.push('Slack workspace');
  if (!channel.trim() || (channelMode === 'select' && !selectedChannelExists)) {
    missingSlackRuleFields.push('Destination');
  }
  const missingEmailRuleFields = [];
  if (!name.trim()) missingEmailRuleFields.push('Name');
  if (!targetObjectSelected) missingEmailRuleFields.push(targetObjectMissingLabel);
  if (!EMAIL_TARGET_RE.test(channel.trim())) missingEmailRuleFields.push('Recipient email');
  const missingActiveRuleFields = activeDestination === 'email' ? missingEmailRuleFields : missingSlackRuleFields;
  const saveDisabledReason = saving
    ? 'Saving alert rule...'
    : !canManageAlerts
      ? alertActionDisabledReason
      : missingActiveRuleFields.length > 0
        ? `Missing fields: ${missingActiveRuleFields.join(', ')}`
        : '';
  const canSave = canManageAlerts
    && !saving
    && (
      activeDestination === 'email'
        ? name.trim() && targetObjectSelected && EMAIL_TARGET_RE.test(channel.trim())
        : name.trim()
          && targetObjectSelected
          && selectedSlackIntegrationId
          && channel.trim()
          && (channelMode === 'manual' || selectedChannelExists)
    );
  const formEditingId = editingId === 'new' ? '' : editingId;
  const isNewRule = editingId === 'new';

  async function saveRule() {
    if (!requirePaidAction(auth, navigate, { requiredRole: 'editor' })) return;
    setSaving(true);
    setFormMessage('');
    try {
      const wasEditing = Boolean(editingId) && editingId !== 'new';
      const body = {
        name,
        ...(targetKind === 'tool-monitor' ? { toolMonitorId } : { workflowId }),
        isActive: enabled,
        scoreThreshold,
        triggerMode,
        consecutiveRequired,
        limitPolicy: {
          cooldownMinutes,
          dailyCap,
        },
        target: activeDestination === 'email'
          ? { type: 'email', channel }
          : { type: 'slack', integrationId: selectedSlackIntegrationId, channel },
      };
      const path = wasEditing ? `/api/alerts/${editingId}` : '/api/alerts';
      const method = wasEditing ? 'PATCH' : 'POST';
      await apiFetch(path, { method, body: JSON.stringify(body) });
      await alerts.reload();
      resetForm();
      toast.show({ tone: 'ok', title: wasEditing ? 'Alert rule updated' : 'Alert rule created' });
    } catch (error) {
      setFormMessage(error.message);
    } finally {
      setSaving(false);
    }
  }

  async function performRemove(ruleId) {
    if (!requirePaidAction(auth, navigate, { requiredRole: 'editor' })) return;
    setDeletingRuleId(ruleId);
    try {
      await apiFetch(`/api/alerts/${ruleId}`, { method: 'DELETE' });
      if (editingId === ruleId) resetForm();
      await alerts.reload();
      toast.show({ tone: 'ok', title: 'Alert rule deleted' });
    } catch (error) {
      toast.show({ tone: 'bad', title: 'Delete failed', description: error.message });
    } finally {
      setDeletingRuleId('');
      setConfirmDeleteRule(null);
    }
  }

  async function toggleRuleActive(rule) {
    if (!requirePaidAction(auth, navigate, { requiredRole: 'editor' })) return;
    try {
      await apiFetch(`/api/alerts/${rule.id}`, {
        method: 'PATCH',
        body: JSON.stringify({ isActive: !rule.isActive }),
      });
      await alerts.reload();
      toast.show({ tone: 'ok', title: rule.isActive ? 'Rule paused' : 'Rule resumed' });
    } catch (error) {
      toast.show({ tone: 'bad', title: 'Update failed', description: error.message });
    }
  }

  // No Escape-key effect here — AlertRuleModal registers its own keydown
  // listener that calls onClose (closeEditor), so handling it here too would
  // run closeEditor twice per keypress.

  const targetField = (
    <RuleTargetObjectField
      targetKind={targetKind}
      setTargetKind={updateTargetKind}
      workflowId={workflowId}
      setWorkflowId={updateWorkflowId}
      workflowRows={workflowRows}
      workflowsLoading={workflows.loading}
      toolMonitorId={toolMonitorId}
      setToolMonitorId={updateToolMonitorId}
      toolMonitorRows={toolMonitorRows}
      toolMonitorsLoading={toolMonitors.loading}
      disabled={editingId !== '' && editingId !== 'new'} />
  );

  const slackPanel = (
    <SlackAlertPanel
      integrations={slackIntegrations}
      integrationsLoading={alertsInitialLoading}
      integrationsReady={integrationsReady}
      connectSlack={slack.connectSlack}
      slackConnecting={slack.slackConnecting}
      editingId={formEditingId}
      saving={saving}
      name={name}
      setName={setName}
      targetField={targetField}
      workflowId={targetKind === 'workflow' ? workflowId : ''}
      integrationId={integrationId}
      setIntegrationId={setIntegrationId}
      channel={channel}
      setChannel={setChannel}
      channelMode={channelMode}
      setChannelMode={setChannelMode}
      channels={channels}
      channelsLoading={channelsLoading}
      channelsError={channelsError}
      selectedChannelExists={selectedChannelExists}
      enabled={enabled}
      setEnabled={setEnabled}
      scoreThreshold={scoreThreshold}
      setScoreThreshold={setScoreThreshold}
      triggerMode={triggerMode}
      setTriggerMode={setTriggerMode}
      consecutiveRequired={consecutiveRequired}
      setConsecutiveRequired={setConsecutiveRequired}
      cooldownMinutes={cooldownMinutes}
      setCooldownMinutes={setCooldownMinutes}
      dailyCap={dailyCap}
      setDailyCap={setDailyCap}
      canSave={canSave}
      saveDisabledReason={saveDisabledReason}
      saveRule={saveRule}
      resetForm={closeEditor} />
  );
  const emailPanel = (
    <EmailAlertPanel
      editingId={formEditingId}
      saving={saving}
      name={name}
      setName={setName}
      targetField={targetField}
      workflowId={targetKind === 'workflow' ? workflowId : ''}
      email={channel}
      setEmail={setChannel}
      enabled={enabled}
      setEnabled={setEnabled}
      scoreThreshold={scoreThreshold}
      setScoreThreshold={setScoreThreshold}
      triggerMode={triggerMode}
      setTriggerMode={setTriggerMode}
      consecutiveRequired={consecutiveRequired}
      setConsecutiveRequired={setConsecutiveRequired}
      cooldownMinutes={cooldownMinutes}
      setCooldownMinutes={setCooldownMinutes}
      dailyCap={dailyCap}
      setDailyCap={setDailyCap}
      canSave={canSave}
      saveDisabledReason={saveDisabledReason}
      saveRule={saveRule}
      resetForm={closeEditor} />
  );

  return (
    <div className="card settings-card settings-alerts-card">
      {slack.message && <div className="auth-message alerts-message">{slack.message}</div>}

      <AlertsDestinationsRow
        integrationByType={integrationByType}
        loading={alertsInitialLoading}
        slackConnecting={slack.slackConnecting}
        connectSlack={slack.connectSlack}
        canManageAlerts={canManageAlerts}
        alertActionDisabledReason={alertActionDisabledReason} />

      <div className="settings-alerts-rollup">
        <div className="settings-alerts-rollup-head">
          <div className="settings-alerts-rollup-head-text">
            <span className="field-label">Alert rules</span>
            <span className="field-help">
              Watch a workflow or tool monitor; deliver to Slack or email when it fails. You can also set rules up directly from each Tool monitor or Workflow row.
            </span>
          </div>
          <Button
            variant="primary"
            size="sm"
            disabled={!canManageAlerts}
            title={canManageAlerts ? undefined : alertActionDisabledReason}
            onClick={beginCreate}>
            <Icon name="plus" size={13} />New rule
          </Button>
        </div>
        <AlertsSummaryStrip rows={rows} />
        <AlertRulesList
          rows={rows}
          rulesLoading={alertsInitialLoading}
          deletingRuleId={deletingRuleId}
          canManageAlerts={canManageAlerts}
          alertActionDisabledReason={alertActionDisabledReason}
          onEdit={beginEdit}
          onDelete={(rule) => setConfirmDeleteRule(rule)}
          onToggleActive={toggleRuleActive}
          onCreate={beginCreate} />
      </div>

      {isEditorOpen && (
        <AlertRuleModal
          editingId={formEditingId}
          isNew={isNewRule}
          saving={saving}
          onClose={closeEditor}
          activeDestinations={VISIBLE_ALERT_DESTINATIONS}
          activeDestination={activeDestination}
          onSelectDestination={setActiveDestination}
          message={formMessage}
          slackPanel={slackPanel}
          emailPanel={emailPanel} />
      )}

      <ConfirmDialog
        open={Boolean(confirmDeleteRule)}
        tone="danger"
        title="Delete this alert rule?"
        description="The rule stops watching its target. Past delivery records are preserved."
        confirmLabel="Delete rule"
        confirmBusyLabel="Deleting"
        busy={deletingRuleId === confirmDeleteRule?.id}
        onCancel={() => setConfirmDeleteRule(null)}
        onConfirm={() => performRemove(confirmDeleteRule.id)} />
    </div>
  );
}

function AlertsDestinationsRow({ integrationByType, loading, slackConnecting, connectSlack, canManageAlerts, alertActionDisabledReason }) {
  return (
    <div className="alerts-destinations-row">
      {VISIBLE_ALERT_DESTINATIONS.map((destination) => (
        <DestinationCard
          key={destination.key}
          destination={destination}
          integration={integrationByType[destination.key]}
          loading={loading}
          slackConnecting={slackConnecting}
          connectSlack={connectSlack}
          canManageAlerts={canManageAlerts}
          alertActionDisabledReason={alertActionDisabledReason} />
      ))}
    </div>
  );
}

function DestinationCard({ destination, integration, loading, slackConnecting, connectSlack, canManageAlerts, alertActionDisabledReason }) {
  const connected = Boolean(integration);
  let statusText = '';
  let detail = '';
  let action = null;
  let dotOn = false;

  if (destination.key === 'slack') {
    if (loading && !integration) {
      statusText = 'Checking workspace…';
    } else if (connected) {
      dotOn = true;
      statusText = `Connected · ${integration.name}`;
      const installType = integration.metadata?.is_enterprise_install ? 'Enterprise install' : 'Workspace install';
      const installedAt = integration.metadata?.installed_at || integration.createdAt;
      detail = installedAt ? `${installType} · installed ${formatDateTime(installedAt)}` : installType;
      action = (
        <Button
          size="sm"
          variant="ghost"
          disabled={!canManageAlerts}
          loading={slackConnecting}
          loadingLabel="Opening Slack…"
          title={canManageAlerts ? undefined : alertActionDisabledReason}
          onClick={connectSlack}>
          <Icon name="refresh" size={11} />Reauthorize
        </Button>
      );
    } else {
      statusText = 'Not connected';
      detail = 'Required before creating Slack alert rules.';
      action = (
        <Button
          size="sm"
          variant="primary"
          disabled={!canManageAlerts}
          loading={slackConnecting}
          loadingLabel="Opening Slack…"
          title={canManageAlerts ? undefined : alertActionDisabledReason}
          onClick={connectSlack}>
          <SlackLogo size={12} />Connect Slack
        </Button>
      );
    }
  } else if (destination.key === 'email') {
    dotOn = true;
    statusText = 'Ready';
    detail = 'Recipient address is set on each alert rule.';
  }

  return (
    <div className={`alerts-destination-card ${connected ? 'connected' : ''}`}>
      <span className="alerts-destination-glyph" aria-hidden>
        <AlertDestinationGlyph destination={destination} size={18} />
      </span>
      <div className="alerts-destination-body">
        <div className="alerts-destination-title">{destination.label}</div>
        <div className="alerts-destination-status">
          <span className={`alerts-destination-dot ${dotOn ? 'on' : 'off'}`} aria-hidden></span>
          <span>{statusText}</span>
        </div>
        {detail && <div className="alerts-destination-detail">{detail}</div>}
      </div>
      {action && <div className="alerts-destination-action">{action}</div>}
    </div>
  );
}

// AlertRulesForTargetModal renders rule CRUD scoped to a single workflow or
// tool monitor. Rules are managed inline here (list ↔ form toggle inside a
// single dialog) so the user never leaves the originating Tool monitors /
// Workflows page to change alert behavior.
function AlertRulesForTargetModal({
  open,
  onClose,
  targetKind,
  targetId,
  targetName,
  alertsResource,
  navigate,
}) {
  const auth = useAuth();
  const toast = useToast();
  const canManageAlerts = canUseTenantAction(auth);
  const alertActionDisabledReason = tenantActionDisabledReason(auth);
  const allRows = alertsResource.data?.rows || [];
  const integrations = alertsResource.data?.integrations || [];
  const slackIntegrations = integrations.filter((integration) => integration.type === 'slack');
  const alertsInitialLoading = alertsResource.loading && !alertsResource.data;
  const rows = useMemoAlerts(
    () => allRows.filter((rule) => (
      targetKind === 'tool-monitor' ? rule.toolMonitorId === targetId : rule.workflowId === targetId
    )),
    [allRows, targetKind, targetId],
  );
  const [activeDestination, setActiveDestination] = useStateAlerts('slack');
  const [view, setView] = useStateAlerts('list'); // 'list' | 'form'
  const [editingId, setEditingId] = useStateAlerts('');
  const [name, setName] = useStateAlerts('');
  const [integrationId, setIntegrationId] = useStateAlerts('');
  const [channel, setChannel] = useStateAlerts('');
  const [channelMode, setChannelMode] = useStateAlerts('select');
  const [channels, setChannels] = useStateAlerts([]);
  const [channelsLoading, setChannelsLoading] = useStateAlerts(false);
  const [channelsError, setChannelsError] = useStateAlerts('');
  const [enabled, setEnabled] = useStateAlerts(true);
  const [scoreThreshold, setScoreThreshold] = useStateAlerts(3);
  const [triggerMode, setTriggerMode] = useStateAlerts('consecutive');
  const [consecutiveRequired, setConsecutiveRequired] = useStateAlerts(2);
  const [cooldownMinutes, setCooldownMinutes] = useStateAlerts(60);
  const [dailyCap, setDailyCap] = useStateAlerts(24);
  const [saving, setSaving] = useStateAlerts(false);
  const [message, setMessage] = useStateAlerts('');
  const [deletingRuleId, setDeletingRuleId] = useStateAlerts('');
  const [confirmDeleteRule, setConfirmDeleteRule] = useStateAlerts(null);

  function resetForm() {
    setEditingId('');
    setName('');
    setIntegrationId('');
    setChannel('');
    setChannelMode('select');
    setEnabled(true);
    setScoreThreshold(3);
    setTriggerMode('consecutive');
    setConsecutiveRequired(2);
    setCooldownMinutes(DEFAULT_ALERT_LIMIT_POLICY.cooldownMinutes);
    setDailyCap(DEFAULT_ALERT_LIMIT_POLICY.dailyCap);
  }

  function applyLimitPolicyDefaultsFromExistingRule() {
    const matching = rows[0];
    const policy = matching ? effectiveLimitPolicy(matching) : DEFAULT_ALERT_LIMIT_POLICY;
    setCooldownMinutes(policy.cooldownMinutes);
    setDailyCap(policy.dailyCap);
  }

  function beginCreate() {
    if (!requirePaidAction(auth, navigate, { requiredRole: 'editor' })) return;
    resetForm();
    setActiveDestination('slack');
    setEditingId('new');
    setMessage('');
    setView('form');
    applyLimitPolicyDefaultsFromExistingRule();
  }

  function beginEdit(rule) {
    if (!canManageAlerts) return;
    setActiveDestination(rule.target?.type || 'slack');
    setEditingId(rule.id);
    setName(rule.name || '');
    setIntegrationId(rule.target?.integrationId || '');
    setChannel(rule.target?.channel || '');
    setChannelMode(rule.target?.channel?.startsWith('#') ? 'manual' : 'select');
    setEnabled(Boolean(rule.isActive));
    setScoreThreshold(rule.scoreThreshold ?? 3);
    setTriggerMode(rule.triggerMode ?? 'consecutive');
    setConsecutiveRequired(rule.consecutiveRequired ?? 2);
    const policy = effectiveLimitPolicy(rule);
    setCooldownMinutes(policy.cooldownMinutes);
    setDailyCap(policy.dailyCap);
    setMessage('');
    setView('form');
  }

  function backToList() {
    setEditingId('');
    setMessage('');
    setView('list');
  }

  // When the targetKind toggle opens this modal for a fresh target, prefill the
  // Slack workspace if there's only one (matches the AlertsPage UX), and reset
  // the integration choice when the previously-picked workspace was removed.
  useEffectAlerts(() => {
    if (!open) return;
    if (activeDestination !== 'slack') return;
    if (slackIntegrations.length === 1 && !integrationId) {
      setIntegrationId(slackIntegrations[0].id);
      return;
    }
    if (integrationId && !slackIntegrations.some((integration) => integration.id === integrationId)) {
      setIntegrationId('');
      setChannel('');
    }
  }, [open, activeDestination, integrations, integrationId]);

  // Load Slack channels when an integration is picked. The fetch is scoped to
  // the modal's lifetime so closing the modal cancels in-flight requests.
  useEffectAlerts(() => {
    if (!open) return undefined;
    let canceled = false;
    async function loadChannels() {
      if (activeDestination !== 'slack' || !integrationId) {
        setChannels([]);
        setChannelsError('');
        setChannelsLoading(false);
        return;
      }
      setChannelsLoading(true);
      setChannelsError('');
      try {
        const data = await apiFetch(`/api/alerts/slack/channels?integrationId=${encodeURIComponent(integrationId)}`);
        if (canceled) return;
        setChannels(data.channels || []);
      } catch (error) {
        if (canceled) return;
        setChannels([]);
        setChannelsError(error.message || 'Could not load Slack channels');
      } finally {
        if (!canceled) setChannelsLoading(false);
      }
    }
    loadChannels();
    return () => { canceled = true; };
  }, [open, activeDestination, integrationId]);

  // Reset modal state on close so reopening it starts on the list view rather
  // than mid-edit. We deliberately don't reset on every prop change — only on
  // close transitions — so the parent can keep the modal mounted between rows
  // without flashing stale state.
  const wasOpenRef = useRefAlerts(false);
  useEffectAlerts(() => {
    if (wasOpenRef.current && !open) {
      resetForm();
      setMessage('');
      setView('list');
    }
    wasOpenRef.current = open;
  }, [open]);

  async function saveRule() {
    if (!requirePaidAction(auth, navigate, { requiredRole: 'editor' })) return;
    setSaving(true);
    setMessage('');
    try {
      const wasEditing = Boolean(editingId) && editingId !== 'new';
      const body = {
        name,
        ...(targetKind === 'tool-monitor' ? { toolMonitorId: targetId } : { workflowId: targetId }),
        isActive: enabled,
        scoreThreshold,
        triggerMode,
        consecutiveRequired,
        limitPolicy: {
          cooldownMinutes,
          dailyCap,
        },
        target: activeDestination === 'email'
          ? { type: 'email', channel }
          : { type: 'slack', integrationId: selectedSlackIntegrationId, channel },
      };
      const path = wasEditing ? `/api/alerts/${editingId}` : '/api/alerts';
      const method = wasEditing ? 'PATCH' : 'POST';
      await apiFetch(path, { method, body: JSON.stringify(body) });
      await alertsResource.reload();
      resetForm();
      setMessage(wasEditing ? 'Alert rule updated.' : 'Alert rule created.');
      setView('list');
    } catch (error) {
      setMessage(error.message);
    } finally {
      setSaving(false);
    }
  }

  async function performRemove(ruleId) {
    if (!requirePaidAction(auth, navigate, { requiredRole: 'editor' })) return;
    setDeletingRuleId(ruleId);
    try {
      await apiFetch(`/api/alerts/${ruleId}`, { method: 'DELETE' });
      if (editingId === ruleId) {
        resetForm();
        setView('list');
      }
      await alertsResource.reload();
      toast.show({ tone: 'ok', title: 'Alert rule deleted' });
    } catch (error) {
      toast.show({ tone: 'bad', title: 'Delete failed', description: error.message });
    } finally {
      setDeletingRuleId('');
      setConfirmDeleteRule(null);
    }
  }

  async function toggleRuleActive(rule) {
    if (!requirePaidAction(auth, navigate, { requiredRole: 'editor' })) return;
    try {
      await apiFetch(`/api/alerts/${rule.id}`, {
        method: 'PATCH',
        body: JSON.stringify({ isActive: !rule.isActive }),
      });
      await alertsResource.reload();
      toast.show({ tone: 'ok', title: rule.isActive ? 'Rule paused' : 'Rule resumed' });
    } catch (error) {
      toast.show({ tone: 'bad', title: 'Update failed', description: error.message });
    }
  }

  const integrationsReady = slackIntegrations.length > 0;
  const selectedChannelExists = channels.some((slackChannel) => slackChannel.id === channel);
  const selectedSlackIntegrationId = integrationId || (slackIntegrations.length === 1 ? slackIntegrations[0].id : '');
  const missingSlackRuleFields = [];
  if (!name.trim()) missingSlackRuleFields.push('Name');
  if (!selectedSlackIntegrationId) missingSlackRuleFields.push('Slack workspace');
  if (!channel.trim() || (channelMode === 'select' && !selectedChannelExists)) {
    missingSlackRuleFields.push('Destination');
  }
  const missingEmailRuleFields = [];
  if (!name.trim()) missingEmailRuleFields.push('Name');
  if (!EMAIL_TARGET_RE.test(channel.trim())) missingEmailRuleFields.push('Recipient email');
  const missingActiveRuleFields = activeDestination === 'email' ? missingEmailRuleFields : missingSlackRuleFields;
  const saveDisabledReason = saving
    ? 'Saving alert rule...'
    : !canManageAlerts
      ? alertActionDisabledReason
      : missingActiveRuleFields.length > 0
        ? `Missing fields: ${missingActiveRuleFields.join(', ')}`
        : '';
  const canSave = canManageAlerts
    && !saving
    && (
      activeDestination === 'email'
        ? name.trim() && EMAIL_TARGET_RE.test(channel.trim())
        : name.trim()
          && selectedSlackIntegrationId
          && channel.trim()
          && (channelMode === 'manual' || selectedChannelExists)
    );
  const formEditingId = editingId === 'new' ? '' : editingId;
  const isNewRule = editingId === 'new';

  useEffectAlerts(() => {
    function onKey(event) {
      if (!open) return;
      if (event.key !== 'Escape') return;
      if (saving) return;
      event.preventDefault();
      if (view === 'form' && rows.length > 0) {
        backToList();
      } else {
        onClose();
      }
    }
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [open, saving, view, rows.length, backToList, onClose]);

  if (!open) return null;

  const slackPanel = (
    <SlackAlertPanel
      integrations={slackIntegrations}
      integrationsLoading={alertsInitialLoading}
      integrationsReady={integrationsReady}
      connectSlack={() => navigate(SETTINGS_ALERTS_PATH)}
      slackConnecting={false}
      editingId={formEditingId}
      saving={saving}
      name={name}
      setName={setName}
      workflowId={targetKind === 'workflow' ? targetId : ''}
      integrationId={integrationId}
      setIntegrationId={setIntegrationId}
      channel={channel}
      setChannel={setChannel}
      channelMode={channelMode}
      setChannelMode={setChannelMode}
      channels={channels}
      channelsLoading={channelsLoading}
      channelsError={channelsError}
      selectedChannelExists={selectedChannelExists}
      enabled={enabled}
      setEnabled={setEnabled}
      scoreThreshold={scoreThreshold}
      setScoreThreshold={setScoreThreshold}
      triggerMode={triggerMode}
      setTriggerMode={setTriggerMode}
      consecutiveRequired={consecutiveRequired}
      setConsecutiveRequired={setConsecutiveRequired}
      cooldownMinutes={cooldownMinutes}
      setCooldownMinutes={setCooldownMinutes}
      dailyCap={dailyCap}
      setDailyCap={setDailyCap}
      canSave={canSave}
      saveDisabledReason={saveDisabledReason}
      saveRule={saveRule}
      resetForm={rows.length > 0 ? backToList : onClose}
      connectSlackLabel="Connect Slack in Settings" />
  );
  const emailPanel = (
    <EmailAlertPanel
      editingId={formEditingId}
      saving={saving}
      name={name}
      setName={setName}
      workflowId={targetKind === 'workflow' ? targetId : ''}
      email={channel}
      setEmail={setChannel}
      enabled={enabled}
      setEnabled={setEnabled}
      scoreThreshold={scoreThreshold}
      setScoreThreshold={setScoreThreshold}
      triggerMode={triggerMode}
      setTriggerMode={setTriggerMode}
      consecutiveRequired={consecutiveRequired}
      setConsecutiveRequired={setConsecutiveRequired}
      cooldownMinutes={cooldownMinutes}
      setCooldownMinutes={setCooldownMinutes}
      dailyCap={dailyCap}
      setDailyCap={setDailyCap}
      canSave={canSave}
      saveDisabledReason={saveDisabledReason}
      saveRule={saveRule}
      resetForm={rows.length > 0 ? backToList : onClose} />
  );

  const targetLabel = targetKind === 'tool-monitor' ? 'tool monitor' : 'workflow';
  const headerSubtitle = view === 'form'
    ? (isNewRule
      ? `Pick a destination, then save. Watches the ${targetLabel} "${targetName}".`
      : `Update where this rule fires for ${targetLabel} "${targetName}".`)
    : `Notifications for ${targetLabel} "${targetName}".`;

  const modal = (
    <div
      className="dialog-backdrop"
      onMouseDown={(event) => { if (event.target === event.currentTarget && !saving) onClose(); }}>
      <div
        className="modal-panel alert-rule-modal alert-rules-target-modal"
        role="dialog"
        aria-modal="true"
        aria-label={`Alerts for ${targetName}`}>
        <div className="modal-header">
          <div>
            <div className="modal-title">
              {view === 'form' ? (isNewRule ? 'New alert rule' : 'Edit alert rule') : 'Alerts'}
            </div>
            <div className="modal-subtitle">{headerSubtitle}</div>
          </div>
          <button className="icon-btn" type="button" aria-label="Close" onClick={onClose} disabled={saving}>
            <Icon name="x" size={15} />
          </button>
        </div>
        <div className="modal-body alert-rule-modal-body">
          {message && <div className="auth-message alerts-message">{message}</div>}
          {view === 'list' ? (
            <>
              <TargetModalChannelStatus
                integrations={integrations}
                navigate={navigate} />
              <AlertRulesList
                rows={rows}
                rulesLoading={alertsInitialLoading}
                deletingRuleId={deletingRuleId}
                canManageAlerts={canManageAlerts}
                alertActionDisabledReason={alertActionDisabledReason}
                onEdit={beginEdit}
                onDelete={(rule) => setConfirmDeleteRule(rule)}
                onToggleActive={toggleRuleActive}
                onCreate={beginCreate} />
              <div className="alert-form-actions">
                <Button onClick={onClose}>Close</Button>
                <Button
                  variant="primary"
                  disabled={!canManageAlerts}
                  title={canManageAlerts ? undefined : alertActionDisabledReason}
                  onClick={beginCreate}>
                  <Icon name="plus" size={13} />New alert rule
                </Button>
              </div>
            </>
          ) : (
            <>
              {isNewRule && (
                <RuleDestinationToggle
                  destinations={VISIBLE_ALERT_DESTINATIONS}
                  activeKey={activeDestination}
                  onSelect={setActiveDestination} />
              )}
              {activeDestination === 'slack' ? slackPanel : emailPanel}
            </>
          )}
        </div>
      </div>
      <ConfirmDialog
        open={Boolean(confirmDeleteRule)}
        tone="danger"
        title="Delete this alert rule?"
        description="The rule stops watching its target. Past delivery records are preserved."
        confirmLabel="Delete rule"
        confirmBusyLabel="Deleting"
        busy={deletingRuleId === confirmDeleteRule?.id}
        onCancel={() => setConfirmDeleteRule(null)}
        onConfirm={() => performRemove(confirmDeleteRule.id)} />
    </div>
  );
  if (typeof document !== 'undefined' && ReactDOM?.createPortal) {
    return ReactDOM.createPortal(modal, document.body);
  }
  return modal;
}

function TargetModalChannelStatus({ integrations, navigate }) {
  const slack = integrations.find((integration) => integration.type === 'slack' && integration.isActive !== false);
  return (
    <div className="alert-rules-target-channel-status">
      <div className="alert-rules-target-channel-row">
        <SlackLogo size={13} />
        <span className="text-xs">
          {slack ? `Slack · ${slack.name}` : 'Slack not connected'}
        </span>
      </div>
      <div className="alert-rules-target-channel-row">
        <Icon name="mail" size={13} />
        <span className="text-xs">Email · ready</span>
      </div>
      <button
        type="button"
        className="link text-xs alert-rules-target-channel-link"
        onClick={() => navigate(SETTINGS_ALERTS_PATH)}>
        Manage channels in Settings
      </button>
    </div>
  );
}

// AlertRulesButton is the inline chip used in Tool monitors and Workflows
// tables. Click → opens AlertRulesForTargetModal for the row's target. Empty
// state and counted-state styling matches what tool-monitors had previously,
// just lifted up so workflows can reuse it 1:1.
function AlertRulesButton({ rules, loading, onOpen, hideWhenEmpty = false }) {
  const activeCount = rules.filter((r) => r.isActive !== false).length;
  if (loading && rules.length === 0) return null;
  if (rules.length === 0) {
    if (hideWhenEmpty) return null;
    return (
      <button
        type="button"
        className="btn btn-sm tm-row-alert tm-row-alert-empty"
        onClick={onOpen}
        title="Create an alert rule for this target">
        <Icon name="bell" size={11} />
        <span className="tm-row-alert-label">Add alert rule</span>
      </button>
    );
  }
  return (
    <button
      type="button"
      className={`btn btn-sm tm-row-alert ${activeCount > 0 ? 'tm-row-alert-active' : ''}`}
      onClick={onOpen}
      title={`${rules.length} alert rule${rules.length === 1 ? '' : 's'} watching this target`}>
      <Icon name="bell" size={11} />
      <span className="tm-row-alert-label">
        {rules.length} alert rule{rules.length === 1 ? '' : 's'} set up
      </span>
    </button>
  );
}

// AlertRuleModal is the dialog chrome used by SettingsAlertsPage when creating
// or editing a rule. The target-scoped flow (AlertRulesForTargetModal) renders
// its own dialog inline; this one is portal-rendered so it can sit on top of
// other surfaces like the Settings panel.
function AlertRuleModal({
  editingId,
  isNew,
  saving,
  onClose,
  activeDestinations,
  activeDestination,
  onSelectDestination,
  message,
  slackPanel,
  emailPanel,
}) {
  useEffectAlerts(() => {
    function onKey(event) {
      if (event.key === 'Escape' && !saving) {
        event.preventDefault();
        onClose();
      }
    }
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose, saving]);
  const modal = (
    <div className="dialog-backdrop" onMouseDown={(event) => { if (event.target === event.currentTarget && !saving) onClose(); }}>
      <div className="modal-panel alert-rule-modal" role="dialog" aria-modal="true" aria-label={isNew ? 'New alert rule' : 'Edit alert rule'}>
        <div className="modal-header">
          <div>
            <div className="modal-title">{isNew ? 'New alert rule' : 'Edit alert rule'}</div>
            <div className="modal-subtitle">
              {isNew
                ? 'Pick a destination, choose what to watch, and save.'
                : 'Update where this rule fires and what it watches.'}
            </div>
          </div>
          <button className="icon-btn" type="button" aria-label="Close" onClick={onClose} disabled={saving}>
            <Icon name="x" size={15} />
          </button>
        </div>
        <div className="modal-body alert-rule-modal-body">
          {message && <div className="auth-message alerts-message">{message}</div>}
          {isNew && (
            <RuleDestinationToggle
              destinations={activeDestinations}
              activeKey={activeDestination}
              onSelect={onSelectDestination} />
          )}
          {activeDestination === 'slack' ? slackPanel : emailPanel}
        </div>
      </div>
    </div>
  );
  if (typeof document !== 'undefined' && ReactDOM?.createPortal) {
    return ReactDOM.createPortal(modal, document.body);
  }
  return modal;
}

// RuleTargetObjectField lets the user pick whether a rule watches a workflow
// or a tool monitor, then pick the specific row. Used only from
// SettingsAlertsPage — the per-target chip flows always know their target.
function RuleTargetObjectField({
  targetKind,
  setTargetKind,
  workflowId,
  setWorkflowId,
  workflowRows,
  workflowsLoading,
  toolMonitorId,
  setToolMonitorId,
  toolMonitorRows,
  toolMonitorsLoading,
  disabled,
}) {
  return (
    <>
      <AlertField label="Target object" help="What this rule watches.">
        <div className="alert-control">
          <Select
            value={targetKind}
            disabled={disabled}
            // setTargetKind here is the parent's updateTargetKind, which
            // already resets workflowId/toolMonitorId and reapplies the
            // limit-policy defaults — don't call the per-id wrappers again.
            onChange={setTargetKind}
            options={[
              { value: 'workflow', label: 'Workflow' },
              { value: 'tool-monitor', label: 'Tool monitor' },
            ]} />
        </div>
      </AlertField>
      {targetKind === 'workflow' ? (
        <AlertField label="Workflow" help="The workflow this rule watches.">
          <div className="alert-control">
            <Select
              value={workflowId}
              disabled={workflowsLoading && workflowRows.length === 0}
              onChange={setWorkflowId}
              options={[
                { value: '', label: workflowsLoading && workflowRows.length === 0 ? 'Loading workflows...' : 'Select a workflow...' },
                ...workflowRows.map((workflow) => ({ value: workflow.id, label: workflow.name })),
              ]} />
          </div>
        </AlertField>
      ) : (
        <AlertField label="Tool monitor" help="The tool monitor this rule watches.">
          <div className="alert-control">
            <Select
              value={toolMonitorId}
              disabled={toolMonitorsLoading}
              onChange={setToolMonitorId}
              options={[
                { value: '', label: toolMonitorsLoading ? 'Loading tool monitors...' : 'Select a tool monitor...' },
                ...toolMonitorRows.map((monitor) => ({
                  value: monitor.id,
                  label: `${monitor.tool_name}${monitor.server_name ? ` · ${monitor.server_name}` : ''}`,
                })),
              ]} />
            {!toolMonitorsLoading && toolMonitorRows.length === 0 && (
              <div className="field-help">No tool monitors yet. Create one in MCP servers › Tool monitors.</div>
            )}
          </div>
        </AlertField>
      )}
    </>
  );
}

function RuleDestinationToggle({ destinations, activeKey, onSelect }) {
  return (
    <div className="rule-destination-toggle" role="tablist" aria-label="Rule destination">
      {destinations.map((destination) => (
        <button
          key={destination.key}
          type="button"
          role="tab"
          aria-selected={activeKey === destination.key}
          className={`rule-destination-toggle-btn ${activeKey === destination.key ? 'active' : ''}`}
          onClick={() => onSelect(destination.key)}>
          <AlertDestinationGlyph destination={destination} size={13} />
          <span>{destination.label}</span>
        </button>
      ))}
    </div>
  );
}

function SlackAlertPanel({
  integrations,
  integrationsLoading,
  integrationsReady,
  connectSlack,
  slackConnecting,
  editingId,
  saving,
  name,
  setName,
  targetField = null,
  workflowId,
  integrationId,
  setIntegrationId,
  channel,
  setChannel,
  channelMode,
  setChannelMode,
  channels,
  channelsLoading,
  channelsError,
  selectedChannelExists,
  enabled,
  setEnabled,
  scoreThreshold,
  setScoreThreshold,
  triggerMode,
  setTriggerMode,
  consecutiveRequired,
  setConsecutiveRequired,
  cooldownMinutes,
  setCooldownMinutes,
  dailyCap,
  setDailyCap,
  canSave,
  saveDisabledReason,
  saveRule,
  resetForm,
  connectSlackLabel = 'Connect Slack',
}) {
  const singleIntegration = integrations.length === 1 ? integrations[0] : null;

  if (!editingId && !integrationsReady) {
    return (
      <div className="slack-connect-cta">
        <Button
          variant="primary"
          loading={integrationsLoading || slackConnecting}
          loadingLabel={integrationsLoading ? 'Checking Slack…' : 'Opening Slack…'}
          onClick={connectSlack}>
          <SlackLogo size={16} />{connectSlackLabel}
        </Button>
      </div>
    );
  }

  return (
    <>
      <div className="card">
        <div className="card-header">
          <div>
            <div className="card-title">{editingId ? 'Edit Slack alert rule' : 'Create Slack alert rule'}</div>
            <div className="card-sub">Rules fire when a workflow run fails, errors, or finishes with a non-passing evaluator verdict.</div>
          </div>
        </div>
        <div className="alert-rule-form">
          <AlertField label="Name" help="Short label shown in the alerts table and Slack notifications.">
            <div className="alert-control">
              <input className="input" placeholder="e.g. Production evaluator regression" value={name} onChange={(event) => setName(event.target.value)} />
            </div>
          </AlertField>
          {targetField}
          <AlertField label="Slack workspace" help="Workspace that owns the bot token for this delivery target.">
            <div className="alert-control">
              {singleIntegration ? (
                <div className="alert-static-value">
                  <span>{singleIntegration.name}</span>
                  <span className="mono muted text-xs">{singleIntegration.id}</span>
                </div>
              ) : (
                <Select
                  value={integrationId}
                  onChange={(value) => {
                    setIntegrationId(value);
                    setChannel('');
                    setChannelMode('select');
                  }}
                  disabled={integrationsLoading || !integrationsReady}
                  options={[
                    { value: '', label: integrationsLoading ? 'Loading Slack workspace...' : integrationsReady ? 'Select a Slack workspace...' : 'Connect Slack first...' },
                    ...integrations.map((integration) => ({ value: integration.id, label: integration.name })),
                  ]} />
              )}
            </div>
          </AlertField>
          <AlertField label="Destination" help="Select a public Slack channel, or enter a Slack channel/user ID manually.">
            <div className="alert-control">
              <div className="alert-destination-head">
                <button
                  className={`btn btn-sm ${channelMode === 'select' ? 'btn-primary' : ''}`}
                  type="button"
                  title="Select from public Slack channels"
                  onClick={() => setChannelMode('select')}>
                  Select channel
                </button>
                <button
                  className={`btn btn-sm ${channelMode === 'manual' ? 'btn-primary' : ''}`}
                  type="button"
                  title="Enter a Slack channel or user ID"
                  onClick={() => setChannelMode('manual')}>
                  Enter Slack ID
                </button>
              </div>
              {channelMode === 'select' ? (
                <>
                  <Select
                    value={selectedChannelExists ? channel : ''}
                    disabled={!integrationId || channelsLoading || channels.length === 0}
                    onChange={setChannel}
                    options={[
                      {
                        value: '',
                        label: !integrationId
                        ? 'Select a Slack workspace first...'
                        : channelsLoading
                          ? 'Loading channels...'
                          : channels.length === 0
                            ? 'No public channels found'
                            : 'Select a channel...',
                      },
                      ...channels.map((slackChannel) => ({ value: slackChannel.id, label: `#${slackChannel.name}` })),
                    ]} />
                  {channelsError && <div className="field-help error-text">{channelsError}</div>}
                  {channel && !selectedChannelExists && !channelsLoading && (
                    <div className="field-help">Saved destination is not in the public channel list. Switch to Enter Slack ID to keep or edit it.</div>
                  )}
                </>
              ) : (
                <input className="input mono" placeholder="Channel/user ID, e.g. C1234567890 or U1234567890" value={channel} onChange={(event) => setChannel(event.target.value)} />
              )}
            </div>
          </AlertField>
          <AlertPausedField enabled={enabled} setEnabled={setEnabled} />
          <AlertFiringControls
            workflowId={workflowId}
            scoreThreshold={scoreThreshold}
            setScoreThreshold={setScoreThreshold}
            triggerMode={triggerMode}
            setTriggerMode={setTriggerMode}
            consecutiveRequired={consecutiveRequired}
            setConsecutiveRequired={setConsecutiveRequired}
            cooldownMinutes={cooldownMinutes}
            setCooldownMinutes={setCooldownMinutes}
            dailyCap={dailyCap}
            setDailyCap={setDailyCap} />
          <div className="alert-form-actions">
            <Button disabled={saving} onClick={resetForm}>Cancel</Button>
            <span
              className={`alert-save-action ${!canSave ? 'is-disabled' : ''}`}
              data-tooltip={!canSave && saveDisabledReason ? saveDisabledReason : undefined}
              title={!canSave && saveDisabledReason ? saveDisabledReason : undefined}
              tabIndex={!canSave && saveDisabledReason ? 0 : undefined}
              aria-label={!canSave && saveDisabledReason ? saveDisabledReason : undefined}>
              <Button
                variant="primary"
                className="alert-save-button"
                disabled={!canSave}
                loading={saving}
                loadingLabel={editingId ? 'Saving...' : 'Creating...'}
                onClick={saveRule}>
                <Icon name={editingId ? 'check' : 'plus'} size={13} />
                {editingId ? 'Save Slack alert rule' : 'Create Slack alert rule'}
              </Button>
            </span>
          </div>
        </div>
      </div>
    </>
  );
}

function EmailAlertPanel({
  editingId,
  saving,
  name,
  setName,
  targetField = null,
  workflowId,
  email,
  setEmail,
  enabled,
  setEnabled,
  scoreThreshold,
  setScoreThreshold,
  triggerMode,
  setTriggerMode,
  consecutiveRequired,
  setConsecutiveRequired,
  cooldownMinutes,
  setCooldownMinutes,
  dailyCap,
  setDailyCap,
  canSave,
  saveDisabledReason,
  saveRule,
  resetForm,
}) {
  return (
    <div className="card">
      <div className="card-header">
        <div>
          <div className="card-title">{editingId ? 'Edit email alert rule' : 'Create email alert rule'}</div>
          <div className="card-sub">Rules fire when a workflow run fails, errors, or finishes with a non-passing evaluator verdict.</div>
        </div>
      </div>
      <div className="alert-rule-form">
        <AlertField label="Name" help="Short label shown in the alerts table and email subject context.">
          <div className="alert-control">
            <input className="input" placeholder="e.g. Production evaluator regression" value={name} onChange={(event) => setName(event.target.value)} />
          </div>
        </AlertField>
        {targetField}
        <AlertField label="Recipient email" help="Workflow alerts will be sent to this email address.">
          <div className="alert-control">
            <input
              className="input"
              type="email"
              inputMode="email"
              placeholder="oncall@example.com"
              value={email}
              onChange={(event) => setEmail(event.target.value)} />
          </div>
        </AlertField>
        <AlertPausedField enabled={enabled} setEnabled={setEnabled} />
        <AlertFiringControls
          workflowId={workflowId}
          scoreThreshold={scoreThreshold}
          setScoreThreshold={setScoreThreshold}
          triggerMode={triggerMode}
          setTriggerMode={setTriggerMode}
          consecutiveRequired={consecutiveRequired}
          setConsecutiveRequired={setConsecutiveRequired}
          cooldownMinutes={cooldownMinutes}
          setCooldownMinutes={setCooldownMinutes}
          dailyCap={dailyCap}
          setDailyCap={setDailyCap} />
        <div className="alert-form-actions">
          <Button disabled={saving} onClick={resetForm}>Cancel</Button>
          <span
            className={`alert-save-action ${!canSave ? 'is-disabled' : ''}`}
            data-tooltip={!canSave && saveDisabledReason ? saveDisabledReason : undefined}
            title={!canSave && saveDisabledReason ? saveDisabledReason : undefined}
            tabIndex={!canSave && saveDisabledReason ? 0 : undefined}
            aria-label={!canSave && saveDisabledReason ? saveDisabledReason : undefined}>
            <Button
              variant="primary"
              className="alert-save-button"
              disabled={!canSave}
              loading={saving}
              loadingLabel={editingId ? 'Saving...' : 'Creating...'}
              onClick={saveRule}>
              <Icon name={editingId ? 'check' : 'plus'} size={13} />
              {editingId ? 'Save email alert rule' : 'Create email alert rule'}
            </Button>
          </span>
        </div>
      </div>
    </div>
  );
}

function AlertRulesList({ rows, rulesLoading, deletingRuleId, canManageAlerts, alertActionDisabledReason, onEdit, onDelete, onToggleActive, onCreate }) {
  if (rulesLoading) {
    return (
      <div className="alerts-rule-list">
        {Array.from({ length: 2 }).map((_, index) => (
          <div key={index} className="alerts-rule-row loading-row">
            <span className="skel skel-badge"></span>
            <span className="skel skel-long" style={{ flex: 1 }}></span>
            <span className="skel skel-mid"></span>
            <span className="skel skel-short"></span>
          </div>
        ))}
      </div>
    );
  }

  if (rows.length === 0) {
    return (
      <div className="ui-surface alerts-empty">
        <EmptyState
          icon="bell"
          title="No alert rules yet"
          body="Create a rule to notify Slack or email when this target fails."
          action={(
            <Button
              variant="primary"
              disabled={!canManageAlerts}
              title={canManageAlerts ? undefined : alertActionDisabledReason}
              onClick={onCreate}>
              <Icon name="plus" size={13} />New rule
            </Button>
          )} />
      </div>
    );
  }

  return (
    <div className="alerts-rule-list ui-surface">
      {rows.map((rule, index) => (
        <RuleRow
          key={rule.id}
          rule={rule}
          deletingRuleId={deletingRuleId}
          canManageAlerts={canManageAlerts}
          alertActionDisabledReason={alertActionDisabledReason}
          onEdit={onEdit}
          onDelete={onDelete}
          onToggleActive={onToggleActive}
          isFirst={index === 0} />
      ))}
    </div>
  );
}

function RuleRow({ rule, deletingRuleId, canManageAlerts, alertActionDisabledReason, onEdit, onDelete, onToggleActive, isFirst }) {
  const status = describeRuleStatus(rule);
  const destinationKey = rule.target?.type || 'slack';
  const destination = ALERT_DESTINATIONS.find((d) => d.key === destinationKey) || ALERT_DESTINATIONS[0];
  const targetLabel = destinationKey === 'email'
    ? rule.target?.email || rule.target?.channel
    : `${rule.target?.integrationName || rule.target?.integrationId} · ${rule.target?.channel}`;
  const limitPolicy = effectiveLimitPolicy(rule);
  const busy = Boolean(deletingRuleId);
  const toneToStatusDot = status.tone === 'warn' ? 'bad' : status.tone === 'ok' ? 'ok' : 'neutral';

  return (
    <div className={`alerts-rule-row ui-hover-row ${isFirst ? 'alerts-rule-row-first' : ''} alerts-rule-row-${status.tone}`}>
      <span className="alerts-rule-status-dot">
        <StatusDot tone={toneToStatusDot} label={status.label} />
      </span>
      <div className="alerts-rule-id">
        <button
          type="button"
          className="alerts-rule-name"
          disabled={!canManageAlerts}
          title={canManageAlerts ? undefined : alertActionDisabledReason}
          onClick={() => onEdit(rule)}>
          {rule.name}
        </button>
        {rule.scoreThreshold != null && (
          <div className="muted text-xs">
            Fires at ≤ {rule.scoreThreshold}/5
            {rule.triggerMode === 'immediate' ? ' immediately' : ` after ${rule.consecutiveRequired ?? 2} consecutive`}
          </div>
        )}
        <div className="muted text-xs">
          Effective limit: {limitPolicy.cooldownMinutes}m cooldown · {limitPolicy.dailyCap}/day
        </div>
      </div>
      <span className="alerts-rule-target text-xs muted">
        <AlertDestinationGlyph destination={destination} size={12} />
        <span>{targetLabel}</span>
      </span>
      <span className={`alerts-rule-status text-xs tone-${status.tone}`}>{status.label}</span>
      <div className="alerts-rule-actions ui-hover-actions">
        <button
          type="button"
          className="btn btn-sm btn-ghost"
          disabled={!canManageAlerts || busy}
          onClick={() => onToggleActive(rule)}
          aria-label={rule.isActive ? 'Pause rule' : 'Resume rule'}
          title={canManageAlerts ? (rule.isActive ? 'Pause' : 'Resume') : alertActionDisabledReason}>
          <Icon name={rule.isActive ? 'pause' : 'play'} size={12} />
        </button>
        <button
          type="button"
          className="btn btn-sm btn-ghost"
          disabled={!canManageAlerts || busy}
          onClick={() => onEdit(rule)}
          aria-label="Edit rule"
          title={canManageAlerts ? 'Edit' : alertActionDisabledReason}>
          <Icon name="edit" size={12} />
        </button>
        <button
          type="button"
          className="btn btn-sm btn-ghost"
          disabled={!canManageAlerts || busy}
          onClick={() => onDelete(rule)}
          aria-label="Delete rule"
          title={canManageAlerts ? 'Delete' : alertActionDisabledReason}>
          {deletingRuleId === rule.id
            ? <span className="loading-spinner loading-spinner-sm" aria-hidden="true"></span>
            : <Icon name="trash" size={12} />}
        </button>
      </div>
      {rule.lastFire && <RuleLastFire rule={rule} />}
    </div>
  );
}

function RuleLastFire({ rule }) {
  const fire = rule.lastFire;
  const isMonitor = Boolean(rule.toolMonitorId);
  const runHref = isMonitor
    ? `/tool-monitors?monitor=${encodeURIComponent(rule.toolMonitorId || '')}`
    : (fire.workflowRunId ? `/runs/${fire.workflowRunId}` : '/runs');
  const navigateTo = (event) => {
    event.preventDefault();
    pushBrowserRoute(runHref);
  };
  const previewMessage = fire.errorMessage
    || fire.summary
    || (isMonitor ? 'Monitor failed' : 'Run did not pass success criteria');
  return (
    <div className="alert-rule-last-fire">
      <div className="alert-rule-last-fire-header">
        <Icon name="zap" size={11} />
        <span className="alert-rule-last-fire-when">Last fired {formatRelative(fire.firedAt)}</span>
      </div>
      <div className="alert-rule-last-fire-preview text-xs">{previewMessage}</div>
      <div className="alert-rule-last-fire-links text-xs">
        <a className="link" href={runHref} onClick={navigateTo}>
          {isMonitor ? 'Open monitor' : 'Open run'}
          <Icon name="chevronRight" size={10} />
        </a>
      </div>
    </div>
  );
}

function AlertFiringControls({
  workflowId,
  scoreThreshold,
  setScoreThreshold,
  triggerMode,
  setTriggerMode,
  consecutiveRequired,
  setConsecutiveRequired,
  cooldownMinutes,
  setCooldownMinutes,
  dailyCap,
  setDailyCap,
}) {
  const consecutiveLabel = `After ${consecutiveRequired} bad run${consecutiveRequired === 1 ? '' : 's'}`;

  return (
    <>
      <AlertField label="Firing behavior" help="When this workflow rule should create an alert.">
        <div className="alert-control alert-control-stack">
          <div className="alert-score-control">
            <span className="alert-control-title alert-label-with-help">
              Score threshold
              <InfoTooltip text="For LLM-as-judge workflows, alerts fire when the judge score is this value or lower." />
            </span>
            <div className="seg alert-score-options" role="group" aria-label="Score threshold">
              {[0, 1, 2, 3, 4, 5].map((score) => (
                <button
                  key={score}
                  type="button"
                  className={scoreThreshold === score ? 'active' : ''}
                  aria-pressed={scoreThreshold === score}
                  onClick={() => setScoreThreshold(score)}>
                  {score}
                </button>
              ))}
            </div>
            <span className="alert-control-value">≤ {scoreThreshold}/5</span>
          </div>

          <div className="alert-trigger-row">
            <span className="alert-inline-label">Trigger</span>
            <div className="seg alert-trigger-options" role="group" aria-label="Trigger">
              <button
                type="button"
                className={triggerMode === 'consecutive' ? 'active' : ''}
                aria-pressed={triggerMode === 'consecutive'}
                onClick={() => setTriggerMode('consecutive')}>
                {consecutiveLabel}
              </button>
              <button
                type="button"
                className={triggerMode === 'immediate' ? 'active' : ''}
                aria-pressed={triggerMode === 'immediate'}
                onClick={() => setTriggerMode('immediate')}>
                Every bad run
              </button>
            </div>
          </div>

          {triggerMode === 'consecutive' && (
            <label className="alert-number-field alert-run-count-field">
              <span>Consecutive runs</span>
              <input
                className="input alert-number-input"
                type="number"
                min="1"
                max="10"
                step="1"
                value={consecutiveRequired}
                onChange={(e) => setConsecutiveRequired(Math.max(1, Math.min(10, Number(e.target.value) || 1)))}
              />
            </label>
          )}

          <AlertFiringPreview
            workflowId={workflowId}
            scoreThreshold={scoreThreshold}
            triggerMode={triggerMode}
            consecutiveRequired={consecutiveRequired}
          />
        </div>
      </AlertField>

      <AlertField label="Target rate limits" help="Applies to all alerts for this workflow/monitor.">
        <div className="alert-control alert-limit-control">
          <div className="alert-limit-grid">
            <label className="alert-number-field">
              <span>
                Cooldown minutes
                <InfoTooltip text="Minimum time before this workflow or monitor can send another failure alert." />
              </span>
              <input
                className="input alert-number-input"
                type="number"
                min="1"
                max="1440"
                step="1"
                value={cooldownMinutes}
                onChange={(e) => setCooldownMinutes(clampInteger(e.target.value, 1, 1440, DEFAULT_ALERT_LIMIT_POLICY.cooldownMinutes))}
              />
            </label>
            <label className="alert-number-field">
              <span>
                Daily cap
                <InfoTooltip text="Maximum failure alert events this workflow or monitor can send in a rolling 24-hour window." />
              </span>
              <input
                className="input alert-number-input"
                type="number"
                min="1"
                max="100"
                step="1"
                value={dailyCap}
                onChange={(e) => setDailyCap(clampInteger(e.target.value, 1, 100, DEFAULT_ALERT_LIMIT_POLICY.dailyCap))}
              />
            </label>
          </div>
        </div>
      </AlertField>
    </>
  );
}

function AlertPausedField({ enabled, setEnabled }) {
  const paused = !enabled;
  return (
    <div className="field-row alert-pause-row">
      <div>
        <div className="field-label">
          <span className="alert-label-with-help">
            Paused
            <InfoTooltip text="Paused rules stay saved but do not send notifications." />
          </span>
        </div>
      </div>
      <div className="alert-control alert-pause-control">
        <button
          type="button"
          className="alert-pause-button"
          aria-label={paused ? 'Resume rule' : 'Pause rule'}
          aria-pressed={paused}
          onClick={() => setEnabled((current) => !current)}>
          <span className={`toggle ${paused ? 'on' : ''}`} aria-hidden="true"></span>
        </button>
      </div>
    </div>
  );
}

function InfoTooltip({ text }) {
  return (
    <span className="ui-tooltip alert-info-tip" data-tooltip={text} tabIndex={0} aria-label={text}>
      <Icon name="info" size={13} />
    </span>
  );
}

function AlertFiringPreview({ workflowId, scoreThreshold, triggerMode, consecutiveRequired }) {
  const [data, setData] = useStateAlerts(null);
  const [loading, setLoading] = useStateAlerts(false);

  useEffectAlerts(() => {
    if (!workflowId) { setData(null); return; }
    setLoading(true);
    const params = new URLSearchParams({
      workflowId,
      scoreThreshold: String(scoreThreshold),
      triggerMode,
      consecutiveRequired: String(consecutiveRequired),
    });
    let cancelled = false;
    apiFetch(`/api/dashboard/alert-firing-preview?${params}`)
      .then((response) => { if (!cancelled) setData(response); })
      .catch(() => { if (!cancelled) setData(null); })
      .finally(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, [workflowId, scoreThreshold, triggerMode, consecutiveRequired]);

  if (!workflowId || loading || !data || data.runsConsidered === 0) return null;

  return (
    <p className="alert-firing-preview muted text-xs">
      In the last {data.windowDays}d this rule would have fired <strong>{data.fires}</strong>{' '}
      time{data.fires === 1 ? '' : 's'} ({data.badRuns} bad run{data.badRuns === 1 ? '' : 's'} of {data.runsConsidered} scheduled).
    </p>
  );
}

function AlertField({ label, help, children }) {
  return (
    <div className="field-row">
      <div>
        <div className="field-label">{label}</div>
        {help && <div className="field-help">{help}</div>}
      </div>
      {children}
    </div>
  );
}

function SlackLogo({ size = 13 }) {
  return (
    <img
      className="slack-logo"
      src={SLACK_LOGO_SRC}
      alt=""
      aria-hidden="true"
      style={{ width: size, height: size }} />
  );
}

function AlertDestinationGlyph({ destination, size = 13 }) {
  if (destination.key === 'slack') {
    return <SlackLogo size={size} />;
  }
  return <Icon name={destination.icon} size={size} />;
}

window.SettingsAlertsPage = SettingsAlertsPage;
window.AlertRulesForTargetModal = AlertRulesForTargetModal;
window.AlertRulesButton = AlertRulesButton;
