/**
 * @file AlertsNotifications.tsx
 * @description Unified "Alerts" control center embedded in the Settings page
 * (replaces the standalone /alerts route). A segmented tab UI
 * combines three concerns that used to be split across a page and a panel:
 *   • Rules    - define what conditions trigger an alert
 *   • Channels - webhook targets that receive fired alerts (Slack/Discord/…)
 *   • Activity - the live fired-alert feed with acknowledge controls
 * Tab badges reflect live state (rule count, unacked alert count), and the feed
 * + counts refetch on alert_triggered / alert_updated WebSocket messages.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */
/* =============================================================================
 * MODULE_GUIDE — extended in-file reference (comments only; safe to read, never executed)
 * =============================================================================
 * **Purpose:** Dashboard module consumed by the React client, MCP tools, or desktop shell depending on deployment mode.
 *
 * ## Design constraints
 * - Local-first: no telemetry leaves the machine unless the user configures webhooks.
 * - Fail-safe hooks path on the server must never block Claude Code; UI mirrors that
 *   philosophy by degrading gracefully (empty states, stale badges, reconnect loops).
 * - Destructive flows stay behind explicit confirmation modals and server-side gates.
 * - Internationalization: user-visible strings belong in i18n JSON, not literals here.
 *
 * ## Remote data & SSH
 * Remote Data Sources let operators aggregate multiple machines. SSH entries describe
 * how to reach a peer dashboard; the global data scope (`dataScope.ts`) narrows every
 * scoped GET via `?sources=`. Health checks and import history surface in Settings.
 *
 * ## Internal dependencies
 * - `../lib/api`
 * - `../lib/eventBus`
 * - `./EmptyState`
 * - `./Skeleton`
 * - `./WebhookSettings`
 * - `./ConfirmModal`
 * - `./Checkbox`
 * - `./FieldHelp`
 * - `../lib/format`
 * - `../lib/types`
 *
 * ## Public surface
 * - `AlertsNotifications` — exported API; see TSDoc on the symbol for behavior.
 *
 * ## Testing pointers
 * - Prefer colocated `__tests__` with Vitest + Testing Library for UI.
 * - Server contract changes require `npm run test:server` and OpenAPI sync.
 * - MCP edits: `npm run mcp:typecheck` and `npm run mcp:build`.
 *
 * ## Related docs
 * - `ARCHITECTURE.md` — hooks → API → SQLite → WebSocket → UI pipeline.
 * - `docs/API.md` — REST reference.
 * - `.claude/skills/file-headers/` — mandatory `@author` header policy.
 * ============================================================================= */
/* -----------------------------------------------------------------------------
 * EXPORT CATALOG — quick index of symbols defined below (documentation only).
 * -----------------------------------------------------------------------------
 * **AlertsNotifications**
 *   Part of this module's public contract. Downstream imports should treat
 *   the signature and return type as stable unless release notes say otherwise.
 *   When behavior changes, update the `@file` overview and relevant tests.
 *
 * ----------------------------------------------------------------------------- */

