import { IconAlarm, IconBell, IconChevronRight, IconClose, IconFilter, IconPlus, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
import { Button, Card, CardGroup, Collapse, Descriptions, Empty, Input, Radio, Select, Spin, Table, Tag, TextArea, Timeline, Typography } from '@douyinfe/semi-ui';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { FormEvent, memo, useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import { api } from '../../api/client';
import type { AlertEvent, AlertQuery, AlertRule, AlertRuleInput, AlertStatus, MetricDefinition, Page } from '../../api/types';
import { actionLabels, alertDeltaText, alertValue, canAct, formatAlertTime, operatorLabels, ruleCondition, severityLabels, statusLabels, thresholdText } from '../domain/alert';
import { InlineError, PanelEmpty, PanelLoading } from '../shared/AsyncState';
import { MobileFilterToggle } from '../shared/MobileFilterToggle';
import { MobileFilterSheet, MobileFilterSheetSection } from '../shared/MobileFilterSheet';
import { ProtocolTag } from '../shared/ProtocolTag';
import { SegmentedTabs } from '../shared/SegmentedTabs';
import { TablePagination } from '../shared/TablePagination';
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
import { WorkspaceDetailSideSheet } from '../shared/WorkspaceDetailSideSheet';
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail';
import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
import { detailTriggerRow } from '../shared/detailTriggerRow';
import { usePlatformSession } from '../auth/AuthGate';
import { canAdminister, canOperate } from '../auth/session';
import { QUERY_MEMORY, queryScopeKey, retainPreviousPageWithinScope } from '../queryPolicy';
import { useMobileLayout } from '../hooks/useMobileLayout';

type Tab = 'events' | 'rules' | 'notifications';
type Filters = { keyword: string; severity: string; status: string; ruleId: string; protocol: string; dateFrom: string; dateTo: string };
const EMPTY_FILTERS: Filters = { keyword: '', severity: '', status: '', ruleId: '', protocol: '', dateFrom: '', dateTo: '' };
const PROTOCOLS = ['GB32960', 'JT808', 'YUTONG_MQTT'];
const NUMERIC_OPERATORS = ['gt', 'gte', 'lt', 'lte', 'eq', 'neq', 'between', 'outside'];
const BOOLEAN_OPERATORS = ['eq', 'neq', 'changed'];
type RuleTemplate = { id: string; title: string; summary: string; draft: AlertRuleInput };

const RULE_TEMPLATES: RuleTemplate[] = [
  {
    id: 'offline-10h',
    title: '离线超过 10 小时',
    summary: '全部协议 · 自动恢复 · 每小时最多提醒一次',
    draft: {
      id: '', name: '车辆离线超过 10 小时', description: '车辆任一有效来源持续 10 小时未上报时告警。',
      severity: 'major', valueType: 'numeric', metric: 'freshness_sec', operator: 'gt', threshold: 36_000, thresholdHigh: 0,
      durationSec: 0, recoveryOperator: 'lte', recoveryThreshold: 300, repeatIntervalSec: 3_600,
      scopeProtocols: [...PROTOCOLS], scopeVins: [], scopeOems: [], scopeModels: [], scopeCompanies: [],
      notificationChannels: ['in_app'], enabled: true, version: 0
    }
  },
  {
    id: 'hydrogen-concentration',
    title: '最高氢浓度',
    summary: 'GB32960 · 阈值由车型或厂家标准确定',
    draft: {
      id: '', name: '最高氢浓度超限', description: '监控 GB32960 燃料电池系统最高氢浓度；保存前须按车型、厂家和安全规范确认百分比阈值。',
      severity: 'critical', valueType: 'numeric', metric: 'hydrogen_concentration_percent', operator: 'gt', threshold: Number.NaN, thresholdHigh: 0,
      durationSec: 0, recoveryOperator: '', recoveryThreshold: 0, repeatIntervalSec: 600,
      scopeProtocols: ['GB32960'], scopeVins: [], scopeOems: [], scopeModels: [], scopeCompanies: [],
      notificationChannels: ['in_app'], enabled: true, version: 0
    }
  }
];

function SeverityTag({ severity }: Pick<AlertEvent, 'severity'>) {
  const color = severity === 'critical' ? 'red' : severity === 'major' ? 'orange' : 'amber';
  return <Tag className={`v2-alert-severity is-${severity}`} color={color} type="light" size="small"><i />{severityLabels[severity]}</Tag>;
}
function StatusTag({ status }: Pick<AlertEvent, 'status'>) {
  return <Tag className={`v2-alert-status is-${status}`} color={alertStatusColor(status)} type="light" size="small">{statusLabels[status]}</Tag>;
}

function alertStatusColor(status: AlertStatus) {
  return status === 'unprocessed' ? 'red' as const : status === 'processing' ? 'blue' as const : status === 'recovered' ? 'green' as const : 'grey' as const;
}

function AlertTime({ value, className = '' }: { value?: string; className?: string }) {
  const classes = ['v2-alert-time', className].filter(Boolean).join(' ');
  if (!value) return <span className={classes}>—</span>;
  return <time className={classes} dateTime={value} title={`${value} · 上海时间`}>{formatAlertTime(value)}</time>;
}

const AlertEventTable = memo(function AlertEventTable({ rows, selectedID, compact, onSelect }: { rows: AlertEvent[]; selectedID: string; compact: boolean; onSelect: (id: string) => void }) {
  const columns = useMemo(() => {
    const allColumns = [
    {
      title: '', dataIndex: 'selection', width: 42,
      render: (_: unknown, event: AlertEvent) => <Radio name="alert-event" checked={selectedID === event.id} onChange={() => onSelect(event.id)} aria-label={`选择 ${event.plate || event.vin} ${event.ruleName}`} />
    },
    { title: '严重程度', dataIndex: 'severity', width: 90, render: (_: AlertEvent['severity'], event: AlertEvent) => <SeverityTag severity={event.severity} /> },
    {
      title: '车牌 / VIN', dataIndex: 'plate', width: 142,
      render: (_: string, event: AlertEvent) => <div className="v2-alert-event-vehicle"><strong>{event.plate || '未绑定车牌'}</strong><small>{event.vin}</small></div>
    },
    { title: '规则', dataIndex: 'ruleName', width: 170, render: (value: string) => <span className="v2-alert-event-rule" title={value}>{value}</span> },
    { title: '协议', dataIndex: 'protocol', width: 112, render: (value: string) => <ProtocolTag className="v2-alert-protocol-tag" protocol={value} compact /> },
    { title: '触发时间', dataIndex: 'triggeredAt', width: 132, render: (value: string) => <AlertTime value={value} /> },
    { title: '状态', dataIndex: 'status', width: 90, render: (_: AlertEvent['status'], event: AlertEvent) => <StatusTag status={event.status} /> },
    {
      title: '触发 / 阈值', dataIndex: 'evidence', width: 164,
      render: (_: unknown, event: AlertEvent) => <div className="v2-alert-event-evidence"><strong>{alertValue(event)}</strong><small>{thresholdText(event)}</small></div>
    },
    { title: '位置', dataIndex: 'location', width: 180, render: (value: string) => <span className="v2-alert-event-location" title={value}>{value || '—'}</span> },
    ];
    return compact ? allColumns.filter((column) => column.dataIndex !== 'evidence' && column.dataIndex !== 'location') : allColumns;
  }, [compact, onSelect, selectedID]);

  return <Table
    className={`v2-alert-event-table${compact ? ' is-compact' : ''}`}
    columns={columns}
    dataSource={rows}
    rowKey="id"
    pagination={false}
    empty={null}
    onRow={(event) => event ? detailTriggerRow({
      className: selectedID === event.id ? 'is-selected' : '',
      expanded: selectedID === event.id,
      label: `查看 ${event.plate || event.vin} ${event.ruleName} 告警详情`,
      testId: `alert-row-${event.id}`,
      onOpen: () => onSelect(event.id)
    }) : ({})}
  />;
});

function EventInspector({ event, note, acting, actionError, editable, onNote, onAction, onClose, sheet = false }: { event?: AlertEvent; note: string; acting: boolean; actionError?: string; editable: boolean; onNote: (value: string) => void; onAction: (action: 'acknowledge' | 'close' | 'ignore') => void; onClose: () => void; sheet?: boolean }) {
  if (!event) return <Card className="v2-alert-inspector" bodyStyle={{ padding: 0 }}><Empty className="v2-alert-inspector-empty" image={<IconAlarm />} title="选择告警事件" description="查看触发证据、状态时间线和处置动作。" /></Card>;
  const actionCount = event.actions?.length ?? 0;
  return <Card className={`v2-alert-inspector${sheet ? ' is-sheet' : ''}`} bodyStyle={{ padding: 0 }}>
    {!sheet ? <WorkspacePanelHeader className="v2-alert-inspector-heading" title="事件处置" description={event.ruleName} actions={<><span className="v2-alert-inspector-status"><SeverityTag severity={event.severity} /></span><Button className="v2-alert-inspector-close" theme="borderless" icon={<IconClose />} aria-label="关闭告警详情" onClick={onClose} /></>} /> : null}
    <div className="v2-alert-inspector-content">
      <Card className="v2-alert-detail-card v2-alert-focus-card" bodyStyle={{ padding: 0 }} aria-label="告警处置摘要">
        <header className="v2-alert-focus-identity">
          <span><small>告警对象</small><strong>{event.plate || '未绑定车牌'}</strong><code>{event.vin}</code></span>
          <span className="v2-alert-focus-current"><small>当前状态</small><StatusTag status={event.status} /></span>
        </header>
        <div className="v2-alert-focus-facts" aria-label="告警事件上下文">
          <span><small>数据来源</small><strong><ProtocolTag className="v2-alert-protocol-tag" protocol={event.protocol} compact unknownLabel="未知协议" /></strong></span>
          <span><small>触发时间</small><strong><AlertTime value={event.triggeredAt} /></strong></span>
          <span><small>处理记录</small><strong>{actionCount} 条</strong></span>
        </div>
        <div className="v2-alert-evidence" aria-label="告警触发证据">
          <Card className="v2-alert-evidence-value is-trigger"><small>触发值</small><strong>{alertValue(event)}</strong></Card>
          <Card className="v2-alert-evidence-value"><small>阈值条件</small><strong>{thresholdText(event)}</strong></Card>
          <Card className="v2-alert-evidence-value is-delta"><small>超限幅度</small><strong>{alertDeltaText(event)}</strong></Card>
        </div>
        <div className="v2-alert-focus-rule"><Tag color="grey" type="light" size="small">规则判断</Tag><span>{event.ruleName}</span></div>
      </Card>
      <Card className="v2-alert-detail-card v2-alert-disposition-card" title={<span className="v2-alert-detail-title"><strong>处置与备注</strong><Tag color={editable ? 'green' : 'grey'} type="light" size="small">{editable ? '可操作' : '只读'}</Tag></span>} headerLine aria-label="告警处置操作">
        {editable ? <><TextArea maxCount={200} autosize={{ minRows: 2, maxRows: 5 }} placeholder="请输入处置说明（选填）" value={note} onChange={onNote} />{actionError ? <p className="v2-alert-action-error">{actionError}</p> : null}<div className="v2-alert-actions"><Button theme="solid" className="is-primary" disabled={acting || !canAct(event.status, 'acknowledge')} onClick={() => onAction('acknowledge')}>确认告警</Button><Button theme="light" disabled={acting || !canAct(event.status, 'close')} onClick={() => onAction('close')}>关闭</Button><Button theme="borderless" disabled={acting || !canAct(event.status, 'ignore')} onClick={() => onAction('ignore')}>忽略</Button></div></> : <p className="v2-role-notice">当前为只读角色，可查看完整证据与处置记录。</p>}
      </Card>
      <Card className="v2-alert-detail-card v2-alert-progress-card" title={<span className="v2-alert-detail-title"><strong>处理进度</strong><Tag color={actionCount ? 'blue' : 'amber'} type="light" size="small">{actionCount ? `${actionCount} 条记录` : '待处置'}</Tag></span>} headerLine>
        {event.actions?.length ? <Timeline className="v2-alert-timeline" aria-label="告警处理进度">{event.actions.map((item, index) => <Timeline.Item key={item.id} type={index === event.actions!.length - 1 ? 'ongoing' : 'default'} time={<span>{item.actor} · <AlertTime value={item.createdAt} /></span>}><strong>{actionLabels[item.action] ?? item.action}</strong>{item.note ? <p>{item.note}</p> : null}</Timeline.Item>)}</Timeline> : <Empty className="v2-alert-timeline-empty" title="暂无处理记录" description="事件被确认、关闭或忽略后，将在这里形成审计履历。" />}
      </Card>
      <Collapse className="v2-alert-technical-collapse" keepDOM>
        <Collapse.Panel
          itemKey="technical-evidence"
          header={<span className="v2-alert-technical-heading"><strong>技术详情</strong><small>事件标识、规则版本与接收时间</small></span>}
        >
          <Descriptions className="v2-alert-descriptions" align="left" size="small" data={[
            { key: '事件 ID', value: <code>{event.id}</code> },
            { key: '来源事件 ID', value: event.sourceEventId || '—' },
            { key: '规则 / 版本', value: `${event.ruleId} / v${event.ruleVersion}` },
            { key: '事件 / 接收', value: <><AlertTime value={event.eventAt} /> / <AlertTime value={event.receivedAt} /></> },
            { key: '恢复时间', value: <AlertTime value={event.recoveredAt} /> },
            { key: '处理人', value: event.handler || '—' }
          ]} />
        </Collapse.Panel>
      </Collapse>
    </div>
    <nav className="v2-alert-links"><Link to={`/vehicles/${encodeURIComponent(event.vin)}`}>查看车辆</Link><Link to={`/tracks?vin=${encodeURIComponent(event.vin)}`}>查看轨迹</Link><Link to={`/history?vin=${encodeURIComponent(event.vin)}`}>历史数据</Link></nav>
  </Card>;
}

function EventWorkspace({ filters, draft, setDraft, setFilters, rules, unread, editable, onTab }: { filters: Filters; draft: Filters; setDraft: (next: Filters) => void; setFilters: (next: Filters) => void; rules: AlertRule[]; unread: number; editable: boolean; onTab: (tab: Tab) => void }) {
  const [offset, setOffset] = useState(0);
  const [limit, setLimit] = useState(20);
  const [selection, setSelection] = useState<{ scope: string; id: string }>();
  const [note, setNote] = useState('');
  const [filtersCollapsed, setFiltersCollapsed] = useState(true);
  const [advancedOpen, setAdvancedOpen] = useState(false);
  const queryClient = useQueryClient();
  const mobileLayout = useMobileLayout();
  const query: AlertQuery = useMemo(() => ({ ...Object.fromEntries(Object.entries(filters).filter(([, value]) => value)), limit, offset }), [filters, limit, offset]);
  const baseQuery = useMemo(() => ({ ...query, limit: undefined, offset: undefined }), [query]);
  const summaryQuery = useMemo(() => {
    const scope = { ...baseQuery };
    delete scope.status;
    return scope;
  }, [baseQuery]);
  const eventScope = useMemo(() => queryScopeKey(baseQuery), [baseQuery]);
  const summary = useQuery({ queryKey: ['alert-summary-v2', summaryQuery], queryFn: ({ signal }) => api.alertSummaryV2(summaryQuery, signal), staleTime: 8_000, gcTime: QUERY_MEMORY.summaryGcTime });
  const events = useQuery<Page<AlertEvent>>({ queryKey: ['alert-events-v2', eventScope, limit, offset], queryFn: ({ signal }) => api.alertEventsV2(query, signal), placeholderData: retainPreviousPageWithinScope<Page<AlertEvent>>(eventScope), staleTime: 5_000, gcTime: QUERY_MEMORY.highVolumeGcTime });
  const rows = events.data?.items ?? [];
  const selectedID = selection?.scope === eventScope ? selection.id : '';
  const detail = useQuery({ queryKey: ['alert-event-v2', selectedID], queryFn: ({ signal }) => api.alertEventV2(selectedID, signal), enabled: Boolean(selectedID), staleTime: 3_000, gcTime: QUERY_MEMORY.summaryGcTime });
  const action = useMutation({ mutationFn: ({ name, event }: { name: 'acknowledge' | 'close' | 'ignore'; event: AlertEvent }) => api.actOnAlertV2(event.id, { version: event.version, action: name, note }), onSuccess: async (event) => { setNote(''); queryClient.setQueryData(['alert-event-v2', event.id], event); await Promise.all([queryClient.invalidateQueries({ queryKey: ['alert-events-v2'] }), queryClient.invalidateQueries({ queryKey: ['alert-summary-v2'] }), queryClient.invalidateQueries({ queryKey: ['alert-notifications-v2'] })]); } });
  const closeDetail = () => { setSelection(undefined); setNote(''); };
  const selectEvent = useCallback((id: string) => setSelection({ scope: eventScope, id }), [eventScope]);
  const selectedEvent = detail.data ?? rows.find((row) => row.id === selectedID);
  const mobileFiltersOpen = mobileLayout && !filtersCollapsed;
  const applyDraft = () => {
    setFilters(draft);
    setOffset(0);
    setFiltersCollapsed(true);
  };
  const submit = (e: FormEvent) => { e.preventDefault(); applyDraft(); };
  const resetMobileDraft = () => setDraft(EMPTY_FILTERS);
  const resetFilters = () => {
    setDraft(EMPTY_FILTERS);
    setFilters(EMPTY_FILTERS);
    setOffset(0);
    setAdvancedOpen(false);
    setFiltersCollapsed(true);
  };
  const quickStatus = (status: string) => { const next = { ...filters, status }; setDraft(next); setFilters(next); setOffset(0); };
  const totalPages = Math.max(1, Math.ceil((events.data?.total ?? 0) / limit));
  const page = Math.floor(offset / limit) + 1;
  const sums = summary.data;
  const activeFilterCount = Object.values(filters).filter(Boolean).length;
  const advancedFilterCount = [filters.ruleId, filters.protocol, filters.dateFrom, filters.dateTo].filter(Boolean).length;
  const draftAdvancedFilterCount = [draft.ruleId, draft.protocol, draft.dateFrom, draft.dateTo].filter(Boolean).length;
  const allEventCount = Number(sums?.active ?? 0) + Number(sums?.recovered ?? 0) + Number(sums?.closed ?? 0) + Number(sums?.ignored ?? 0);
  const inspector = <EventInspector event={selectedEvent} note={note} acting={action.isPending} actionError={action.error instanceof Error ? action.error.message : undefined} editable={editable} onNote={setNote} onAction={(name) => { const event = detail.data; if (event) action.mutate({ name, event }); }} onClose={closeDetail} />;
  const coreFilterFields = <>
    <label><span>关键词</span><Input prefix={<IconSearch />} value={draft.keyword} onChange={(value) => setDraft({ ...draft, keyword: value })} placeholder="车牌 / VIN / 规则名称" /></label>
    <label><span id="alert-severity-label">严重程度</span><Select aria-labelledby="alert-severity-label" value={draft.severity} onChange={(value) => setDraft({ ...draft, severity: String(value) })} optionList={[{ value: '', label: '全部' }, { value: 'critical', label: '紧急' }, { value: 'major', label: '重要' }, { value: 'minor', label: '一般' }]} /></label>
    <label><span id="alert-status-label">状态</span><Select aria-labelledby="alert-status-label" value={draft.status} onChange={(value) => setDraft({ ...draft, status: String(value) })} optionList={[{ value: '', label: '全部' }, ...Object.entries(statusLabels).map(([value, label]) => ({ value, label }))]} /></label>
  </>;
  const advancedFilterFields = <>
    <label><span id="alert-rule-label">规则</span><Select aria-labelledby="alert-rule-label" value={draft.ruleId} onChange={(value) => setDraft({ ...draft, ruleId: String(value) })} optionList={[{ value: '', label: '全部规则' }, ...rules.map((rule) => ({ value: rule.id, label: rule.name }))]} /></label>
    <label><span id="alert-protocol-label">协议</span><Select aria-labelledby="alert-protocol-label" value={draft.protocol} onChange={(value) => setDraft({ ...draft, protocol: String(value) })} optionList={[{ value: '', label: '全部协议' }, ...PROTOCOLS.map((item) => ({ value: item, label: item }))]} /></label>
    <label><span>起始时间</span><Input aria-label="告警起始时间" type="datetime-local" value={draft.dateFrom} onChange={(value) => setDraft({ ...draft, dateFrom: value })} /></label>
    <label><span>结束时间</span><Input aria-label="告警结束时间" type="datetime-local" value={draft.dateTo} onChange={(value) => setDraft({ ...draft, dateTo: value })} /></label>
  </>;
  const metricItem = (
    label: string,
    value: number,
    status: string,
    options: Pick<WorkspaceQueueMetricRailItem, 'note' | 'tone' | 'emphasis'>
  ): WorkspaceQueueMetricRailItem => {
    const formattedValue = value.toLocaleString('zh-CN');
    const notice = status === 'notice';
    return {
      label,
      value: formattedValue,
      ...options,
      active: !notice && filters.status === status,
      ariaLabel: notice ? `查看未读通知，共 ${formattedValue} 条` : `筛选${label}，共 ${formattedValue} 条`,
      onClick: () => notice ? onTab('notifications') : quickStatus(status)
    };
  };
  const primaryMetrics: WorkspaceQueueMetricRailItem[] = mobileLayout
    ? [
      metricItem('待处置', Number(sums?.unprocessed ?? 0), 'unprocessed', { note: '需要核验', tone: 'danger', emphasis: 'primary' }),
      metricItem('全部事件', allEventCount, '', { note: '当前范围', tone: 'neutral', emphasis: 'primary' })
    ]
    : [
      metricItem('未处理', Number(sums?.unprocessed ?? 0), 'unprocessed', { note: '需要核验与处置', tone: 'danger', emphasis: 'primary' }),
      metricItem('全部事件', allEventCount, '', { note: '当前查询范围', tone: 'neutral', emphasis: 'primary' }),
      metricItem('处理中', Number(sums?.processing ?? 0), 'processing', { note: '处置进行中', tone: 'primary', emphasis: 'secondary' }),
      metricItem('未读通知', unread, 'notice', { note: '进入通知队列', tone: 'warning', emphasis: 'secondary' })
    ];
  return <>
    {mobileLayout ? <div className="v2-alert-mobile-discovery">
      <MobileFilterToggle
        title="事件筛选"
        summary={activeFilterCount ? `已启用 ${activeFilterCount} 项` : '全部告警'}
        expanded={mobileFiltersOpen}
        controls="v2-alert-mobile-filters"
        expandedLabel="关闭"
        collapsedLabel="修改"
        onToggle={() => setFiltersCollapsed((value) => !value)}
      />
      <MobileFilterSheet
        className="v2-alert-filter-sidesheet v2-alert-mobile-filter-sidesheet"
        visible={mobileFiltersOpen}
        height="min(82dvh, 690px)"
        ariaLabel="告警事件筛选"
        dialogId="v2-alert-mobile-filters"
        closeLabel="关闭告警事件筛选"
        title="筛选告警事件"
        description="车辆、状态、规则与时间范围"
        onCancel={() => setFiltersCollapsed(true)}
        secondaryAction={{ label: '重置条件', onClick: resetMobileDraft }}
        primaryAction={{ label: '应用并查询', onClick: applyDraft }}
      >
        <form className="v2-alert-mobile-filter-form" onSubmit={submit}>
          <MobileFilterSheetSection className="v2-alert-mobile-filter-section" ariaLabel="常用筛选" title="常用条件" description="快速定位车辆与待处置事件">
            <div className="v2-alert-mobile-filter-grid is-primary">{coreFilterFields}</div>
          </MobileFilterSheetSection>
          <MobileFilterSheetSection className="v2-alert-mobile-filter-section" ariaLabel="精确筛选" title="精确范围" description="按规则、协议和触发时间进一步收窄">
            <div className="v2-alert-mobile-filter-grid">{advancedFilterFields}</div>
          </MobileFilterSheetSection>
        </form>
      </MobileFilterSheet>
    </div> : <WorkspaceFilterPanel
      className="v2-alert-filter-card"
      title="事件筛选"
      description="关键词、严重程度、处理状态与高级范围"
      mobileSummary={activeFilterCount ? `已启用 ${activeFilterCount} 项` : '全部告警'}
      expanded={!filtersCollapsed}
      status={activeFilterCount ? `${activeFilterCount} 项已启用` : '全部告警'}
      statusColor={activeFilterCount ? 'blue' : 'grey'}
      onToggle={() => setFiltersCollapsed((value) => !value)}
    >
      <form className="v2-alert-filter v2-alert-filter-primary" onSubmit={submit}>
        {coreFilterFields}
        <Button className="v2-alert-more-filter" theme="light" htmlType="button" icon={<IconFilter />} aria-haspopup="dialog" aria-controls="v2-alert-advanced-filters" aria-expanded={advancedOpen} onClick={() => setAdvancedOpen(true)}>更多筛选{advancedFilterCount ? ` · ${advancedFilterCount}` : ''}</Button>
        <Button className="v2-primary-button" theme="solid" htmlType="submit">查询</Button>
        <Button className="v2-secondary-button" theme="light" htmlType="button" onClick={resetFilters}>重置</Button>
      </form>
    </WorkspaceFilterPanel>}
    {summary.isError ? <InlineError message={summary.error instanceof Error ? summary.error.message : '告警汇总读取失败'} onRetry={() => summary.refetch()} /> : null}
    <WorkspaceMetricRail
      variant="queue"
      className="v2-alert-metric-rail"
      ariaLabel="活跃告警概览"
      items={primaryMetrics}
      context={!mobileLayout ? <nav className="v2-alert-metric-context-actions" aria-label="已结束告警状态">{
        [
          { label: '已恢复', value: sums?.recovered, status: 'recovered' },
          { label: '已关闭', value: sums?.closed, status: 'closed' },
          { label: '已忽略', value: sums?.ignored, status: 'ignored' }
        ].map((item) => <Button key={item.status} theme="borderless" type="tertiary" aria-pressed={filters.status === item.status} onClick={() => quickStatus(item.status)}><span>{item.label}</span><strong>{Number(item.value ?? 0).toLocaleString('zh-CN')}</strong></Button>)
      }</nav> : undefined}
    />
    {events.isError ? <InlineError message={events.error instanceof Error ? events.error.message : '告警事件读取失败'} onRetry={() => events.refetch()} /> : null}
    <div className={`v2-alert-workspace${selectedID && !mobileLayout ? ' is-inspector-open' : ''}`}>
      <Card className="v2-alert-table-card" bodyStyle={{ padding: 0 }}>
        <WorkspacePanelHeader
          title="告警事件"
          description={selectedID && !mobileLayout ? '已展开处置详情 · 点击其他事件快速切换' : '点击事件查看触发证据、处理进度与处置动作'}
          meta={`共 ${(events.data?.total ?? 0).toLocaleString('zh-CN')} 条`}
          actions={<Button theme="borderless" icon={<IconRefresh />} loading={events.isFetching} onClick={() => Promise.all([events.refetch(), summary.refetch(), ...(selectedID ? [detail.refetch()] : [])])}>刷新</Button>}
        />
        <div
          className={`v2-alert-table-scroll${mobileLayout ? ' is-mobile-scroll' : ''}`}
          tabIndex={mobileLayout ? 0 : undefined}
          aria-label={mobileLayout ? '告警事件列表，可上下滚动' : undefined}
        >
          {mobileLayout
            ? <div className="v2-alert-mobile-list">{rows.map((event) => <Card key={event.id} className={`v2-alert-mobile-card${selectedID === event.id ? ' is-selected' : ''}`} bodyStyle={{ padding: 0 }}><Button theme="borderless" type="tertiary" className="v2-alert-mobile-action" aria-pressed={selectedID === event.id} aria-expanded={selectedID === event.id} aria-label={`查看 ${event.plate || event.vin} ${event.ruleName} 告警详情`} onClick={() => selectEvent(event.id)}><span className="v2-alert-mobile-card-content"><header><strong>{event.plate || '未绑定车牌'}</strong><span><SeverityTag severity={event.severity} /><StatusTag status={event.status} /></span></header><p>{event.ruleName}</p><dl><div><dt>触发时间</dt><dd><AlertTime value={event.triggeredAt} /></dd></div><div><dt>数据来源</dt><dd><ProtocolTag className="v2-alert-protocol-tag" protocol={event.protocol} compact /></dd></div><div><dt>超限幅度</dt><dd>{alertDeltaText(event)}</dd></div></dl><footer>核验并处置<IconChevronRight /></footer></span></Button></Card>)}</div>
            : <AlertEventTable rows={rows} selectedID={selectedID} compact={Boolean(selectedID)} onSelect={selectEvent} />}
          {events.isFetching ? <PanelLoading className="v2-alert-loading" title="正在更新事件…" description="告警列表返回后会自动更新。" compact={Boolean(rows.length)} /> : null}
          {!events.isFetching && !rows.length ? <PanelEmpty className="v2-alert-empty" title="当前筛选条件没有告警事件" description="调整车辆、严重程度、状态或时间范围后重试。" /> : null}
        </div>
        <footer><TablePagination page={page} totalPages={totalPages} info={`共 ${(events.data?.total ?? 0).toLocaleString('zh-CN')} 条`} onPageChange={(next) => setOffset((next - 1) * limit)} pageSize={limit} onPageSizeChange={(next) => { setLimit(next); setOffset(0); }} pageSizeOptions={[{ value: 20, label: '20 条/页' }, { value: 50, label: '50 条/页' }]} /></footer>
      </Card>
      {!mobileLayout && selectedID ? inspector : null}
    </div>
    {!mobileLayout ? <WorkspaceSideSheet
      className="v2-alert-filter-sidesheet"
      variant="filter"
      visible={advancedOpen}
      ariaLabel="告警高级筛选"
      dialogId="v2-alert-advanced-filters"
      closeLabel="关闭告警高级筛选"
      width={430}
      title="更多筛选"
      description="按规则、协议和触发时间收窄告警范围"
      icon={<IconFilter />}
      badge={draftAdvancedFilterCount ? `${draftAdvancedFilterCount} 项待应用` : '全部范围'}
      badgeColor={draftAdvancedFilterCount ? 'blue' : 'grey'}
      summaryItems={[
        { label: '规则', value: rules.find((rule) => rule.id === draft.ruleId)?.name || '全部规则', tone: draft.ruleId ? 'primary' : 'neutral' },
        { label: '协议', value: draft.protocol || '全部协议', tone: draft.protocol ? 'primary' : 'neutral' },
        { label: '触发时间', value: draft.dateFrom || draft.dateTo ? '已限定' : '不限时间', detail: draft.dateFrom || draft.dateTo ? '按本地时间查询' : undefined, tone: draft.dateFrom || draft.dateTo ? 'primary' : 'neutral' }
      ]}
      footerNote="高级条件仅在应用后更新事件列表"
      secondaryActions={[{ label: '清空高级条件', onClick: () => setDraft({ ...draft, ruleId: '', protocol: '', dateFrom: '', dateTo: '' }) }]}
      primaryAction={{ label: '应用筛选', onClick: () => { applyDraft(); setAdvancedOpen(false); } }}
      onCancel={() => setAdvancedOpen(false)}
    >
      <div className="v2-alert-advanced-filter">{advancedFilterFields}</div>
    </WorkspaceSideSheet>
    : null}
    <WorkspaceDetailSideSheet
      className="v2-alert-detail-sidesheet"
      visible={mobileLayout && Boolean(selectedID)}
      ariaLabel="告警事件详情"
      closeLabel="关闭告警详情"
      placement="bottom"
      height="min(90dvh, 800px)"
      title="告警详情"
      description={selectedEvent ? `${selectedEvent.plate || selectedEvent.vin} · ${selectedEvent.ruleName}` : '证据、状态与处置履历'}
      badge={selectedEvent ? statusLabels[selectedEvent.status] : undefined}
      badgeColor={selectedEvent ? alertStatusColor(selectedEvent.status) : 'grey'}
      onCancel={closeDetail}
    >
      {mobileLayout && selectedID ? <EventInspector event={selectedEvent} note={note} acting={action.isPending} actionError={action.error instanceof Error ? action.error.message : undefined} editable={editable} onNote={setNote} onAction={(name) => { const event = detail.data; if (event) action.mutate({ name, event }); }} onClose={closeDetail} sheet /> : null}
    </WorkspaceDetailSideSheet>
  </>;
}

function emptyRule(): AlertRuleInput { return { id: '', name: '', description: '', severity: 'major', valueType: 'numeric', metric: 'speed_kmh', operator: 'gt', threshold: 80, thresholdHigh: 100, durationSec: 60, recoveryOperator: 'lte', recoveryThreshold: 75, repeatIntervalSec: 600, scopeProtocols: [], scopeVins: [], scopeOems: [], scopeModels: [], scopeCompanies: [], notificationChannels: ['in_app'], enabled: true, version: 0 }; }
function ruleDraft(rule: AlertRule): AlertRuleInput {
  return {
    id: rule.id, name: rule.name, description: rule.description, severity: rule.severity, valueType: rule.valueType,
    metric: rule.metric, operator: rule.operator, threshold: rule.threshold, thresholdHigh: rule.thresholdHigh, booleanThreshold: rule.booleanThreshold,
    durationSec: rule.durationSec, recoveryOperator: rule.recoveryOperator, recoveryThreshold: rule.recoveryThreshold,
    repeatIntervalSec: rule.repeatIntervalSec, scopeProtocols: [...(rule.scopeProtocols ?? [])], scopeVins: [...(rule.scopeVins ?? [])], scopeOems: [...(rule.scopeOems ?? [])], scopeModels: [...(rule.scopeModels ?? [])], scopeCompanies: [...(rule.scopeCompanies ?? [])],
    notificationChannels: [...(rule.notificationChannels ?? ['in_app'])], enabled: rule.enabled, version: rule.version
  };
}

function RuleFormSection({ id, title, description, children }: { id: string; title: string; description: string; children: ReactNode }) {
  return <section className="v2-rule-form-section" aria-labelledby={id}>
    <header><span><Typography.Title heading={6} id={id}>{title}</Typography.Title><small>{description}</small></span></header>
    <div className="v2-rule-form-grid">{children}</div>
  </section>;
}

function RulesWorkspace({ rules, metrics }: { rules: AlertRule[]; metrics: MetricDefinition[] }) {
  const queryClient = useQueryClient();
  const mobileLayout = useMobileLayout();
  const [selectedID, setSelectedID] = useState('');
  const [draft, setDraft] = useState<AlertRuleInput>(emptyRule());
  const [editorOpen, setEditorOpen] = useState(false);
  useEffect(() => { if (!selectedID && rules[0]) { setSelectedID(rules[0].id); setDraft(ruleDraft(rules[0])); } }, [rules, selectedID]);
  const save = useMutation({ mutationFn: api.saveAlertRuleV2, onSuccess: async (rule) => { setSelectedID(rule.id); setDraft(ruleDraft(rule)); await queryClient.invalidateQueries({ queryKey: ['alert-rules-v2'] }); } });
  const toggle = useMutation({ mutationFn: (rule: AlertRule) => api.setAlertRuleEnabledV2(rule.id, { version: rule.version, enabled: !rule.enabled }), onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ['alert-rules-v2'] }); } });
  const mutationError = save.error ?? toggle.error;
  const operators = draft.valueType === 'boolean' ? BOOLEAN_OPERATORS : NUMERIC_OPERATORS;
  const availableMetrics = metrics.filter((metric) => metric.alertable && metric.valueType === draft.valueType);
  const catalogLabels = Object.fromEntries(metrics.map((metric) => [metric.key, metric.label]));
  const setList = (key: 'scopeProtocols' | 'scopeVins' | 'scopeOems' | 'scopeModels' | 'scopeCompanies', value: string) => setDraft({ ...draft, [key]: value.split(',').map((item) => item.trim()).filter(Boolean) });
  const openNewRule = () => {
    setSelectedID('__new__');
    setDraft(emptyRule());
    if (mobileLayout) setEditorOpen(true);
  };
  const openRule = (rule: AlertRule) => {
    setSelectedID(rule.id);
    setDraft(ruleDraft(rule));
    if (mobileLayout) setEditorOpen(true);
  };
  const applyTemplate = (template: RuleTemplate) => {
    setSelectedID('__new__');
    setDraft({ ...template.draft, scopeProtocols: [...template.draft.scopeProtocols], notificationChannels: [...template.draft.notificationChannels] });
  };
  const severityColor = (severity: AlertRule['severity']) => severity === 'critical' ? 'red' : severity === 'major' ? 'orange' : 'amber';
  const toggleButton = draft.version ? <Button
    htmlType="button"
    theme="light"
    disabled={toggle.isPending || save.isPending}
    onClick={() => {
      const current = rules.find((item) => item.id === draft.id);
      if (current) toggle.mutate(current);
    }}
  >{toggle.isPending ? '更新中' : draft.enabled ? '停用规则' : '启用规则'}</Button> : null;
  const editor = <Card className="v2-alert-rule-editor" bodyStyle={{ padding: 0 }}>
    <form className="v2-alert-rule-editor-form" onSubmit={(event) => { event.preventDefault(); save.mutate(draft); }}>
    {!mobileLayout ? <WorkspacePanelHeader
      className="v2-alert-rule-editor-heading"
      title={draft.version ? '编辑规则' : '新建规则'}
      description="数值、状态与主数据范围均纳入版本审计"
      actions={toggleButton}
    /> : <div className={`v2-alert-rule-sheet-status${draft.version ? '' : ' is-new'}`}>
      <span>
        <small>{draft.version ? `v${draft.version} · 配置变更写入版本审计` : '保存后生成首个可审计版本'}</small>
      </span>
      {toggleButton}
    </div>}
    {!draft.version ? <section className="v2-alert-rule-templates" aria-label="告警规则模板">
      <div><strong>快速创建</strong><span>从常用业务场景开始，再按车辆、车型或企业收窄范围。</span></div>
      <nav>{RULE_TEMPLATES.map((template) => {
        const available = metrics.some((metric) => metric.key === template.draft.metric && metric.alertable);
        return <Button
          className="v2-alert-template-card"
          key={template.id}
          theme="borderless"
          type="tertiary"
          disabled={!available}
          onClick={() => applyTemplate(template)}
        >
          <span className="v2-alert-template-icon"><IconAlarm /></span>
          <span className="v2-alert-template-copy"><b>{template.title}</b><small>{available ? template.summary : '当前字段目录尚未启用此指标'}</small></span>
          <Tag size="small" color={available ? 'blue' : 'grey'} type="light">{available ? (template.draft.scopeProtocols.join(' / ') || '全部协议') : '不可用'}</Tag>
        </Button>;
      })}</nav>
    </section> : null}
    <div className="v2-rule-form-sections">
      <RuleFormSection id="v2-rule-trigger-heading" title="触发条件" description="定义监控指标、判断方式与持续时间">
        <label><span>规则名称</span><Input required maxLength={80} value={draft.name} onChange={(value) => setDraft({ ...draft, name: value })} /></label>
        <label><span>严重程度</span><Select value={draft.severity} onChange={(value) => setDraft({ ...draft, severity: String(value) as AlertRuleInput['severity'] })} optionList={[{ value: 'critical', label: '紧急' }, { value: 'major', label: '重要' }, { value: 'minor', label: '一般' }]} /></label>
        <label><span>值类型</span><Select value={draft.valueType} onChange={(value) => { const valueType = String(value) as AlertRuleInput['valueType']; const metric = metrics.find((item) => item.alertable && item.valueType === valueType)?.key ?? ''; setDraft({ ...draft, valueType, operator: valueType === 'boolean' ? 'eq' : 'gt', metric }); }} optionList={[{ value: 'numeric', label: '数值' }, { value: 'boolean', label: '布尔' }]} /></label>
        <label><span>指标</span><Select disabled={!availableMetrics.length} value={draft.metric} onChange={(value) => setDraft({ ...draft, metric: String(value) })} optionList={availableMetrics.map((metric) => ({ value: metric.key, label: `${metric.label}${metric.unit ? ` (${metric.unit})` : ''}` }))} /></label>
        <label><span>触发比较符</span><Select value={draft.operator} onChange={(value) => { const operator = String(value); setDraft({ ...draft, operator, durationSec: operator === 'changed' ? 0 : draft.durationSec }); }} optionList={operators.map((value) => ({ value, label: operatorLabels[value] }))} /></label>
        {draft.operator === 'changed' ? <label><span>变化语义</span><Input value="false ↔ true" disabled /></label> : draft.valueType === 'boolean' ? <label><span>目标值</span><Select value={draft.booleanThreshold ? 'true' : 'false'} onChange={(value) => setDraft({ ...draft, booleanThreshold: value === 'true' })} optionList={[{ value: 'true', label: '是' }, { value: 'false', label: '否' }]} /></label> : <label><span>{draft.operator === 'between' || draft.operator === 'outside' ? '区间下限' : '触发阈值'}</span><Input required type="number" step="0.01" placeholder={draft.metric === 'hydrogen_concentration_percent' ? '请按厂家标准填写百分比' : undefined} value={Number.isFinite(draft.threshold) ? String(draft.threshold) : ''} onChange={(value) => setDraft({ ...draft, threshold: value === '' ? Number.NaN : Number(value) })} /></label>}
        {draft.operator === 'between' || draft.operator === 'outside' ? <label><span>区间上限</span><Input type="number" step="0.1" value={String(draft.thresholdHigh)} onChange={(value) => setDraft({ ...draft, thresholdHigh: Number(value) })} /></label> : null}
        <label><span>持续时间（秒）</span><Input type="number" min="0" max="86400" disabled={draft.operator === 'changed'} value={String(draft.durationSec)} onChange={(value) => setDraft({ ...draft, durationSec: Number(value) })} /></label>
      </RuleFormSection>
      <RuleFormSection id="v2-rule-recovery-heading" title="恢复与提醒" description="控制恢复条件和重复通知频率">
        <label><span>恢复比较符</span><Select value={draft.recoveryOperator} onChange={(value) => setDraft({ ...draft, recoveryOperator: String(value) })} optionList={[{ value: '', label: '未配置' }, ...['gt', 'gte', 'lt', 'lte', 'eq', 'neq'].map((value) => ({ value, label: operatorLabels[value] }))]} /></label>
        <label><span>恢复阈值</span><Input type="number" step="0.1" value={String(draft.recoveryThreshold)} onChange={(value) => setDraft({ ...draft, recoveryThreshold: Number(value) })} /></label>
        <label><span>重复间隔（秒）</span><Input type="number" min="0" max="604800" value={String(draft.repeatIntervalSec)} onChange={(value) => setDraft({ ...draft, repeatIntervalSec: Number(value) })} /></label>
      </RuleFormSection>
      <RuleFormSection id="v2-rule-scope-heading" title="适用范围与说明" description="按协议、车辆与主数据范围精确生效">
        <label className="is-wide"><span>协议范围（逗号分隔；空为全部）</span><Input value={draft.scopeProtocols.join(',')} onChange={(value) => setList('scopeProtocols', value)} /></label>
        <label className="is-wide"><span>车辆 VIN 范围（逗号分隔；空为全部）</span><Input value={draft.scopeVins.join(',')} onChange={(value) => setList('scopeVins', value)} /></label>
        <label className="is-wide"><span>厂家范围（逗号分隔；空为全部）</span><Input value={draft.scopeOems.join(',')} onChange={(value) => setList('scopeOems', value)} /></label>
        <label className="is-wide"><span>车型范围（来自车辆主档；空为全部）</span><Input value={draft.scopeModels.join(',')} onChange={(value) => setList('scopeModels', value)} /></label>
        <label className="is-wide"><span>企业范围（来自车辆主档；空为全部）</span><Input value={draft.scopeCompanies.join(',')} onChange={(value) => setList('scopeCompanies', value)} /></label>
        <label className="is-wide"><span>说明</span><TextArea maxCount={500} autosize={{ minRows: 3, maxRows: 7 }} value={draft.description} onChange={(value) => setDraft({ ...draft, description: value })} /></label>
      </RuleFormSection>
    </div>
    <footer><div><b>通知通道</b><span>站内通知已启用；短信、邮件、企微为预留 / 未启用。</span></div>{mutationError ? <em>{mutationError.message}</em> : null}<Button className="v2-primary-button" theme="solid" htmlType="submit" disabled={save.isPending || toggle.isPending}>{save.isPending ? '保存中' : '保存规则'}</Button></footer>
    </form>
  </Card>;
  return <div className={`v2-alert-rules${mobileLayout ? ' is-mobile' : ''}`}>
    <Card className="v2-alert-rule-list" bodyStyle={{ padding: 0 }}>
      <WorkspacePanelHeader
        className="v2-alert-rule-list-heading"
        title="规则配置"
        description={rules.length ? `${rules.length} 条可审计规则` : '尚未创建规则'}
        actions={<Button theme="light" type="primary" icon={<IconPlus />} aria-haspopup={mobileLayout ? 'dialog' : undefined} aria-expanded={mobileLayout ? editorOpen && selectedID === '__new__' : undefined} onClick={openNewRule}>新建规则</Button>}
      />
      <div className="v2-alert-rule-items" role="region" tabIndex={0} aria-label={mobileLayout ? '告警规则列表' : '告警规则列表，可左右滚动选择'}>
        {rules.map((rule) => <Button
          className={`v2-alert-rule-item${selectedID === rule.id ? ' is-selected' : ''}`}
          key={rule.id}
          theme="borderless"
          type="tertiary"
          aria-pressed={selectedID === rule.id}
          aria-haspopup={mobileLayout ? 'dialog' : undefined}
          aria-expanded={mobileLayout ? editorOpen && selectedID === rule.id : undefined}
          onClick={() => openRule(rule)}
        >
          <span className={`v2-alert-rule-dot is-${rule.severity}`} aria-hidden="true" />
          <span className="v2-alert-rule-copy">
            <span><strong>{rule.name}</strong><Tag size="small" color={severityColor(rule.severity)} type="light">{severityLabels[rule.severity]}</Tag></span>
            <small>{ruleCondition(rule, catalogLabels)} · v{rule.version}</small>
          </span>
          <Tag className="v2-alert-rule-state" size="small" color={rule.enabled ? 'green' : 'grey'} type="light">{rule.enabled ? '已启用' : '已停用'}</Tag>
        </Button>)}
        {!rules.length ? <Empty className="v2-alert-rule-empty" title="暂无告警规则" description="新建规则后可从常用模板开始，也可直接配置自定义条件。" /> : null}
      </div>
    </Card>
    {!mobileLayout ? editor : null}
    {mobileLayout ? <WorkspaceSideSheet
      className="v2-alert-rule-editor-sidesheet"
      variant="editor"
      visible={editorOpen}
      ariaLabel="告警规则编辑"
      placement="bottom"
      height="min(92dvh, 820px)"
      title={draft.version ? '编辑规则' : '新建规则'}
      description={draft.name || '配置触发条件、范围与通知策略'}
      badge={draft.version ? draft.enabled ? '已启用' : '已停用' : '新规则'}
      badgeColor={draft.version ? draft.enabled ? 'green' : 'grey' : 'blue'}
      onCancel={() => setEditorOpen(false)}
    >
      {editorOpen ? editor : null}
    </WorkspaceSideSheet> : null}
  </div>;
}

function NotificationsWorkspace({ editable }: { editable: boolean }) {
  const queryClient = useQueryClient();
  const mobileLayout = useMobileLayout();
  const [offset, setOffset] = useState(0);
  const [limit, setLimit] = useState(20);
  const notifications = useQuery({
    queryKey: ['alert-notifications-v2', 'all', limit, offset],
    queryFn: ({ signal }) => api.alertNotificationsV2(new URLSearchParams({ limit: String(limit), offset: String(offset) }), signal),
    staleTime: 5_000,
    gcTime: QUERY_MEMORY.highVolumeGcTime
  });
  const read = useMutation({ mutationFn: api.readAlertNotificationsV2, onSuccess: async () => { await Promise.all([queryClient.invalidateQueries({ queryKey: ['alert-notifications-v2'] }), queryClient.invalidateQueries({ queryKey: ['alert-summary-v2'] })]); } });
  const items = notifications.data?.items ?? [];
  const total = notifications.data?.total ?? 0;
  const totalPages = Math.max(1, Math.ceil(total / limit));
  const page = Math.floor(offset / limit) + 1;
  useEffect(() => {
    if (offset > 0 && offset >= total && !notifications.isPending) setOffset(Math.max(0, (totalPages - 1) * limit));
  }, [limit, notifications.isPending, offset, total, totalPages]);
  const totalTag = <Tag className="v2-alert-notification-total" color="blue" type="light" size="small">{total.toLocaleString('zh-CN')} 条</Tag>;
  return <Card className="v2-alert-notifications" bodyStyle={{ padding: 0 }}><WorkspacePanelHeader className="v2-alert-notification-heading" title={mobileLayout ? <span className="v2-alert-notification-mobile-title">通知列表{totalTag}</span> : '站内通知'} description="仅站内通道具备真实送达与已读状态" meta={mobileLayout ? undefined : totalTag} actions={editable ? <Button theme="light" type="primary" disabled={read.isPending || !items.some((item) => !item.read)} onClick={() => read.mutate(items.filter((item) => !item.read).map((item) => item.id))}>{read.isPending ? '正在更新' : mobileLayout ? '本页已读' : '本页全部已读'}</Button> : <span className="v2-role-badge">只读</span>} />{notifications.isError ? <InlineError message={notifications.error?.message ?? '站内通知读取失败'} onRetry={() => notifications.refetch()} /> : null}{read.isError ? <InlineError message={read.error instanceof Error ? read.error.message : '通知状态更新失败'} /> : null}
    <div className="v2-alert-notification-list">{notifications.isPending ? <div className="v2-alert-notification-loading"><Spin size="middle" tip="正在读取站内通知" /></div> : items.length ? <CardGroup className="v2-alert-notification-cards" type="grid" spacing={0}>{items.map((item) => <Card className={`v2-alert-notification-card${item.read ? ' is-read' : ' is-unread'}`} key={item.id} title={<span className="v2-alert-notification-title"><SeverityTag severity={item.severity} /><span title={item.title}>{item.title}</span></span>} headerExtraContent={<Tag color={item.read ? 'grey' : 'orange'} type="light" size="small">{item.read ? '已读' : '未读'}</Tag>} headerLine bodyStyle={{ padding: 0 }}>
      <p className="v2-alert-notification-content" title={item.content}>{item.content}</p>
      <footer><Typography.Text type="tertiary"><AlertTime value={item.createdAt} /></Typography.Text>{editable && !item.read ? <Button theme="borderless" disabled={read.isPending} onClick={() => read.mutate([item.id])}>{read.isPending ? '更新中' : '标为已读'}</Button> : null}</footer>
    </Card>)}</CardGroup> : !notifications.isError ? <Empty className="v2-alert-notification-empty" image={<IconBell />} title="暂无站内通知" description="告警触发或状态变更后，通知会在这里形成可追溯记录。" /> : null}</div>
    <footer className="v2-alert-notification-pagination"><TablePagination page={page} totalPages={totalPages} info={`共 ${total.toLocaleString('zh-CN')} 条 · 本页 ${items.length.toLocaleString('zh-CN')} 条`} disabled={notifications.isFetching} onPageChange={(next) => setOffset((next - 1) * limit)} pageSize={limit} pageSizeLabel="每页通知数" onPageSizeChange={(next) => { setLimit(next); setOffset(0); }} pageSizeOptions={[{ value: 20, label: '20 条/页' }, { value: 50, label: '50 条/页' }]} /></footer>
    <Collapse className="v2-alert-channel-collapse" keepDOM>
      <Collapse.Panel itemKey="external-channels" header={<span className="v2-alert-channel-heading"><span><strong>外部通知通道</strong><small>短信、邮件与企业微信均为预留能力</small></span><Tag color="grey" type="light" size="small">3 个 · 未启用</Tag></span>}>
        <Descriptions className="v2-alert-channel-descriptions" align="left" size="small" data={[
          { key: '短信（SMS）', value: <Tag color="grey" type="light" size="small">预留 · 未启用</Tag> },
          { key: '邮件（Email）', value: <Tag color="grey" type="light" size="small">预留 · 未启用</Tag> },
          { key: '企业微信（WeCom）', value: <Tag color="grey" type="light" size="small">预留 · 未启用</Tag> }
        ]} />
      </Collapse.Panel>
    </Collapse>
  </Card>;
}