import { useCallback, useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import {
  BellRing,
  BellOff,
  Check,
  CheckCheck,
  ChevronDown,
  ListChecks,
  Plus,
  RefreshCw,
  Trash2,
  Webhook,
  X,
} from "lucide-react";
import { api } from "../lib/api";
import { eventBus } from "../lib/eventBus";
import { EmptyState } from "./EmptyState";
import { Skeleton } from "./Skeleton";
import { WebhookSettings } from "./WebhookSettings";
import { ConfirmModal } from "./ConfirmModal";
import { Checkbox } from "./Checkbox";
import { FieldHelp } from "./FieldHelp";
import { timeAgo } from "../lib/format";
import type { AlertEvent, AlertRule, AlertRuleType, WSMessage } from "../lib/types";

const PAGE_SIZE = 25;

// Example values surfaced in the field-help tooltips so users know what to type.
// These are the Claude Code hook event types and common built-in tool names.
const EVENT_TYPE_EXAMPLES = [
  "PreToolUse",
  "PostToolUse",
  "Stop",
  "SubagentStop",
  "Notification",
  "SessionStart",
  "SessionEnd",
  "UserPromptSubmit",
];
const TOOL_NAME_EXAMPLES = [
  "Bash",
  "Read",
  "Edit",
  "Write",
  "Grep",
  "Glob",
  "Task",
  "WebFetch",
  "WebSearch",
  "TodoWrite",
];
const SUMMARY_EXAMPLES = ["error", "permission", "timeout", "rate limit", "denied"];

const RULE_TYPES: AlertRuleType[] = [
  "event_pattern",
  "inactivity",
  "status_duration",
  "token_threshold",
];

type TabKey = "rules" | "channels" | "activity";

interface RuleFormState {
  name: string;
  rule_type: AlertRuleType;
  event_type: string;
  tool_name: string;
  summary_contains: string;
  count: string;
  window_minutes: string;
  minutes: string;
  status: "working" | "waiting";
  total_tokens: string;
  cooldown_seconds: string;
}

const EMPTY_FORM: RuleFormState = {
  name: "",
  rule_type: "event_pattern",
  event_type: "",
  tool_name: "",
  summary_contains: "",
  count: "1",
  window_minutes: "5",
  minutes: "10",
  status: "working",
  total_tokens: "1000000",
  cooldown_seconds: "300",
};

function buildConfig(form: RuleFormState): AlertRule["config"] {
  switch (form.rule_type) {
    case "event_pattern": {
      const config: AlertRule["config"] = {};
      if (form.event_type.trim()) config.event_type = form.event_type.trim();
      if (form.tool_name.trim()) config.tool_name = form.tool_name.trim();
      if (form.summary_contains.trim()) config.summary_contains = form.summary_contains.trim();
      const count = parseInt(form.count, 10);
      config.count = Number.isFinite(count) && count > 0 ? count : 1;
      if (config.count > 1) {
        const window = parseFloat(form.window_minutes);
        config.window_minutes = Number.isFinite(window) && window > 0 ? window : 5;
      }
      return config;
    }
    case "inactivity":
      return { minutes: parseFloat(form.minutes) };
    case "status_duration":
      return { status: form.status, minutes: parseFloat(form.minutes) };
    case "token_threshold":
      return { total_tokens: parseInt(form.total_tokens, 10) };
  }
}

function describeRule(rule: AlertRule, t: (key: string, opts?: Record<string, unknown>) => string) {
  const c = rule.config;
  switch (rule.rule_type) {
    case "event_pattern": {
      const parts = [
        c.event_type && `event=${c.event_type}`,
        c.tool_name && `tool=${c.tool_name}`,
        c.summary_contains && `summary~"${c.summary_contains}"`,
      ].filter(Boolean);
      const base = parts.join(" · ");
      return (c.count ?? 1) > 1
        ? t("ruleDesc.eventPatternCount", {
            pattern: base,
            count: c.count,
            window: c.window_minutes,
          })
        : t("ruleDesc.eventPattern", { pattern: base });
    }
    case "inactivity":
      return t("ruleDesc.inactivity", { minutes: c.minutes });
    case "status_duration":
      return t("ruleDesc.statusDuration", { status: c.status, minutes: c.minutes });
    case "token_threshold":
      return t("ruleDesc.tokenThreshold", { tokens: (c.total_tokens ?? 0).toLocaleString() });
  }
}

export function AlertsNotifications() {
  const { t } = useTranslation("alerts");
  const { t: ts } = useTranslation("settings");

  const [tab, setTab] = useState<TabKey>("rules");

  // Rules
  const [rules, setRules] = useState<AlertRule[]>([]);
  const [loadingRules, setLoadingRules] = useState(true);
  const [formOpen, setFormOpen] = useState(false);
  const [form, setForm] = useState<RuleFormState>(EMPTY_FORM);
  const [formError, setFormError] = useState<string | null>(null);
  const [saving, setSaving] = useState(false);
  const [confirmRule, setConfirmRule] = useState<AlertRule | null>(null);

  // Feed
  const [alerts, setAlerts] = useState<AlertEvent[]>([]);
  const [total, setTotal] = useState(0);
  const [unacked, setUnacked] = useState(0);
  const [unackedOnly, setUnackedOnly] = useState(false);
  const [loadingAlerts, setLoadingAlerts] = useState(true);

  const loadRules = useCallback(async () => {
    setLoadingRules(true);
    try {
      const res = await api.alerts.rules.list();
      setRules(res.rules);
    } catch (err) {
      console.error("Failed to load alert rules:", err);
    } finally {
      setLoadingRules(false);
    }
  }, []);

  const loadAlerts = useCallback(async () => {
    setLoadingAlerts(true);
    try {
      const res = await api.alerts.list({
        unacked: unackedOnly || undefined,
        limit: PAGE_SIZE,
        offset: 0,
      });
      setAlerts(res.alerts);
      setTotal(res.total);
      setUnacked(res.unacked);
    } catch (err) {
      console.error("Failed to load alerts:", err);
    } finally {
      setLoadingAlerts(false);
    }
  }, [unackedOnly]);

  const loadMore = useCallback(async () => {
    try {
      const res = await api.alerts.list({
        unacked: unackedOnly || undefined,
        limit: PAGE_SIZE,
        offset: alerts.length,
      });
      setAlerts((prev) => [...prev, ...res.alerts]);
      setTotal(res.total);
      setUnacked(res.unacked);
    } catch (err) {
      console.error("Failed to load more alerts:", err);
    }
  }, [unackedOnly, alerts.length]);

  useEffect(() => {
    loadRules();
  }, [loadRules]);

  useEffect(() => {
    loadAlerts();
  }, [loadAlerts]);

  // Live updates: any fired/acked alert refreshes the feed + counts regardless
  // of which tab is open, so the Activity badge stays accurate.
  useEffect(() => {
    return eventBus.subscribe((msg: WSMessage) => {
      if (msg.type === "alert_triggered" || msg.type === "alert_updated") {
        loadAlerts();
      }
    });
  }, [loadAlerts]);

  const set = (patch: Partial<RuleFormState>) => setForm((prev) => ({ ...prev, ...patch }));

  const onCreateRule = async () => {
    if (saving) return;
    setSaving(true);
    setFormError(null);
    try {
      const cooldown = parseInt(form.cooldown_seconds, 10);
      await api.alerts.rules.create({
        name: form.name.trim(),
        rule_type: form.rule_type,
        config: buildConfig(form),
        cooldown_seconds: Number.isFinite(cooldown) && cooldown >= 0 ? cooldown : 300,
      });
      setForm(EMPTY_FORM);
      setFormOpen(false);
      loadRules();
    } catch (err) {
      setFormError(err instanceof Error ? err.message : String(err));
    } finally {
      setSaving(false);
    }
  };

  const onToggleRule = async (rule: AlertRule) => {
    try {
      await api.alerts.rules.update(rule.id, { enabled: !rule.enabled });
      loadRules();
    } catch (err) {
      console.error("Failed to toggle alert rule:", err);
    }
  };

  const onDeleteRule = async (rule: AlertRule) => {
    try {
      await api.alerts.rules.remove(rule.id);
      setConfirmRule(null);
      loadRules();
      loadAlerts();
    } catch (err) {
      console.error("Failed to delete alert rule:", err);
    }
  };

  const onAck = async (id: number) => {
    try {
      await api.alerts.ack(id);
      loadAlerts();
    } catch (err) {
      console.error("Failed to acknowledge alert:", err);
    }
  };

  const onAckAll = async () => {
    try {
      await api.alerts.ackAll();
      loadAlerts();
    } catch (err) {
      console.error("Failed to acknowledge alerts:", err);
    }
  };

  // Mirror the server-side validation so obviously invalid rules never make it
  // to a request.
  const minutesVal = parseFloat(form.minutes);
  const tokensVal = parseInt(form.total_tokens, 10);
  const countVal = parseInt(form.count, 10);
  const windowVal = parseFloat(form.window_minutes);
  const canSubmit =
    form.name.trim().length > 0 &&
    (form.rule_type !== "event_pattern" ||
      (Boolean(form.event_type.trim() || form.tool_name.trim() || form.summary_contains.trim()) &&
        Number.isFinite(countVal) &&
        countVal > 0 &&
        (countVal <= 1 || (Number.isFinite(windowVal) && windowVal > 0)))) &&
    ((form.rule_type !== "inactivity" && form.rule_type !== "status_duration") ||
      (Number.isFinite(minutesVal) && minutesVal > 0)) &&
    (form.rule_type !== "token_threshold" || (Number.isFinite(tokensVal) && tokensVal > 0));

  const TABS: { key: TabKey; label: string; icon: typeof ListChecks; badge?: number }[] = [
    {
      key: "rules",
      label: ts("alertsHub.tabRules"),
      icon: ListChecks,
      badge: rules.length || undefined,
    },
    { key: "channels", label: ts("alertsHub.tabChannels"), icon: Webhook },
    {
      key: "activity",
      label: ts("alertsHub.tabActivity"),
      icon: BellRing,
      badge: unacked || undefined,
    },
  ];

  return (
    <div className="space-y-4">
      {/* Segmented tab control */}
      <div className="inline-flex flex-wrap rounded-xl border border-border bg-surface-2 p-1 gap-1">
        {TABS.map((tb) => {
          const active = tab === tb.key;
          const Icon = tb.icon;
          return (
            <button
              key={tb.key}
              onClick={() => setTab(tb.key)}
              className={`inline-flex items-center gap-2 text-xs font-medium px-3.5 py-2 rounded-lg transition-colors ${
                active
                  ? "bg-surface-4 text-fg-primary shadow-sm"
                  : "text-fg-muted hover:text-fg-secondary hover:bg-surface-3"
              }`}
            >
              <Icon className="w-3.5 h-3.5" />
              {tb.label}
              {tb.badge != null && (
                <span
                  className={`text-[10px] font-semibold rounded-full px-1.5 min-w-[18px] text-center ${
                    tb.key === "activity"
                      ? "text-status-warning bg-status-warning/15"
                      : active
                        ? "text-accent bg-accent/15"
                        : "text-fg-secondary bg-surface-2"
                  }`}
                >
                  {tb.badge}
                </span>
              )}
            </button>
          );
        })}
      </div>

      {/* ── RULES ── */}
      {tab === "rules" && (
        <div className="card p-4">
          <div className="flex items-center justify-between gap-3 mb-3">
            <div>
              <h4 className="text-sm font-semibold text-fg-secondary">{t("rules.title")}</h4>
              <p className="text-xs text-fg-muted mt-0.5">{ts("alertsHub.rulesHint")}</p>
            </div>
            <button
              onClick={() => {
                setFormOpen((open) => !open);
                setFormError(null);
              }}
              className="btn-ghost border border-border inline-flex items-center gap-1.5 text-xs flex-shrink-0"
            >
              {formOpen ? <X className="w-3.5 h-3.5" /> : <Plus className="w-3.5 h-3.5" />}
              {formOpen ? t("rules.cancel") : t("rules.add")}
            </button>
          </div>

          {formOpen && (
            <div className="rounded-lg border border-border bg-surface-2 p-3 mb-3 space-y-3">
              <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
                <label className="block text-xs text-fg-secondary">
                  <span className="inline-flex items-center gap-1">
                    {t("rules.form.name")}
                    <FieldHelp description={t("rules.help.name")} />
                  </span>
                  <input
                    type="text"
                    value={form.name}
                    onChange={(e) => set({ name: e.target.value })}
                    placeholder={t("rules.form.namePlaceholder")}
                    className="input mt-1 w-full"
                  />
                </label>
                <label className="block text-xs text-fg-secondary">
                  <span className="inline-flex items-center gap-1">
                    {t("rules.form.type")}
                    <FieldHelp title={t("rules.form.type")} description={t("rules.help.type")} />
                  </span>
                  <div className="relative mt-1">
                    <select
                      value={form.rule_type}
                      onChange={(e) => set({ rule_type: e.target.value as AlertRuleType })}
                      className="input w-full appearance-none pr-8"
                    >
                      {RULE_TYPES.map((type) => (
                        <option key={type} value={type}>
                          {t(`ruleTypes.${type}`)}
                        </option>
                      ))}
                    </select>
                    <ChevronDown className="w-3.5 h-3.5 absolute right-2.5 top-1/2 -translate-y-1/2 text-fg-muted pointer-events-none" />
                  </div>
                </label>
              </div>

              <p className="text-[11px] text-fg-muted">{t(`ruleTypeHints.${form.rule_type}`)}</p>

              {form.rule_type === "event_pattern" && (
                <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
                  <label className="block text-xs text-fg-secondary">
                    <span className="inline-flex items-center gap-1">
                      {t("rules.form.eventType")}
                      <FieldHelp
                        title={t("rules.form.eventType")}
                        description={t("rules.help.eventType")}
                        examples={EVENT_TYPE_EXAMPLES}
                      />
                    </span>
                    <input
                      type="text"
                      value={form.event_type}
                      onChange={(e) => set({ event_type: e.target.value })}
                      placeholder="PostToolUse"
                      className="input mt-1 w-full"
                    />
                  </label>
                  <label className="block text-xs text-fg-secondary">
                    <span className="inline-flex items-center gap-1">
                      {t("rules.form.toolName")}
                      <FieldHelp
                        title={t("rules.form.toolName")}
                        description={t("rules.help.toolName")}
                        examples={TOOL_NAME_EXAMPLES}
                      />
                    </span>
                    <input
                      type="text"
                      value={form.tool_name}
                      onChange={(e) => set({ tool_name: e.target.value })}
                      placeholder="Bash"
                      className="input mt-1 w-full"
                    />
                  </label>
                  <label className="block text-xs text-fg-secondary">
                    <span className="inline-flex items-center gap-1">
                      {t("rules.form.summaryContains")}
                      <FieldHelp
                        title={t("rules.form.summaryContains")}
                        description={t("rules.help.summaryContains")}
                        examples={SUMMARY_EXAMPLES}
                      />
                    </span>
                    <input
                      type="text"
                      value={form.summary_contains}
                      onChange={(e) => set({ summary_contains: e.target.value })}
                      placeholder="error"
                      className="input mt-1 w-full"
                    />
                  </label>
                  <label className="block text-xs text-fg-secondary">
                    <span className="inline-flex items-center gap-1">
                      {t("rules.form.count")}
                      <FieldHelp description={t("rules.help.count")} />
                    </span>
                    <input
                      type="number"
                      min={1}
                      value={form.count}
                      onChange={(e) => set({ count: e.target.value })}
                      className="input mt-1 w-full"
                    />
                  </label>
                  {parseInt(form.count, 10) > 1 && (
                    <label className="block text-xs text-fg-secondary">
                      <span className="inline-flex items-center gap-1">
                        {t("rules.form.windowMinutes")}
                        <FieldHelp description={t("rules.help.window")} />
                      </span>
                      <input
                        type="number"
                        min={1}
                        value={form.window_minutes}
                        onChange={(e) => set({ window_minutes: e.target.value })}
                        className="input mt-1 w-full"
                      />
                    </label>
                  )}
                </div>
              )}

              {(form.rule_type === "inactivity" || form.rule_type === "status_duration") && (
                <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
                  {form.rule_type === "status_duration" && (
                    <label className="block text-xs text-fg-secondary">
                      <span className="inline-flex items-center gap-1">
                        {t("rules.form.agentStatus")}
                        <FieldHelp description={t("rules.help.status")} />
                      </span>
                      <div className="relative mt-1">
                        <select
                          value={form.status}
                          onChange={(e) => set({ status: e.target.value as "working" | "waiting" })}
                          className="input w-full appearance-none pr-8"
                        >
                          <option value="working">working</option>
                          <option value="waiting">waiting</option>
                        </select>
                        <ChevronDown className="w-3.5 h-3.5 absolute right-2.5 top-1/2 -translate-y-1/2 text-fg-muted pointer-events-none" />
                      </div>
                    </label>
                  )}
                  <label className="block text-xs text-fg-secondary">
                    <span className="inline-flex items-center gap-1">
                      {t("rules.form.minutes")}
                      <FieldHelp
                        description={t(
                          form.rule_type === "inactivity"
                            ? "rules.help.minutesInactivity"
                            : "rules.help.minutesStatus"
                        )}
                      />
                    </span>
                    <input
                      type="number"
                      min={1}
                      value={form.minutes}
                      onChange={(e) => set({ minutes: e.target.value })}
                      className="input mt-1 w-full"
                    />
                  </label>
                </div>
              )}

              {form.rule_type === "token_threshold" && (
                <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
                  <label className="block text-xs text-fg-secondary">
                    <span className="inline-flex items-center gap-1">
                      {t("rules.form.totalTokens")}
                      <FieldHelp description={t("rules.help.totalTokens")} />
                    </span>
                    <input
                      type="number"
                      min={1}
                      value={form.total_tokens}
                      onChange={(e) => set({ total_tokens: e.target.value })}
                      className="input mt-1 w-full"
                    />
                  </label>
                </div>
              )}

              <div className="flex flex-wrap items-end justify-between gap-3">
                <label className="block text-xs text-fg-secondary">
                  <span className="mb-1.5 flex items-center gap-1">
                    {t("rules.form.cooldown")}
                    <FieldHelp description={t("rules.help.cooldown")} />
                  </span>
                  <input
                    type="number"
                    min={0}
                    value={form.cooldown_seconds}
                    onChange={(e) => set({ cooldown_seconds: e.target.value })}
                    className="input w-40"
                  />
                </label>
                <button
                  onClick={onCreateRule}
                  disabled={!canSubmit || saving}
                  className="btn-primary inline-flex items-center gap-2 text-sm disabled:opacity-50 disabled:cursor-not-allowed"
                >
                  <Plus className="w-3.5 h-3.5" />
                  {saving ? t("rules.saving") : t("rules.create")}
                </button>
              </div>
              {formError && <p className="text-xs text-status-danger">{formError}</p>}
            </div>
          )}

          {loadingRules ? (
            <div className="space-y-2">
              <Skeleton className="h-12 w-full" />
              <Skeleton className="h-12 w-full" />
            </div>
          ) : rules.length === 0 ? (
            <EmptyState
              icon={ListChecks}
              title={t("rules.empty")}
              description={ts("alertsHub.rulesEmptyHint")}
            />
          ) : (
            <ul className="space-y-2">
              {rules.map((rule) => (
                <li
                  key={rule.id}
                  className="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border bg-surface-2 px-3 py-2.5"
                >
                  <div className="min-w-0">
                    <div className="flex items-center gap-2">
                      <span
                        className={`text-sm font-medium truncate ${rule.enabled ? "text-fg-secondary" : "text-fg-muted line-through"}`}
                      >
                        {rule.name}
                      </span>
                      <span className="text-[10px] font-semibold uppercase tracking-wider text-accent bg-accent/10 border border-accent/20 rounded px-1.5 py-0.5 flex-shrink-0">
                        {t(`ruleTypes.${rule.rule_type}`)}
                      </span>
                    </div>
                    <p className="text-xs text-fg-muted mt-0.5 truncate">
                      {describeRule(rule, t)} ·{" "}
                      {t("rules.cooldown", { seconds: rule.cooldown_seconds })}
                    </p>
                  </div>
                  <div className="flex items-center gap-1.5 flex-shrink-0">
                    <button
                      onClick={() => onToggleRule(rule)}
                      className={`text-xs px-2.5 py-1.5 rounded-md border transition-colors ${
                        rule.enabled
                          ? "border-status-success/30 text-status-success hover:bg-status-success/10"
                          : "border-border text-fg-muted hover:text-fg-secondary hover:bg-surface-3"
                      }`}
                      title={rule.enabled ? t("rules.disable") : t("rules.enable")}
                    >
                      {rule.enabled ? t("rules.enabled") : t("rules.disabled")}
                    </button>
                    <button
                      onClick={() => setConfirmRule(rule)}
                      className="p-1.5 rounded-md text-fg-muted hover:text-status-danger hover:bg-status-danger/10 transition-colors"
                      title={t("rules.delete")}
                      aria-label={t("rules.delete")}
                    >
                      <Trash2 className="w-3.5 h-3.5" />
                    </button>
                  </div>
                </li>
              ))}
            </ul>
          )}
        </div>
      )}

      {/* ── CHANNELS (webhooks) ── */}
      {tab === "channels" && <WebhookSettings />}

      {/* ── ACTIVITY (fired-alert feed) ── */}
      {tab === "activity" && (
        <div className="card p-4">
          <div className="flex flex-wrap items-center justify-between gap-3 mb-3">
            <h4 className="text-sm font-semibold text-fg-secondary">
              {t("feed.title")}
              {unacked > 0 && (
                <span className="ml-2 text-[10px] font-semibold text-status-warning bg-status-warning/10 border border-status-warning/30 rounded-full px-2 py-0.5">
                  {t("feed.unackedCount", { count: unacked })}
                </span>
              )}
            </h4>
            <div className="flex items-center gap-2">
              <Checkbox
                checked={unackedOnly}
                onChange={setUnackedOnly}
                label={t("feed.unackedOnly")}
              />
              <button
                onClick={() => loadAlerts()}
                className="btn-ghost border border-border inline-flex items-center gap-1.5 text-xs"
                title={t("refresh")}
              >
                <RefreshCw className="w-3.5 h-3.5" />
              </button>
              {unacked > 0 && (
                <button
                  onClick={onAckAll}
                  className="btn-primary inline-flex items-center gap-1.5 text-xs"
                >
                  <CheckCheck className="w-3.5 h-3.5" />
                  {t("ackAll", { count: unacked })}
                </button>
              )}
            </div>
          </div>

          {loadingAlerts && alerts.length === 0 ? (
            <div className="space-y-2">
              <Skeleton className="h-14 w-full" />
              <Skeleton className="h-14 w-full" />
              <Skeleton className="h-14 w-full" />
            </div>
          ) : alerts.length === 0 ? (
            <EmptyState
              icon={unackedOnly ? BellOff : BellRing}
              title={t("feed.emptyTitle")}
              description={unackedOnly ? t("feed.emptyUnacked") : t("feed.emptyDescription")}
            />
          ) : (
            <>
              <ul className="space-y-2">
                {alerts.map((alert) => (
                  <li
                    key={alert.id}
                    className={`flex flex-wrap items-center justify-between gap-3 rounded-lg border px-3 py-2.5 ${
                      alert.acknowledged_at
                        ? "border-border bg-surface-2 opacity-70"
                        : "border-status-warning/30 bg-status-warning/5"
                    }`}
                  >
                    <div className="min-w-0">
                      <div className="flex items-center gap-2">
                        <BellRing
                          className={`w-3.5 h-3.5 flex-shrink-0 ${alert.acknowledged_at ? "text-fg-muted" : "text-status-warning"}`}
                        />
                        <span className="text-sm text-fg-secondary truncate">{alert.message}</span>
                      </div>
                      <p className="text-[11px] text-fg-muted mt-0.5 font-mono">
                        {timeAgo(alert.triggered_at)} · {alert.rule_name}
                        {alert.session_id && (
                          <>
                            {" · "}
                            <Link
                              to={`/sessions/${encodeURIComponent(alert.session_id)}`}
                              className="text-accent hover:underline"
                            >
                              {t("feed.viewSession")}
                            </Link>
                          </>
                        )}
                      </p>
                    </div>
                    {!alert.acknowledged_at && (
                      <button
                        onClick={() => onAck(alert.id)}
                        className="inline-flex items-center gap-1.5 text-xs px-2.5 py-1.5 rounded-md border border-border text-fg-secondary hover:text-fg-primary hover:bg-surface-3 transition-colors flex-shrink-0"
                      >
                        <Check className="w-3.5 h-3.5" />
                        {t("feed.ack")}
                      </button>
                    )}
                  </li>
                ))}
              </ul>

              {alerts.length < total && (
                <div className="flex justify-center mt-4">
                  <button onClick={loadMore} className="btn-ghost border border-border text-sm">
                    {t("feed.loadMore", { shown: alerts.length, total })}
                  </button>
                </div>
              )}
            </>
          )}
        </div>
      )}

      <ConfirmModal
        open={!!confirmRule}
        title={t("rules.deleteTitle", "Delete alert rule?")}
        message={confirmRule ? t("rules.confirmDelete", { name: confirmRule.name }) : ""}
        confirmLabel={t("rules.delete")}
        cancelLabel={t("rules.cancel")}
        onCancel={() => setConfirmRule(null)}
        onConfirm={() => confirmRule && onDeleteRule(confirmRule)}
      />
    </div>
  );
}