export default function AlertsPage() {
  const { session } = usePlatformSession(); const operator = canOperate(session); const admin = canAdminister(session);
  const [params, setParams] = useSearchParams(); const initialTab = (params.get('tab') as Tab) || 'events'; const [tab, setTabState] = useState<Tab>(['events', 'rules', 'notifications'].includes(initialTab) ? initialTab : 'events');
  const initialFilters: Filters = { ...EMPTY_FILTERS, keyword: params.get('vin') ?? params.get('keyword') ?? '', severity: params.get('severity') ?? '', status: params.get('status') ?? '', ruleId: params.get('ruleId') ?? '', protocol: params.get('protocol') ?? '' };
  const activeTab = tab === 'rules' && !admin ? 'events' : tab;
  const [filters, setFilterState] = useState(initialFilters); const [eventDraft, setEventDraft] = useState(initialFilters);
  const rules = useQuery({ queryKey: ['alert-rules-v2'], queryFn: ({ signal }) => api.alertRulesV2(signal), staleTime: 30_000, gcTime: QUERY_MEMORY.summaryGcTime, enabled: activeTab !== 'notifications' });
  const metrics = useQuery({ queryKey: ['metric-catalog-v2'], queryFn: ({ signal }) => api.metricCatalog(signal), staleTime: 300_000, enabled: activeTab === 'rules' });
  const notices = useQuery({ queryKey: ['alert-notifications-v2', 'unread'], queryFn: ({ signal }) => api.alertNotificationsV2(new URLSearchParams({ unreadOnly: 'true', limit: '1' }), signal), staleTime: 5_000, gcTime: QUERY_MEMORY.summaryGcTime });
  const unread = notices.data?.total ?? 0;
  const setTab = (next: Tab) => { setTabState(next); const copy = new URLSearchParams(params); copy.set('tab', next); setParams(copy, { replace: true }); };
  const setFilters = (next: Filters) => { setFilterState(next); const copy = new URLSearchParams(); if (tab !== 'events') copy.set('tab', tab); Object.entries(next).forEach(([key, value]) => { if (value) copy.set(key, value); }); setParams(copy, { replace: true }); };
  const tabs = [{ key: 'events' as const, label: '告警事件', icon: <IconAlarm /> }, ...(admin ? [{ key: 'rules' as const, label: '规则配置', icon: <IconSetting /> }] : []), { key: 'notifications' as const, label: '站内通知', icon: <IconBell />, count: unread || undefined }];
  return <div className={`v2-alert-page is-${activeTab}`}>
    <WorkspaceCommandBar
      className="v2-alert-navigation v2-alert-command-bar"
      ariaLabel="告警中心工作区"
      title="告警处置工作台"
      description="核验事件证据、治理规则并追踪通知状态"
      status={unread ? `${unread.toLocaleString('zh-CN')} 条未读` : '通知已读'}
      statusColor={unread ? 'orange' : 'green'}
      meta={<Typography.Text type="tertiary">{operator ? '可处置事件' : '只读查看'}</Typography.Text>}
      actions={<SegmentedTabs className="v2-alert-tabs" variant="filled" ariaLabel="告警中心分类" value={activeTab} items={tabs} onChange={setTab} />}
    />
    {activeTab !== 'notifications' && rules.isError ? <InlineError message={rules.error.message} onRetry={() => rules.refetch()} /> : null}
    {activeTab === 'rules' && metrics.isError ? <InlineError message={metrics.error.message} onRetry={() => metrics.refetch()} /> : null}
    {activeTab === 'events'
      ? <EventWorkspace filters={filters} draft={eventDraft} setDraft={setEventDraft} setFilters={setFilters} rules={rules.data ?? []} unread={unread} editable={operator} onTab={setTab} />
      : activeTab === 'rules'
        ? <RulesWorkspace rules={rules.data ?? []} metrics={metrics.data?.metrics ?? []} />
        : <NotificationsWorkspace editable={operator} />}
  </div>;
}
