'use client';

import {
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
  type CSSProperties,
  type MouseEvent,
  type ReactNode,
} from 'react';
import Link from 'next/link';
import AdminModal from '@/components/AdminModal';
import AdminMaintenancePanel from '@/components/AdminMaintenancePanel';
import {
  createDossierForJiraTicket,
  deriveWorkspaceAnalyze,
  deriveWorkspaceApply,
  fetchDashboardTicketTrendDrillDown,
  fetchDashboardTicketTrends,
  getAdminJiraSiteUrl,
  linkJiraTicketToDossier,
  syncJiraTicketStandalone,
  type ApplySelection,
  type DeriveAction,
  type DeriveWorkspaceAnalyzeResponse,
  type DashboardManualActivityRangeTotals,
  type DashboardTicketTrendsResponse,
  type DashboardTrendBucket,
  type DashboardTrendDrillItem,
  type DashboardTrendDrillMergeItem,
  type DashboardTrendDrillTicketItem,
  type DashboardTrendMetric,
  type DispositionTrendMetric,
  type IncidentAnalysisStatus,
  type IncidentAnalysisSummary,
  type IncidentListItem,
} from '@/lib/admin-incidents';
import {
  buildIncidentDossiersCsv,
  buildMergeDrillCsv,
  buildTotalTicketsDrillCsv,
  downloadCsvFile,
  incidentAnalysisStatusForExport,
} from '@/lib/incident-list-csv';
import { useTranslation, type TranslationKey } from '@/lib/i18n';
import { jiraIssueBrowseUrl } from '@/lib/jira-ui';

const LOOKBACK_WEEKS = [4, 8, 12, 26, 52] as const;
const LOOKBACK_MONTHS = [3, 6, 12, 18, 24] as const;

const TICKETS_BAR_COLOR = '#5c5fbc';

const DISPOSITION_STACK: {
  key: Exclude<DispositionTrendMetric, 'disposition_total'>;
  color: string;
  i18nKey:
    | 'admin.incidents.analysisStatus.not_incident'
    | 'admin.incidents.analysisStatus.unsupported'
    | 'admin.incidents.analysisStatus.not_analyzed'
    | 'admin.incidents.analysisStatus.no_analysis_needed'
    | 'admin.incidents.analysisStatus.analyzed'
    | 'admin.incidents.analysisStatus.merged';
}[] = [
  { key: 'disposition_not_incident', color: '#f59e0b', i18nKey: 'admin.incidents.analysisStatus.not_incident' },
  { key: 'disposition_unsupported', color: '#ea580c', i18nKey: 'admin.incidents.analysisStatus.unsupported' },
  { key: 'disposition_not_analyzed', color: '#3b82f6', i18nKey: 'admin.incidents.analysisStatus.not_analyzed' },
  {
    key: 'disposition_no_analysis_needed',
    color: '#64748b',
    i18nKey: 'admin.incidents.analysisStatus.no_analysis_needed',
  },
  { key: 'disposition_analyzed', color: '#10b981', i18nKey: 'admin.incidents.analysisStatus.analyzed' },
  { key: 'disposition_merged', color: '#9ca3af', i18nKey: 'admin.incidents.analysisStatus.merged' },
];

const METRIC_TITLE: Record<DashboardTrendMetric, string> = {
  total_tickets: 'Tickets',
  dossiers_created: 'Incidents',
  merged_issues_moved: 'Merged incidents',
  dossiers_workspace_linked_new: 'Analysed Incidents',
  disposition_total: 'All dossiers',
  disposition_not_incident: 'Not an incident',
  disposition_analyzed: 'Incident · analyzed',
  disposition_no_analysis_needed: 'Incident · no analysis needed',
  disposition_not_analyzed: 'Incident · not analyzed',
  disposition_merged: 'Merged',
  disposition_unsupported: 'Incident · unsupported',
};

function bucketDisposition(b: DashboardTrendBucket, key: DispositionTrendMetric): number {
  switch (key) {
    case 'disposition_total':
      return b.disposition_total ?? 0;
    case 'disposition_not_incident':
      return b.disposition_not_incident ?? 0;
    case 'disposition_analyzed':
      return b.disposition_analyzed ?? 0;
    case 'disposition_no_analysis_needed':
      return b.disposition_no_analysis_needed ?? 0;
    case 'disposition_not_analyzed':
      return b.disposition_not_analyzed ?? 0;
    case 'disposition_merged':
      return b.disposition_merged ?? 0;
    case 'disposition_unsupported':
      return b.disposition_unsupported ?? 0;
    default:
      return 0;
  }
}

function isDispositionDrillMetric(metric: DashboardTrendMetric): boolean {
  return metric.startsWith('disposition_');
}

type DrillStatusFilter = IncidentAnalysisStatus | 'all';

const DRILL_STATUS_FILTER_OPTIONS: { value: DrillStatusFilter; labelKey: TranslationKey }[] = [
  { value: 'all', labelKey: 'admin.incidents.list.filterAll' },
  { value: 'not_incident', labelKey: 'admin.incidents.analysisStatus.not_incident' },
  { value: 'unsupported', labelKey: 'admin.incidents.analysisStatus.unsupported' },
  { value: 'no_analysis_needed', labelKey: 'admin.incidents.analysisStatus.no_analysis_needed' },
  { value: 'not_analyzed', labelKey: 'admin.incidents.analysisStatus.not_analyzed' },
  { value: 'analyzed', labelKey: 'admin.incidents.analysisStatus.analyzed' },
  { value: 'merged', labelKey: 'admin.incidents.analysisStatus.merged' },
];

function dispositionMetricToStatusFilter(metric: DashboardTrendMetric): DrillStatusFilter {
  switch (metric) {
    case 'disposition_not_incident':
      return 'not_incident';
    case 'disposition_unsupported':
      return 'unsupported';
    case 'disposition_not_analyzed':
      return 'not_analyzed';
    case 'disposition_no_analysis_needed':
      return 'no_analysis_needed';
    case 'disposition_analyzed':
      return 'analyzed';
    case 'disposition_merged':
      return 'merged';
    default:
      return 'all';
  }
}

function statusFilterToDispositionMetric(filter: DrillStatusFilter): DispositionTrendMetric | null {
  switch (filter) {
    case 'all':
      return 'disposition_total';
    case 'not_incident':
      return 'disposition_not_incident';
    case 'unsupported':
      return 'disposition_unsupported';
    case 'not_analyzed':
      return 'disposition_not_analyzed';
    case 'analyzed':
      return 'disposition_analyzed';
    case 'no_analysis_needed':
      return 'disposition_no_analysis_needed';
    case 'merged':
      return 'disposition_merged';
    default:
      return null;
  }
}

function resolveRowAnalysisStatus(row: IncidentListItem): IncidentAnalysisStatus {
  if (row.analysis_status) return row.analysis_status;
  if (row.manual_not_incident) return 'not_incident';
  return 'not_analyzed';
}

function rowMatchesDrillStatusFilter(row: IncidentListItem, filter: DrillStatusFilter): boolean {
  if (filter === 'all') return true;
  const st = resolveRowAnalysisStatus(row);
  return st === filter;
}

function drillStatusTitle(t: (key: TranslationKey) => string, filter: DrillStatusFilter): string {
  if (filter === 'all') return t('admin.incidents.drill.metric.disposition_total');
  return t(`admin.incidents.analysisStatus.${filter}` as TranslationKey);
}

/** Chart-only accent for Analysed Incidents ratio (right axis labels + polyline). */
const COVERAGE_ACCENT = '#5b21b6';
const COVERAGE_LINE = '#7c3aed';

function summarizeRangeDisposition(
  buckets: DashboardTrendBucket[],
  ar?: IncidentAnalysisSummary
) {
  if (ar?.counts) {
    const c = ar.counts;
    const analyzed = c.analyzed ?? 0;
    const notAnalyzed = c.not_analyzed ?? 0;
    const unsupported = c.unsupported ?? 0;
    const noAnalysisNeeded = c.no_analysis_needed ?? 0;
    const merged = c.merged ?? 0;
    const incidents = analyzed + notAnalyzed + unsupported + noAnalysisNeeded + merged;
    return { incidents, analyzed, notAnalyzed, unsupported, noAnalysisNeeded };
  }
  let analyzed = 0;
  let notAnalyzed = 0;
  let unsupported = 0;
  let noAnalysisNeeded = 0;
  let dispositionTotal = 0;
  let notIncident = 0;
  for (const b of buckets) {
    analyzed += b.disposition_analyzed ?? 0;
    notAnalyzed += b.disposition_not_analyzed ?? 0;
    unsupported += b.disposition_unsupported ?? 0;
    noAnalysisNeeded += b.disposition_no_analysis_needed ?? 0;
    dispositionTotal += b.disposition_total ?? 0;
    notIncident += b.disposition_not_incident ?? 0;
  }
  return {
    incidents: dispositionTotal - notIncident,
    analyzed,
    notAnalyzed,
    unsupported,
    noAnalysisNeeded,
  };
}

function summarizeDashboardBuckets(
  buckets: DashboardTrendBucket[],
  rangeAnalysis?: { analyzed: number; not_analyzed: number; analysis_rate_pct: number | null }
) {
  let totalTickets = 0;
  let dispositionTotal = 0;
  let analyzed = 0;
  let notAnalyzed = 0;
  for (const b of buckets) {
    totalTickets += b.total_tickets;
    dispositionTotal += b.disposition_total ?? 0;
    analyzed += b.incidents_analyzed ?? 0;
    notAnalyzed += b.incidents_not_analyzed ?? 0;
  }
  const arAnalyzed = rangeAnalysis?.analyzed ?? analyzed;
  const arNotAnalyzed = rangeAnalysis?.not_analyzed ?? notAnalyzed;
  const denom = arAnalyzed + arNotAnalyzed;
  const coveragePct =
    rangeAnalysis?.analysis_rate_pct != null
      ? rangeAnalysis.analysis_rate_pct
      : denom > 0
        ? Math.round((1000 * arAnalyzed) / denom) / 10
        : null;
  return {
    totalTickets,
    dispositionTotal,
    coveragePct,
    incidentsAnalyzed: arAnalyzed,
    incidentsNotAnalyzed: arNotAnalyzed,
  };
}

function bucketAnalysisRatePct(b: DashboardTrendBucket): number | null {
  return b.incident_analysis_rate_pct ?? b.workspace_penetration_pct ?? null;
}

function defaultManualActivityTotals(): DashboardManualActivityRangeTotals {
  return {
    merge_events: 0,
    incidents_created: 0,
    ticket_links_manual: 0,
    workspace_links_manual: 0,
    not_incident_marks: 0,
  };
}

/** Keeps KPI values on one baseline when labels wrap (Range summary cards). */
const RANGE_SUMMARY_LABEL_STYLE: CSSProperties = {
  fontSize: 11,
  fontWeight: 600,
  color: '#9ca3af',
  textTransform: 'uppercase',
  letterSpacing: '0.04em',
  lineHeight: 1.35,
  minHeight: '2.7em',
};

const RANGE_SUMMARY_VALUE_STYLE: CSSProperties = {
  marginTop: 6,
  fontSize: 22,
  fontWeight: 700,
};

/** Coverage card sub-line so large % lines up with other KPI numbers. */
const RANGE_SUMMARY_COVERAGE_SUB_STYLE: CSSProperties = {
  marginTop: 4,
  minHeight: '1.35em',
  fontSize: 11,
  color: '#9ca3af',
  lineHeight: 1.35,
};

const ANALYSIS_STATUS_STYLE: Record<
  IncidentAnalysisStatus,
  { background: string; color: string; i18nKey: 'admin.incidents.analysisStatus.not_incident' | 'admin.incidents.analysisStatus.unsupported' | 'admin.incidents.analysisStatus.no_analysis_needed' | 'admin.incidents.analysisStatus.not_analyzed' | 'admin.incidents.analysisStatus.analyzed' | 'admin.incidents.analysisStatus.merged' }
> = {
  not_incident: { background: '#fffbeb', color: '#b45309', i18nKey: 'admin.incidents.analysisStatus.not_incident' },
  unsupported: { background: '#fff7ed', color: '#c2410c', i18nKey: 'admin.incidents.analysisStatus.unsupported' },
  no_analysis_needed: {
    background: '#f1f5f9',
    color: '#475569',
    i18nKey: 'admin.incidents.analysisStatus.no_analysis_needed',
  },
  not_analyzed: { background: '#eff6ff', color: '#1d4ed8', i18nKey: 'admin.incidents.analysisStatus.not_analyzed' },
  analyzed: { background: '#ecfdf5', color: '#047857', i18nKey: 'admin.incidents.analysisStatus.analyzed' },
  merged: { background: '#f3f4f6', color: '#4b5563', i18nKey: 'admin.incidents.analysisStatus.merged' },
};

function IncidentAnalysisStatusBadge({ status }: { status: IncidentAnalysisStatus }) {
  const { t } = useTranslation();
  const meta = ANALYSIS_STATUS_STYLE[status];
  return (
    <span
      style={{
        display: 'inline-block',
        fontSize: 11,
        fontWeight: 600,
        color: meta.color,
        background: meta.background,
        padding: '2px 8px',
        borderRadius: 6,
        whiteSpace: 'nowrap',
      }}
    >
      {t(meta.i18nKey)}
    </span>
  );
}

function TrendRangeSummary({ data }: { data: DashboardTicketTrendsResponse }) {
  const { t } = useTranslation();
  const ar = data.incident_analysis_summary;
  const s = summarizeDashboardBuckets(data.buckets, ar);
  const disp = summarizeRangeDisposition(data.buckets, ar);
  const gran = data.granularity ?? 'week';
  const bucketCount = data.buckets.length;
  const nPeriods = data.periods ?? bucketCount;
  const manual = data.manual_activity_range_totals ?? defaultManualActivityTotals();

  const DISPOSITION_SUMMARY_SERIES: {
    key: string;
    label: string;
    value: number;
    color: string;
  }[] = [
    {
      key: 'incidents',
      label: t('admin.incidents.volumeTrends.incidentsExclNotIncident'),
      value: disp.incidents,
      color: '#374151',
    },
    {
      key: 'analyzed',
      label: t('admin.incidents.volumeTrends.dispositionAnalyzed'),
      value: disp.analyzed,
      color: '#10b981',
    },
    {
      key: 'not_analyzed',
      label: t('admin.incidents.volumeTrends.dispositionNotAnalyzed'),
      value: disp.notAnalyzed,
      color: '#3b82f6',
    },
    {
      key: 'unsupported',
      label: t('admin.incidents.volumeTrends.dispositionUnsupported'),
      value: disp.unsupported,
      color: '#ea580c',
    },
    {
      key: 'no_analysis_needed',
      label: t('admin.incidents.volumeTrends.dispositionNoAnalysisNeeded'),
      value: disp.noAnalysisNeeded,
      color: '#64748b',
    },
  ];

  const MANUAL_SERIES: {
    key: keyof DashboardManualActivityRangeTotals;
    label: string;
    color: string;
  }[] = [
    { key: 'merge_events', label: 'Manual merges', color: '#0ea5e9' },
    { key: 'incidents_created', label: 'Manual incidents created', color: '#14b8a6' },
    { key: 'ticket_links_manual', label: 'Manual ticket links', color: '#a855f7' },
    { key: 'workspace_links_manual', label: 'Manual workspace links', color: '#f97316' },
    { key: 'not_incident_marks', label: 'Marked not an incident', color: '#ca8a04' },
  ];
  return (
    <div>
      <div style={{ marginBottom: 12, fontSize: 13, color: '#6b7280', lineHeight: 1.5 }}>
        <span style={{ fontWeight: 600, color: '#374151' }}>Range summary</span>
        {' · '}
        Last <span style={{ fontWeight: 600, color: '#1a1a2e' }}>{nPeriods}</span>{' '}
        {gran === 'month' ? 'calendar months' : 'weeks (ISO Mon–Sun)'}
        {' · '}
        {data.range.start_day} → {data.range.end_day} {data.range.timezone}
        {' · '}
        {bucketCount} bucket{bucketCount === 1 ? '' : 's'}
        {' · '}
        project <span style={{ fontWeight: 600, color: '#1a1a2e' }}>{data.project}</span>
      </div>
      <div
        style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(auto-fill, minmax(148px, 1fr))',
          gap: 12,
        }}
      >
        {[
          {
            key: 'tickets',
            label: t('admin.incidents.volumeTrends.tickets'),
            value: s.totalTickets,
            color: TICKETS_BAR_COLOR,
          },
        ].map((series) => (
          <div
            key={series.key}
            style={{
              borderRadius: 10,
              border: '1px solid #f0f0f5',
              padding: '12px 14px',
              background: '#fafafa',
            }}
          >
            <div style={RANGE_SUMMARY_LABEL_STYLE}>{series.label}</div>
            <div style={{ ...RANGE_SUMMARY_VALUE_STYLE, color: series.color }}>
              {series.value.toLocaleString()}
            </div>
            <div style={RANGE_SUMMARY_COVERAGE_SUB_STYLE} aria-hidden />
          </div>
        ))}
        {DISPOSITION_SUMMARY_SERIES.map((series) => (
          <div
            key={series.key}
            style={{
              borderRadius: 10,
              border: '1px solid #f0f0f5',
              padding: '12px 14px',
              background: '#fafafa',
            }}
          >
            <div style={RANGE_SUMMARY_LABEL_STYLE}>{series.label}</div>
            <div style={{ ...RANGE_SUMMARY_VALUE_STYLE, color: series.color }}>
              {series.value.toLocaleString()}
            </div>
            <div style={RANGE_SUMMARY_COVERAGE_SUB_STYLE} aria-hidden />
          </div>
        ))}
        <div
          style={{
            borderRadius: 10,
            border: '1px solid #f0f0f5',
            padding: '12px 14px',
            background: '#fafafa',
          }}
        >
          <div style={RANGE_SUMMARY_LABEL_STYLE}>{t('admin.incidents.volumeTrends.analyzedRatio')}</div>
          <div style={{ ...RANGE_SUMMARY_VALUE_STYLE, color: '#64748b' }}>
            {s.coveragePct != null ? `${s.coveragePct}%` : '—'}
          </div>
          <div style={RANGE_SUMMARY_COVERAGE_SUB_STYLE}>
            {s.incidentsAnalyzed + s.incidentsNotAnalyzed > 0
              ? `${s.incidentsAnalyzed} / (${s.incidentsNotAnalyzed} + ${s.incidentsAnalyzed})`
              : '\u00a0'}
          </div>
        </div>
      </div>
      <div
        style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(auto-fill, minmax(148px, 1fr))',
          gap: 12,
          marginTop: 10,
          marginBottom: 14,
        }}
      >
        {MANUAL_SERIES.map((series) => (
          <div
            key={series.key}
            style={{
              borderRadius: 10,
              border: '1px solid #f0f0f5',
              padding: '12px 14px',
              background: '#fafafa',
            }}
          >
            <div style={RANGE_SUMMARY_LABEL_STYLE}>{series.label}</div>
            <div style={{ ...RANGE_SUMMARY_VALUE_STYLE, color: series.color }}>
              {manual[series.key].toLocaleString()}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function incidentFlagLabelEn(f: string | null | undefined) {
  if (f === 'yes') return 'Yes';
  if (f === 'no') return 'No';
  if (f === 'unknown') return 'Unknown';
  return '—';
}

const TICK_DRILL_PAGE_SIZE = 40;

function TotalTicketsDrillTable({
  items,
  onSynced,
}: {
  items: DashboardTrendDrillTicketItem[];
  onSynced: () => void;
}) {
  const [qInput, setQInput] = useState('');
  const [appliedQ, setAppliedQ] = useState('');
  const [offset, setOffset] = useState(0);
  const [syncingKey, setSyncingKey] = useState<string | null>(null);
  const [creatingKey, setCreatingKey] = useState<string | null>(null);
  const [syncHint, setSyncHint] = useState<string | null>(null);
  const [syncErr, setSyncErr] = useState<string | null>(null);

  const [linkOpen, setLinkOpen] = useState(false);
  const [linkForKey, setLinkForKey] = useState('');
  const [linkTargetInput, setLinkTargetInput] = useState('');
  const [linkBusy, setLinkBusy] = useState(false);
  const [linkModalErr, setLinkModalErr] = useState<string | null>(null);
  const [jiraBrowseBase, setJiraBrowseBase] = useState('');

  useEffect(() => {
    let cancelled = false;
    void getAdminJiraSiteUrl().then((b) => {
      if (!cancelled) setJiraBrowseBase(b);
    });
    return () => {
      cancelled = true;
    };
  }, []);

  const filtered = useMemo(() => {
    const q = appliedQ.trim().toLowerCase();
    if (!q) return items;
    return items.filter(
      (r) =>
        r.issue_key.toLowerCase().includes(q) || (r.summary || '').toLowerCase().includes(q)
    );
  }, [items, appliedQ]);

  useEffect(() => {
    setOffset(0);
  }, [appliedQ, items]);

  const total = filtered.length;
  const pageRows = filtered.slice(offset, offset + TICK_DRILL_PAGE_SIZE);

  function applySearch() {
    setAppliedQ(qInput);
    setOffset(0);
  }

  async function handleSyncRow(issue_key: string) {
    setSyncingKey(issue_key);
    setSyncHint(null);
    setSyncErr(null);
    try {
      await syncJiraTicketStandalone(issue_key);
      setSyncHint(`Synced ${issue_key} from Jira`);
      onSynced();
    } catch (e) {
      setSyncErr(e instanceof Error ? e.message : 'Sync failed');
    } finally {
      setSyncingKey(null);
    }
  }

  async function handleCreateDossier(issue_key: string) {
    setCreatingKey(issue_key);
    setSyncHint(null);
    setSyncErr(null);
    try {
      const r = await createDossierForJiraTicket(issue_key);
      setSyncHint(
        r.created
          ? `Created incident dossier for ${issue_key}`
          : `Issue ${issue_key} already linked to dossier ${r.dossier_id}`
      );
      onSynced();
    } catch (e) {
      setSyncErr(e instanceof Error ? e.message : 'Create failed');
    } finally {
      setCreatingKey(null);
    }
  }

  function openLinkModal(issue_key: string) {
    setLinkForKey(issue_key);
    setLinkTargetInput('');
    setLinkModalErr(null);
    setSyncErr(null);
    setLinkOpen(true);
  }

  async function submitLinkToDossier() {
    const tid = linkTargetInput.trim();
    if (!tid) {
      setLinkModalErr('Dossier ID is required');
      return;
    }
    setLinkBusy(true);
    setLinkModalErr(null);
    try {
      await linkJiraTicketToDossier(linkForKey, tid);
      setSyncHint(`Linked ${linkForKey} to dossier`);
      setLinkOpen(false);
      onSynced();
    } catch (e) {
      setLinkModalErr(e instanceof Error ? e.message : 'Link failed');
    } finally {
      setLinkBusy(false);
    }
  }

  const rowActionsLocked = syncingKey !== null || creatingKey !== null || linkBusy;

  return (
    <div style={{ marginTop: 4 }}>
      <AdminModal
        opened={linkOpen}
        onClose={() => {
          if (!linkBusy) setLinkOpen(false);
        }}
        title={`Link ${linkForKey || 'issue'} to incident dossier`}
        width={480}
        disableBackdropClose={linkBusy}
        zIndexBackdrop={1050}
        zIndexContent={1051}
      >
        <p style={{ margin: '0 0 10px', fontSize: 13, color: '#6b7280', lineHeight: 1.5 }}>
          Enter the target incident dossier ID. The issue will be attached to that dossier.
        </p>
        <label style={{ display: 'block', fontSize: 13, fontWeight: 600, color: '#374151', marginBottom: 6 }}>
          Dossier ID
        </label>
        <input
          type="text"
          value={linkTargetInput}
          onChange={(e) => setLinkTargetInput(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Enter') void submitLinkToDossier();
          }}
          disabled={linkBusy}
          placeholder="Incident dossier id"
          style={{
            width: '100%',
            boxSizing: 'border-box',
            padding: '10px 12px',
            borderRadius: 8,
            border: '1px solid #e5e7eb',
            fontSize: 14,
            marginBottom: linkModalErr ? 10 : 16,
          }}
        />
        {linkModalErr && (
          <div
            style={{
              padding: 10,
              borderRadius: 8,
              background: '#fef2f2',
              color: '#991b1b',
              fontSize: 13,
              marginBottom: 14,
            }}
          >
            {linkModalErr}
          </div>
        )}
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10, flexWrap: 'wrap' }}>
          <button
            type="button"
            disabled={linkBusy}
            onClick={() => setLinkOpen(false)}
            style={{
              padding: '8px 16px',
              borderRadius: 8,
              border: '1px solid #e5e7eb',
              background: '#fff',
              color: '#374151',
              fontWeight: 600,
              fontSize: 14,
              cursor: linkBusy ? 'not-allowed' : 'pointer',
            }}
          >
            Cancel
          </button>
          <button
            type="button"
            disabled={linkBusy}
            onClick={() => void submitLinkToDossier()}
            style={{
              padding: '8px 16px',
              borderRadius: 8,
              border: 'none',
              background: 'linear-gradient(135deg, #5c5fbc, #7c7fdc)',
              color: '#fff',
              fontWeight: 600,
              fontSize: 14,
              cursor: linkBusy ? 'wait' : 'pointer',
            }}
          >
            {linkBusy ? 'Linking…' : 'Link'}
          </button>
        </div>
      </AdminModal>

      <div
        style={{
          display: 'flex',
          flexWrap: 'wrap',
          gap: 12,
          alignItems: 'center',
          marginBottom: 12,
        }}
      >
        <input
          type="search"
          placeholder="Search issue key or summary…"
          value={qInput}
          onChange={(e) => setQInput(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Enter') applySearch();
          }}
          style={{
            flex: '1 1 200px',
            maxWidth: 360,
            padding: '10px 12px',
            borderRadius: 8,
            border: '1px solid #e5e7eb',
            fontSize: 14,
          }}
        />
        <button
          type="button"
          onClick={applySearch}
          style={{
            padding: '10px 18px',
            borderRadius: 8,
            border: 'none',
            background: 'linear-gradient(135deg, #5c5fbc, #7c7fdc)',
            color: '#fff',
            fontWeight: 600,
            cursor: 'pointer',
            fontSize: 14,
          }}
        >
          Search
        </button>
      </div>

      {syncHint && (
        <div
          style={{
            padding: 10,
            borderRadius: 8,
            background: '#eff6ff',
            color: '#1d4ed8',
            marginBottom: 12,
            fontSize: 13,
          }}
        >
          {syncHint}
        </div>
      )}

      {syncErr && (
        <div
          style={{
            padding: 12,
            borderRadius: 8,
            background: '#fef2f2',
            color: '#991b1b',
            marginBottom: 12,
            fontSize: 14,
          }}
        >
          {syncErr}
        </div>
      )}

      <div style={{ marginBottom: 8, fontSize: 13, color: '#6b7280' }}>
        {total} {total === 1 ? 'row' : 'rows'}
        {items.length >= 500 ? (
          <span style={{ marginLeft: 8, color: '#9ca3af' }}>(up to 500 rows for this day)</span>
        ) : null}
      </div>

      <div
        style={{
          background: '#fff',
          borderRadius: 12,
          border: '1px solid #f0f0f5',
          overflow: 'auto',
        }}
      >
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 14 }}>
          <thead>
            <tr style={{ borderBottom: '1px solid #ececf4', textAlign: 'left' }}>
              <th style={{ padding: '12px 14px', color: '#6b7280', fontWeight: 600 }}>Issue</th>
              <th style={{ padding: '12px 14px', color: '#6b7280', fontWeight: 600 }}>Summary</th>
              <th style={{ padding: '12px 14px', color: '#6b7280', fontWeight: 600 }}>Status</th>
              <th style={{ padding: '12px 14px', color: '#6b7280', fontWeight: 600 }}>Type</th>
              <th style={{ padding: '12px 14px', color: '#6b7280', fontWeight: 600 }}>Incident flag</th>
              <th style={{ padding: '12px 14px', color: '#6b7280', fontWeight: 600 }}>Incident</th>
              <th
                style={{
                  padding: '12px 14px',
                  color: '#6b7280',
                  fontWeight: 600,
                  textAlign: 'right',
                }}
              >
                Actions
              </th>
            </tr>
          </thead>
          <tbody>
            {pageRows.map((row) => {
              const jiraHref = jiraIssueBrowseUrl(row.issue_key, jiraBrowseBase);
              return (
                <tr key={row.issue_key} style={{ borderBottom: '1px solid #f3f4f6' }}>
                  <td style={{ padding: '12px 14px', fontWeight: 600 }}>
                    {jiraHref ? (
                      <a
                        href={jiraHref}
                        target="_blank"
                        rel="noopener noreferrer"
                        style={{ color: '#4f46e5', textDecoration: 'none', fontWeight: 600 }}
                      >
                        {row.issue_key}
                      </a>
                    ) : (
                      row.issue_key
                    )}
                  </td>
                  <td style={{ padding: '12px 14px', color: '#374151', maxWidth: 280 }}>
                    <div
                      style={{
                        overflow: 'hidden',
                        textOverflow: 'ellipsis',
                        whiteSpace: 'nowrap',
                      }}
                      title={row.summary || ''}
                    >
                      {row.summary || '—'}
                    </div>
                  </td>
                  <td style={{ padding: '12px 14px', color: '#4b5563' }}>{row.status || '—'}</td>
                  <td style={{ padding: '12px 14px', color: '#4b5563' }}>{row.issue_type || '—'}</td>
                  <td style={{ padding: '12px 14px' }}>{incidentFlagLabelEn(row.jira_incident_flag)}</td>
                  <td style={{ padding: '12px 14px', minWidth: 140 }}>
                    {row.dossier_id ? (
                      <Link
                        href={`/admin/incidents/${encodeURIComponent(row.dossier_id)}`}
                        style={{ color: '#4f46e5', textDecoration: 'none', fontWeight: 500 }}
                      >
                        Open
                      </Link>
                    ) : (
                      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
                        <button
                          type="button"
                          disabled={rowActionsLocked}
                          onClick={() => void handleCreateDossier(row.issue_key)}
                          style={{
                            border: 'none',
                            background: 'none',
                            color: '#4f46e5',
                            cursor: rowActionsLocked ? 'wait' : 'pointer',
                            fontSize: 13,
                            fontWeight: 600,
                            padding: 0,
                          }}
                        >
                          {creatingKey === row.issue_key ? 'Creating…' : 'Create'}
                        </button>
                        <button
                          type="button"
                          disabled={rowActionsLocked}
                          onClick={() => openLinkModal(row.issue_key)}
                          style={{
                            border: 'none',
                            background: 'none',
                            color: '#4f46e5',
                            cursor: rowActionsLocked ? 'wait' : 'pointer',
                            fontSize: 13,
                            fontWeight: 600,
                            padding: 0,
                          }}
                        >
                          Link
                        </button>
                      </div>
                    )}
                  </td>
                  <td style={{ padding: '12px 14px', textAlign: 'right' }}>
                    <button
                      type="button"
                      disabled={rowActionsLocked}
                      onClick={() => void handleSyncRow(row.issue_key)}
                      style={{
                        border: 'none',
                        background: 'none',
                        color: '#4f46e5',
                        cursor: rowActionsLocked ? 'wait' : 'pointer',
                        fontSize: 13,
                        fontWeight: 600,
                      }}
                    >
                      {syncingKey === row.issue_key ? 'Syncing…' : 'Sync'}
                    </button>
                  </td>
              </tr>
              );
            })}
          </tbody>
        </table>
        {pageRows.length === 0 && (
          <p style={{ padding: 24, color: '#9ca3af', margin: 0, fontSize: 14 }}>No data</p>
        )}
      </div>

      {total > TICK_DRILL_PAGE_SIZE && (
        <div style={{ marginTop: 16, display: 'flex', gap: 12, alignItems: 'center' }}>
          <button
            type="button"
            disabled={offset <= 0}
            onClick={() => setOffset((o) => Math.max(0, o - TICK_DRILL_PAGE_SIZE))}
            style={{
              padding: '8px 14px',
              borderRadius: 8,
              border: '1px solid #e5e7eb',
              background: '#fff',
              cursor: offset <= 0 ? 'not-allowed' : 'pointer',
            }}
          >
            Previous
          </button>
          <span style={{ fontSize: 13, color: '#6b7280' }}>
            {total === 0 ? '0' : `${offset + 1}–${Math.min(offset + TICK_DRILL_PAGE_SIZE, total)}`} /{' '}
            {total}
          </span>
          <button
            type="button"
            disabled={offset + TICK_DRILL_PAGE_SIZE >= total}
            onClick={() => setOffset((o) => o + TICK_DRILL_PAGE_SIZE)}
            style={{
              padding: '8px 14px',
              borderRadius: 8,
              border: '1px solid #e5e7eb',
              background: '#fff',
              cursor: offset + TICK_DRILL_PAGE_SIZE >= total ? 'not-allowed' : 'pointer',
            }}
          >
            Next
          </button>
        </div>
      )}
    </div>
  );
}

/** API timestamps are UTC ISO strings; show YYYY-MM-DD in drill tables. */
function isoTimestampToDateOnly(iso: string): string {
  const s = (iso || '').trim();
  if (!s) return '—';
  if (/^\d{4}-\d{2}-\d{2}/.test(s)) return s.slice(0, 10);
  const d = new Date(s);
  if (Number.isNaN(d.getTime())) return s;
  return d.toISOString().slice(0, 10);
}

function drillDossiersAsIncidentRows(items: DashboardTrendDrillItem[]): IncidentListItem[] {
  return (items as unknown as Record<string, unknown>[]).map((r) => ({
    id: String(r.id ?? r.dossier_id ?? ''),
    primary_issue_key: String(r.primary_issue_key ?? '').trim() || undefined,
    title: String(r.title ?? ''),
    status: String(r.status ?? ''),
    merged_into_dossier_id: (r.merged_into_dossier_id as string | null) ?? null,
    created_at: String(r.created_at ?? ''),
    updated_at: String(r.updated_at ?? ''),
    issue_count: Number(r.issue_count ?? 0),
    derived_workspace_count: Number(r.derived_workspace_count ?? 0),
    manual_workspace_count: Number(r.manual_workspace_count ?? 0),
    manual_not_incident: Boolean(r.manual_not_incident),
    analysis_status: (r.analysis_status as IncidentAnalysisStatus | undefined) ?? undefined,
  }));
}

function IncidentsDataTable({
  rows,
  emptyHint,
  jiraBrowseBase,
  statusFilter,
  onStatusFilterChange,
}: {
  rows: IncidentListItem[];
  emptyHint?: string;
  jiraBrowseBase?: string;
  statusFilter?: DrillStatusFilter;
  onStatusFilterChange?: (filter: DrillStatusFilter) => void;
}) {
  const { t } = useTranslation();
  const activeFilter = statusFilter ?? 'all';
  const filteredRows =
    onStatusFilterChange != null
      ? rows.filter((row) => rowMatchesDrillStatusFilter(row, activeFilter))
      : rows;
  return (
    <div
      style={{
        background: '#fff',
        borderRadius: 12,
        border: '1px solid #f0f0f5',
        overflow: 'auto',
      }}
    >
      <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 14 }}>
        <thead>
          <tr style={{ borderBottom: '1px solid #ececf4', textAlign: 'left' }}>
            <th style={{ padding: '12px 14px', color: '#6b7280', fontWeight: 600 }}>
              {t('admin.incidents.drill.columnJira')}
            </th>
            <th
              style={{
                padding: '12px 14px',
                color: '#6b7280',
                fontWeight: 600,
                minWidth: 320,
                width: '38%',
              }}
            >
              Title
            </th>
            <th style={{ padding: '12px 14px', color: '#6b7280', fontWeight: 600 }}>
              {onStatusFilterChange ? (
                <label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontWeight: 600 }}>
                  {t('admin.incidents.analysisStatus.column')}
                  <select
                    value={activeFilter}
                    onChange={(e) => onStatusFilterChange(e.target.value as DrillStatusFilter)}
                    style={{
                      padding: '4px 8px',
                      borderRadius: 6,
                      border: '1px solid #e5e7eb',
                      fontSize: 12,
                      fontWeight: 500,
                      color: '#374151',
                      maxWidth: 180,
                    }}
                  >
                    {DRILL_STATUS_FILTER_OPTIONS.map((opt) => (
                      <option key={opt.value} value={opt.value}>
                        {t(opt.labelKey)}
                      </option>
                    ))}
                  </select>
                </label>
              ) : (
                t('admin.incidents.analysisStatus.column')
              )}
            </th>
            <th style={{ padding: '12px 14px', color: '#6b7280', fontWeight: 600 }}>Issues</th>
            <th style={{ padding: '12px 14px', color: '#6b7280', fontWeight: 600 }}>Workspace</th>
            <th style={{ padding: '12px 14px', color: '#6b7280', fontWeight: 600 }}>Created</th>
            <th style={{ padding: '12px 14px', color: '#6b7280', fontWeight: 600 }}>Updated</th>
          </tr>
        </thead>
        <tbody>
          {filteredRows.map((row) => {
            const issueKey = (row.primary_issue_key ?? '').trim();
            const jiraHref = issueKey ? jiraIssueBrowseUrl(issueKey, jiraBrowseBase) : null;
            return (
            <tr key={row.id} style={{ borderBottom: '1px solid #f3f4f6' }}>
              <td style={{ padding: '12px 14px', fontWeight: 600, whiteSpace: 'nowrap' }}>
                {issueKey ? (
                  jiraHref ? (
                    <a
                      href={jiraHref}
                      target="_blank"
                      rel="noopener noreferrer"
                      style={{ color: '#4f46e5', textDecoration: 'none', fontWeight: 600 }}
                    >
                      {issueKey}
                    </a>
                  ) : (
                    issueKey
                  )
                ) : (
                  <span style={{ color: '#9ca3af' }}>—</span>
                )}
              </td>
              <td
                style={{
                  padding: '12px 14px',
                  minWidth: 320,
                  maxWidth: 520,
                  verticalAlign: 'top',
                }}
              >
                <Link
                  href={`/admin/incidents/${encodeURIComponent(row.id)}`}
                  style={{
                    color: '#4f46e5',
                    fontWeight: 600,
                    textDecoration: 'none',
                    display: 'block',
                    lineHeight: 1.35,
                  }}
                  title={row.title || row.id}
                >
                  {row.title || row.id}
                </Link>
                <div style={{ fontSize: 11, color: '#9ca3af', marginTop: 2 }}>{row.id}</div>
              </td>
              <td style={{ padding: '12px 14px' }}>
                {row.analysis_status ? (
                  <IncidentAnalysisStatusBadge status={row.analysis_status} />
                ) : row.manual_not_incident ? (
                  <IncidentAnalysisStatusBadge status="not_incident" />
                ) : (
                  <span style={{ color: '#9ca3af' }}>—</span>
                )}
              </td>
              <td style={{ padding: '12px 14px' }}>{row.issue_count}</td>
              <td style={{ padding: '12px 14px' }}>
                derived {row.derived_workspace_count}
                {row.manual_workspace_count > 0 && (
                  <span style={{ marginLeft: 8, color: '#6b7280' }}>
                    + manual {row.manual_workspace_count}
                  </span>
                )}
              </td>
              <td style={{ padding: '12px 14px', color: '#6b7280', whiteSpace: 'nowrap' }}>
                {isoTimestampToDateOnly(row.created_at)}
              </td>
              <td style={{ padding: '12px 14px', color: '#6b7280', whiteSpace: 'nowrap' }}>
                {isoTimestampToDateOnly(row.updated_at)}
              </td>
            </tr>
            );
          })}
        </tbody>
      </table>
      {filteredRows.length === 0 && emptyHint && (
        <div style={{ padding: 32, textAlign: 'center', color: '#9ca3af', fontSize: 14 }}>{emptyHint}</div>
      )}
    </div>
  );
}

function TrendDrillDetail({
  metric,
  items,
  loading,
  error,
  onTicketsDrillRefresh,
  jiraBrowseBase,
  statusFilter,
  onStatusFilterChange,
}: {
  metric: DashboardTrendMetric;
  items: DashboardTrendDrillItem[];
  loading: boolean;
  error: string | null;
  onTicketsDrillRefresh?: () => void;
  jiraBrowseBase?: string;
  statusFilter?: DrillStatusFilter;
  onStatusFilterChange?: (filter: DrillStatusFilter) => void;
}) {
  const { t } = useTranslation();
  const scrollWrap = { overflowX: 'auto' as const, maxHeight: 'min(52vh, 480px)' };

  if (loading) {
    return <p style={{ color: '#6b7280', margin: '10px 0 0', fontSize: 13 }}>Loading detail…</p>;
  }

  if (error) {
    return (
      <div
        style={{
          marginTop: 10,
          padding: 10,
          borderRadius: 8,
          background: '#fef2f2',
          color: '#991b1b',
          fontSize: 13,
        }}
      >
        {error}
      </div>
    );
  }

  if (metric === 'total_tickets') {
    return (
      <div style={scrollWrap}>
        <TotalTicketsDrillTable
          items={items as DashboardTrendDrillTicketItem[]}
          onSynced={onTicketsDrillRefresh ?? (() => {})}
        />
      </div>
    );
  }

  if (
    metric === 'dossiers_created' ||
    metric === 'dossiers_workspace_linked_new' ||
    isDispositionDrillMetric(metric)
  ) {
    const rows = drillDossiersAsIncidentRows(items);
    const dispositionDrill = isDispositionDrillMetric(metric);
    return (
      <div style={scrollWrap}>
        <IncidentsDataTable
          rows={rows}
          jiraBrowseBase={jiraBrowseBase}
          emptyHint={
            items.length === 0
              ? 'No rows'
              : dispositionDrill && rows.length > 0
                ? t('admin.incidents.drill.emptyFiltered')
                : undefined
          }
          statusFilter={dispositionDrill ? statusFilter : undefined}
          onStatusFilterChange={dispositionDrill ? onStatusFilterChange : undefined}
        />
      </div>
    );
  }

  return (
    <div style={scrollWrap}>
      <div
        style={{
          background: '#fff',
          borderRadius: 12,
          border: '1px solid #f0f0f5',
          overflow: 'auto',
        }}
      >
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
          <thead>
            <tr style={{ borderBottom: '1px solid #ececf4', textAlign: 'left' }}>
              <th style={{ padding: '8px', color: '#6b7280' }}>Time</th>
              <th style={{ padding: '8px', color: '#6b7280' }}>Source</th>
              <th style={{ padding: '8px', color: '#6b7280' }}>Target</th>
              <th style={{ padding: '8px', color: '#6b7280' }}>Moved</th>
            </tr>
          </thead>
          <tbody>
            {(
              items as {
                at: string;
                source_dossier_id: string;
                canonical_target_dossier_id: string;
                issues_moved: number;
                issues_deduped: number;
              }[]
            ).map((r, idx) => (
              <tr
                key={`${r.source_dossier_id}-${r.canonical_target_dossier_id}-${idx}`}
                style={{ borderBottom: '1px solid #f3f4f6' }}
              >
                <td style={{ padding: '8px', color: '#9ca3af', whiteSpace: 'nowrap' }}>{r.at || '—'}</td>
                <td style={{ padding: '8px' }}>
                  {r.source_dossier_id ? (
                    <Link
                      href={`/admin/incidents/${encodeURIComponent(r.source_dossier_id)}`}
                      style={{ color: '#4f46e5', textDecoration: 'none' }}
                    >
                      {r.source_dossier_id.slice(0, 8)}…
                    </Link>
                  ) : (
                    '—'
                  )}
                </td>
                <td style={{ padding: '8px' }}>
                  {r.canonical_target_dossier_id ? (
                    <Link
                      href={`/admin/incidents/${encodeURIComponent(r.canonical_target_dossier_id)}`}
                      style={{ color: '#4f46e5', textDecoration: 'none' }}
                    >
                      {r.canonical_target_dossier_id.slice(0, 8)}…
                    </Link>
                  ) : (
                    '—'
                  )}
                </td>
                <td style={{ padding: '8px', color: '#374151' }}>
                  {r.issues_moved}
                  {r.issues_deduped ? ` (dedupe ${r.issues_deduped})` : ''}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
        {items.length === 0 && (
          <div style={{ padding: 24, textAlign: 'center', color: '#9ca3af', fontSize: 13 }}>No rows</div>
        )}
      </div>
    </div>
  );
}

const BAR_WIDTH_MAX_PX = 20;

function trendChartLayout(buckets: DashboardTrendBucket[], chartWidth: number) {
  const n = buckets.length;
  const padL = 46;
  const padR = 52;
  const W = Math.max(400, chartWidth);
  const innerW = W - padL - padR;
  const groupW = n > 0 ? innerW / n : innerW;
  return { n, padL, padR, W, groupW };
}

function bucketIndexFromClientX(
  clientX: number,
  svg: SVGSVGElement,
  n: number,
  padL: number,
  padR: number,
  groupW: number,
  W: number
): number | null {
  const bounds = svg.getBoundingClientRect();
  const x = clientX - bounds.left;
  const plotRight = W - padR;
  if (x < padL || x > plotRight || n === 0) return null;
  return Math.min(n - 1, Math.max(0, Math.floor((x - padL) / groupW)));
}

/** Y domain for ratio chart: pads data range, stays within [0, 100]. */
function penetrationPctAxisDomain(buckets: DashboardTrendBucket[]): { yMin: number; yMax: number } {
  const vals = buckets
    .map((b) => bucketAnalysisRatePct(b))
    .filter((p): p is number => typeof p === 'number' && !Number.isNaN(p));
  if (vals.length === 0) return { yMin: 0, yMax: 100 };
  let lo = Math.min(...vals);
  let hi = Math.max(...vals);
  if (lo === hi) {
    const pad = Math.max(4, Math.min(lo, 100 - lo) * 0.12);
    lo = Math.max(0, lo - pad);
    hi = Math.min(100, hi + pad);
  } else {
    const span = hi - lo;
    const pad = Math.max(0.5, span * 0.12);
    lo = Math.max(0, lo - pad);
    hi = Math.min(100, hi + pad);
  }
  if (hi - lo < 6) {
    const mid = (hi + lo) / 2;
    lo = Math.max(0, mid - 3);
    hi = Math.min(100, mid + 3);
  }
  return { yMin: lo, yMax: hi };
}

function IncidentCoverageTrendChart({
  buckets,
  chartWidth,
  granularity = 'week',
}: {
  buckets: DashboardTrendBucket[];
  chartWidth: number;
  granularity?: 'week' | 'month';
}) {
  const [covTip, setCovTip] = useState<null | { px: number; py: number; b: DashboardTrendBucket }>(null);
  const { n, padL, padR, W, groupW } = trendChartLayout(buckets, chartWidth);
  const Hcov = 146;
  const padTcov = 28;
  const padBcov = 24;
  const innerHcov = Hcov - padTcov - padBcov;
  const baselineYcov = padTcov + innerHcov;
  const { yMin: ratioYMin, yMax: ratioYMax } = penetrationPctAxisDomain(buckets);
  const ratioYSpan = ratioYMax - ratioYMin || 1;
  const scalePctYcov = (pct: number) =>
    padTcov + innerHcov - ((pct - ratioYMin) / ratioYSpan) * innerHcov;
  const labelEveryCov = n <= 14 ? 1 : n <= 45 ? Math.ceil(n / 14) : Math.ceil(n / 12);
  const ratioAxisTopPct = Math.round(ratioYMax);
  const ratioAxisBottomPct = Math.round(ratioYMin);

  const linePts: string[] = [];
  for (let i = 0; i < n; i++) {
    const b = buckets[i];
    const p = bucketAnalysisRatePct(b);
    if (p == null) continue;
    const cx = padL + i * groupW + groupW / 2;
    linePts.push(`${cx},${scalePctYcov(p)}`);
  }

  const handleCovSvgMove = (e: MouseEvent<SVGSVGElement>) => {
    const svg = e.currentTarget;
    const idx = bucketIndexFromClientX(e.clientX, svg, n, padL, padR, groupW, W);
    if (idx == null) {
      setCovTip(null);
      return;
    }
    setCovTip({ px: e.clientX, py: e.clientY, b: buckets[idx] });
  };

  return (
    <div style={{ width: '100%', position: 'relative' }}>
      <div
        style={{
          fontSize: 12,
          fontWeight: 700,
          color: COVERAGE_ACCENT,
          marginBottom: 6,
          letterSpacing: '0.02em',
        }}
      >
        Analysed Incidents ratio
      </div>
      <svg
        width={W}
        height={Hcov}
        style={{ display: 'block' }}
        onMouseMove={handleCovSvgMove}
        onMouseLeave={() => setCovTip(null)}
      >
        <line
          x1={padL}
          y1={baselineYcov}
          x2={W - padR}
          y2={baselineYcov}
          stroke="#ede9fe"
          strokeWidth={1}
        />
        <text x={4} y={scalePctYcov(ratioYMax) + 4} fontSize={10} fill="#9ca3af">
          {ratioAxisTopPct}%
        </text>
        <text x={4} y={baselineYcov + 4} fontSize={10} fill="#9ca3af">
          {ratioAxisBottomPct}%
        </text>
        {linePts.length > 1 && (
          <polyline
            fill="none"
            stroke={COVERAGE_LINE}
            strokeWidth={2}
            strokeLinecap="round"
            strokeLinejoin="round"
            points={linePts.join(' ')}
          />
        )}
        {linePts.length === 1 && (
          <circle
            cx={Number(linePts[0].split(',')[0])}
            cy={Number(linePts[0].split(',')[1])}
            r={3}
            fill={COVERAGE_LINE}
            stroke="#faf5ff"
            strokeWidth={1}
          />
        )}
        {buckets.map((b, i) => {
          const p = bucketAnalysisRatePct(b);
          if (p == null) {
            return (
              <text
                key={`cov-${b.day}`}
                x={padL + i * groupW + groupW / 2}
                y={baselineYcov - 4}
                fontSize={8}
                fill="#c4b5fd"
                textAnchor="middle"
              >
                —
              </text>
            );
          }
          const cx = padL + i * groupW + groupW / 2;
          const cy = scalePctYcov(p);
          return (
            <g key={`cov-${b.day}`}>
              <circle cx={cx} cy={cy} r={3} fill={COVERAGE_LINE} stroke="#fff" strokeWidth={1} />
              <text
                x={cx}
                y={cy - 8}
                fontSize={8}
                fontWeight={600}
                fill={COVERAGE_ACCENT}
                textAnchor="middle"
              >
                {p}%
              </text>
            </g>
          );
        })}
        {buckets.map((b, i) => {
          if (i % labelEveryCov !== 0) return null;
          const tick = granularity === 'month' ? b.day.slice(0, 7) : b.day.slice(5);
          return (
            <text
              key={`cov-axis-${b.day}`}
              x={padL + i * groupW + groupW / 2}
              y={baselineYcov + 12}
              fontSize={8}
              fill="#9ca3af"
              textAnchor="middle"
            >
              {tick}
            </text>
          );
        })}
      </svg>

      {covTip && (
        <div
          style={{
            position: 'fixed',
            left: Math.min(
              covTip.px + 14,
              typeof window !== 'undefined' ? window.innerWidth - 270 : covTip.px + 14
            ),
            top: covTip.py + 14,
            zIndex: 60,
            pointerEvents: 'none',
            background: 'rgba(17, 24, 39, 0.94)',
            color: '#f9fafb',
            padding: '10px 12px',
            borderRadius: 8,
            fontSize: 12,
            lineHeight: 1.45,
            maxWidth: 260,
            boxShadow: '0 10px 30px rgba(0,0,0,0.25)',
          }}
        >
          <div style={{ fontWeight: 700, marginBottom: 6 }}>
            {granularity === 'month'
              ? `Month ${covTip.b.day.slice(0, 7)}`
              : `Week of ${covTip.b.day}`}{' '}
            <span style={{ fontWeight: 500, color: '#d1d5db' }}>(UTC+8)</span>
          </div>
          <div style={{ color: '#e9d5ff', fontWeight: 600 }}>Analysed Incidents ratio</div>
          <div style={{ marginTop: 4 }}>
            {(() => {
              const a = covTip.b.incidents_analyzed ?? 0;
              const n = covTip.b.incidents_not_analyzed ?? 0;
              const pct = bucketAnalysisRatePct(covTip.b);
              if (a + n > 0) {
                return (
                  <strong style={{ color: '#f5f3ff' }}>
                    {a} / ({n} + {a}) = {pct ?? 0}%
                  </strong>
                );
              }
              return <strong style={{ color: '#f5f3ff' }}>—</strong>;
            })()}
          </div>
        </div>
      )}
    </div>
  );
}

function IncidentBarTrendsChart({
  buckets,
  chartWidth,
  onBarClick,
  selection,
  granularity = 'week',
}: {
  buckets: DashboardTrendBucket[];
  chartWidth: number;
  onBarClick: (day: string, metric: DashboardTrendMetric) => void;
  selection?: { day: string; metric: DashboardTrendMetric; statusFilter?: DrillStatusFilter } | null;
  granularity?: 'week' | 'month';
}) {
  const { t } = useTranslation();
  const [tip, setTip] = useState<null | { px: number; py: number; b: DashboardTrendBucket }>(null);

  const { n, padL, padR, W, groupW } = trendChartLayout(buckets, chartWidth);
  const padT = 16;
  const padB = 34;
  const H = 300;
  const innerH = H - padT - padB;
  const baselineY = padT + innerH;

  let maxTickets = 1;
  let maxDisposition = 1;
  for (const b of buckets) {
    maxTickets = Math.max(maxTickets, b.total_tickets);
    maxDisposition = Math.max(maxDisposition, bucketDisposition(b, 'disposition_total'));
  }

  const scaleTicketsY = (v: number) => baselineY - (v / maxTickets) * innerH;
  const scaleDispositionY = (v: number) => baselineY - (v / maxDisposition) * innerH;
  const labelEvery = n <= 14 ? 1 : n <= 45 ? Math.ceil(n / 14) : Math.ceil(n / 12);
  const barGap = 4;
  const barW = Math.min(BAR_WIDTH_MAX_PX, Math.max(8, (groupW - 12 - barGap) / 2));

  const handleBarSvgMove = (e: MouseEvent<SVGSVGElement>) => {
    const svg = e.currentTarget;
    const idx = bucketIndexFromClientX(e.clientX, svg, n, padL, padR, groupW, W);
    if (idx == null) {
      setTip(null);
      return;
    }
    setTip({ px: e.clientX, py: e.clientY, b: buckets[idx] });
  };

  return (
    <div style={{ width: '100%', position: 'relative' }}>
      <div
        style={{
          fontSize: 12,
          fontWeight: 700,
          color: '#374151',
          marginBottom: 10,
          letterSpacing: '0.02em',
        }}
      >
        {t('admin.incidents.volumeTrends.title')}
      </div>
      <div
        style={{
          display: 'flex',
          flexWrap: 'wrap',
          gap: '10px 14px',
          alignItems: 'center',
          marginBottom: 12,
          fontSize: 12,
        }}
      >
        <span style={{ color: '#4b5563', display: 'inline-flex', alignItems: 'center', gap: 6 }}>
          <span style={{ width: 10, height: 10, borderRadius: 2, background: TICKETS_BAR_COLOR }} />
          {t('admin.incidents.volumeTrends.tickets')}
        </span>
        {DISPOSITION_STACK.map((s) => (
          <span key={s.key} style={{ color: '#4b5563', display: 'inline-flex', alignItems: 'center', gap: 6 }}>
            <span style={{ width: 10, height: 10, borderRadius: 2, background: s.color }} />
            {t(s.i18nKey)}
          </span>
        ))}
      </div>
      <p style={{ margin: '0 0 10px', fontSize: 11, color: '#9ca3af', lineHeight: 1.45 }}>
        {t('admin.incidents.volumeTrends.clickHint')}
      </p>

      <svg
        width={W}
        height={H}
        style={{ display: 'block' }}
        onMouseMove={handleBarSvgMove}
        onMouseLeave={() => setTip(null)}
      >
        <line x1={padL} y1={baselineY} x2={W - padR} y2={baselineY} stroke="#e5e7eb" strokeWidth={1} />
        <text x={4} y={scaleTicketsY(maxTickets) + 4} fontSize={10} fill="#9ca3af">
          {maxTickets}
        </text>
        <text x={W - padR - 4} y={scaleDispositionY(maxDisposition) + 4} fontSize={10} fill="#9ca3af" textAnchor="end">
          {maxDisposition}
        </text>
        <text x={4} y={baselineY + 4} fontSize={10} fill="#9ca3af">
          0
        </text>
        {buckets.map((b, i) => {
          const clusterW = barW * 2 + barGap;
          const x0 = padL + i * groupW + (groupW - clusterW) / 2;
          const tickets = b.total_tickets;
          const ticketsTopY = scaleTicketsY(tickets);
          const stackTotal = bucketDisposition(b, 'disposition_total');
          const stackX = x0 + barW + barGap;
          const selectedTickets = selection?.day === b.day && selection.metric === 'total_tickets';
          const selectedDispositionMetric =
            selection?.day === b.day && selection.metric !== 'total_tickets'
              ? statusFilterToDispositionMetric(selection.statusFilter ?? 'all')
              : null;

          return (
            <g key={b.day}>
              <g>
                <rect
                  x={x0}
                  y={ticketsTopY}
                  width={barW}
                  height={Math.max(baselineY - ticketsTopY, 0)}
                  fill={TICKETS_BAR_COLOR}
                  opacity={tickets > 0 ? 0.92 : 0.22}
                  stroke={selectedTickets ? '#1e1b4b' : 'none'}
                  strokeWidth={selectedTickets ? 2 : 0}
                  vectorEffect="non-scaling-stroke"
                  style={{ cursor: tickets > 0 ? 'pointer' : 'default' }}
                  onClick={() => {
                    if (tickets > 0) onBarClick(b.day, 'total_tickets');
                  }}
                />
                <text
                  x={x0 + barW / 2}
                  y={tickets > 0 ? ticketsTopY - 4 : baselineY - 11}
                  fontSize={8}
                  fill="#4b5563"
                  textAnchor="middle"
                  style={{ pointerEvents: 'none' }}
                >
                  {tickets}
                </text>
              </g>
              <g>
                {(() => {
                  const totalBarH = baselineY - scaleDispositionY(stackTotal);
                  let yBottom = baselineY;
                  return DISPOSITION_STACK.map((seg) => {
                    const v = bucketDisposition(b, seg.key);
                    if (v <= 0) return null;
                    const segH = (v / stackTotal) * totalBarH;
                    const y = yBottom - segH;
                    yBottom = y;
                    const selected = selectedDispositionMetric === seg.key;
                    return (
                      <rect
                        key={seg.key}
                        x={stackX}
                        y={y}
                        width={barW}
                        height={Math.max(segH, 0)}
                        fill={seg.color}
                        opacity={0.92}
                        stroke={selected ? '#1e1b4b' : 'none'}
                        strokeWidth={selected ? 2 : 0}
                        vectorEffect="non-scaling-stroke"
                        style={{ cursor: 'pointer' }}
                        onClick={() => onBarClick(b.day, seg.key)}
                      />
                    );
                  });
                })()}
                {stackTotal <= 0 && (
                  <rect
                    x={stackX}
                    y={baselineY - 2}
                    width={barW}
                    height={2}
                    fill="#e5e7eb"
                    opacity={0.5}
                  />
                )}
                {stackTotal > 0 && (
                  <text
                    x={stackX + barW / 2}
                    y={scaleDispositionY(stackTotal) - 4}
                    fontSize={8}
                    fill="#374151"
                    fontWeight={600}
                    textAnchor="middle"
                    style={{ pointerEvents: 'none' }}
                  >
                    {stackTotal}
                  </text>
                )}
              </g>
            </g>
          );
        })}
        {buckets.map((b, i) => {
          if (i % labelEvery !== 0) return null;
          return (
            <text
              key={`bar-axis-${b.day}`}
              x={padL + i * groupW + groupW / 2}
              y={baselineY + 12}
              fontSize={8}
              fill="#9ca3af"
              textAnchor="middle"
            >
              {granularity === 'month' ? b.day.slice(0, 7) : b.day.slice(5)}
            </text>
          );
        })}
      </svg>

      {tip && (
        <div
          style={{
            position: 'fixed',
            left: Math.min(tip.px + 14, typeof window !== 'undefined' ? window.innerWidth - 270 : tip.px + 14),
            top: tip.py + 14,
            zIndex: 60,
            pointerEvents: 'none',
            background: 'rgba(17, 24, 39, 0.94)',
            color: '#f9fafb',
            padding: '10px 12px',
            borderRadius: 8,
            fontSize: 12,
            lineHeight: 1.45,
            maxWidth: 280,
            boxShadow: '0 10px 30px rgba(0,0,0,0.25)',
          }}
        >
          <div style={{ fontWeight: 700, marginBottom: 6 }}>
            {granularity === 'month'
              ? `Month ${tip.b.day.slice(0, 7)}`
              : `Week of ${tip.b.day}`}{' '}
            <span style={{ fontWeight: 500, color: '#d1d5db' }}>(UTC+8)</span>
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between', gap: 12 }}>
            <span style={{ color: '#d1d5db' }}>{t('admin.incidents.volumeTrends.tickets')}</span>
            <span style={{ fontWeight: 600 }}>{tip.b.total_tickets}</span>
          </div>
          <div style={{ marginTop: 8, paddingTop: 6, borderTop: '1px solid rgba(255,255,255,0.12)' }}>
            <div style={{ fontWeight: 600, marginBottom: 4 }}>
              {t('admin.incidents.volumeTrends.incidentsTotal')}: {bucketDisposition(tip.b, 'disposition_total')}
            </div>
            {DISPOSITION_STACK.map((seg) => {
              const v = bucketDisposition(tip.b, seg.key);
              if (v <= 0) return null;
              return (
                <div key={seg.key} style={{ display: 'flex', justifyContent: 'space-between', gap: 12 }}>
                  <span style={{ color: '#d1d5db' }}>{t(seg.i18nKey)}</span>
                  <span style={{ fontWeight: 600 }}>{v}</span>
                </div>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}

function drillMetricTitle(
  t: (key: TranslationKey, vars?: Record<string, string>) => string,
  metric: DashboardTrendMetric,
): string {
  if (metric.startsWith('disposition_')) {
    return t(`admin.incidents.drill.metric.${metric}` as TranslationKey);
  }
  return METRIC_TITLE[metric];
}

function sectionCard(children: ReactNode) {
  return (
    <div
      style={{
        background: '#fff',
        borderRadius: 12,
        border: '1px solid #f0f0f5',
        padding: '14px 16px',
        marginBottom: 14,
        boxShadow: '0 2px 8px rgba(92,95,188,0.04)',
      }}
    >
      {children}
    </div>
  );
}

export default function AdminIncidentsPage() {
  const { t } = useTranslation();
  const chartWrapRef = useRef<HTMLDivElement>(null);
  const [chartWidth, setChartWidth] = useState(960);

  const [trendGranularity, setTrendGranularity] = useState<'week' | 'month'>('week');
  const [lookbackPeriods, setLookbackPeriods] = useState<number>(12);
  const [trendProject, setTrendProject] = useState('ALL');
  const [trendData, setTrendData] = useState<DashboardTicketTrendsResponse | null>(null);
  const [trendLoading, setTrendLoading] = useState(false);
  const [trendErr, setTrendErr] = useState<string | null>(null);

  const [drillSelection, setDrillSelection] = useState<{
    day: string;
    metric: DashboardTrendMetric;
    statusFilter?: DrillStatusFilter;
  } | null>(null);
  const [drillLoading, setDrillLoading] = useState(false);
  const [drillErr, setDrillErr] = useState<string | null>(null);
  const [drillItems, setDrillItems] = useState<DashboardTrendDrillItem[]>([]);
  const [maintenanceModalOpen, setMaintenanceModalOpen] = useState(false);
  const [analyzeModalOpen, setAnalyzeModalOpen] = useState(false);
  const [jiraBrowseBase, setJiraBrowseBase] = useState('');
  const [analyzeLoading, setAnalyzeLoading] = useState(false);
  const [analyzeError, setAnalyzeError] = useState<string | null>(null);
  const [analyzeData, setAnalyzeData] = useState<DeriveWorkspaceAnalyzeResponse | null>(null);
  const [selectedActionIds, setSelectedActionIds] = useState<Set<string>>(new Set());
  const [applyLoading, setApplyLoading] = useState(false);
  const [applyResult, setApplyResult] = useState<string | null>(null);

  useLayoutEffect(() => {
    const el = chartWrapRef.current;
    if (!el || typeof ResizeObserver === 'undefined') return;
    const ro = new ResizeObserver(() => {
      const w = Math.floor(el.getBoundingClientRect().width);
      setChartWidth(Math.max(400, w));
    });
    ro.observe(el);
    setChartWidth(Math.max(400, Math.floor(el.getBoundingClientRect().width)));
    return () => ro.disconnect();
  }, [trendData]);

  const loadTrends = useCallback(async () => {
    setTrendLoading(true);
    setTrendErr(null);
    try {
      const r = await fetchDashboardTicketTrends({
        periods: lookbackPeriods,
        project: trendProject,
        granularity: trendGranularity,
      });
      setTrendData(r);
      setDrillSelection(null);
      setDrillItems([]);
      setDrillErr(null);
    } catch (e) {
      setTrendErr(e instanceof Error ? e.message : 'Failed to load trends');
      setTrendData(null);
      setDrillSelection(null);
      setDrillItems([]);
      setDrillErr(null);
    } finally {
      setTrendLoading(false);
    }
  }, [lookbackPeriods, trendProject, trendGranularity]);

  const selectTrendDrill = useCallback(
    async (day: string, metric: DashboardTrendMetric) => {
      const dispositionDrill = isDispositionDrillMetric(metric);
      const statusFilter = dispositionDrill ? dispositionMetricToStatusFilter(metric) : undefined;
      const fetchMetric = dispositionDrill ? 'disposition_total' : metric;
      setDrillSelection({
        day,
        metric: fetchMetric,
        statusFilter,
      });
      setDrillLoading(true);
      setDrillErr(null);
      setDrillItems([]);
      try {
        const res = await fetchDashboardTicketTrendDrillDown({
          day,
          metric: fetchMetric,
          project: trendProject,
          granularity: trendGranularity,
          rangeFrom: trendData?.range.start_day,
          rangeTo: trendData?.range.end_day,
        });
        setDrillItems(res.items);
      } catch (e) {
        setDrillErr(e instanceof Error ? e.message : 'Failed to load drill-down');
      } finally {
        setDrillLoading(false);
      }
    },
    [trendProject, trendGranularity, trendData?.range.start_day, trendData?.range.end_day]
  );

  const setDrillStatusFilter = useCallback((filter: DrillStatusFilter) => {
    setDrillSelection((cur) => (cur ? { ...cur, statusFilter: filter } : cur));
  }, []);

  const refreshTrendDrill = useCallback(() => {
    if (!drillSelection) return;
    void selectTrendDrill(drillSelection.day, drillSelection.metric);
  }, [drillSelection, selectTrendDrill]);

  const exportDrillCsv = useCallback(() => {
    if (!drillSelection || drillItems.length === 0) return;
    const origin = typeof window !== 'undefined' ? window.location.origin : '';
    const day = drillSelection.day;
    const gran = trendGranularity;
    const metric = drillSelection.metric;
    const statusLabel = (status: IncidentAnalysisStatus) =>
      t(`admin.incidents.analysisStatus.${status}` as TranslationKey);

    let csv = '';
    let filename = `incidents-${day}-${gran}.csv`;

    if (metric === 'total_tickets') {
      csv = buildTotalTicketsDrillCsv(drillItems as DashboardTrendDrillTicketItem[], {
        jiraBrowseBase: jiraBrowseBase,
        pageOrigin: origin,
        labels: {
          issueKey: t('admin.incidents.exportCsv.column.issueKey'),
          jiraUrl: t('admin.incidents.exportCsv.column.jiraUrl'),
          summary: t('admin.incidents.exportCsv.column.summary'),
          issueType: t('admin.incidents.exportCsv.column.issueType'),
          status: t('admin.incidents.exportCsv.column.status'),
          effectiveAt: t('admin.incidents.exportCsv.column.effectiveAt'),
          jiraIncidentFlag: t('admin.incidents.exportCsv.column.jiraIncidentFlag'),
          dossierId: t('admin.incidents.exportCsv.column.dossierId'),
          dossierUrl: t('admin.incidents.exportCsv.column.dossierUrl'),
        },
      });
      filename = `tickets-${day}-${gran}.csv`;
    } else if (metric === 'merged_issues_moved') {
      csv = buildMergeDrillCsv(drillItems as DashboardTrendDrillMergeItem[], {
        pageOrigin: origin,
        labels: {
          at: t('admin.incidents.exportCsv.column.at'),
          sourceDossierId: t('admin.incidents.exportCsv.column.sourceDossierId'),
          sourceDossierUrl: t('admin.incidents.exportCsv.column.sourceDossierUrl'),
          targetDossierId: t('admin.incidents.exportCsv.column.targetDossierId'),
          targetDossierUrl: t('admin.incidents.exportCsv.column.targetDossierUrl'),
          issuesMoved: t('admin.incidents.exportCsv.column.issuesMoved'),
          issuesDeduped: t('admin.incidents.exportCsv.column.issuesDeduped'),
        },
      });
      filename = `merges-${day}-${gran}.csv`;
    } else if (
      metric === 'dossiers_created' ||
      metric === 'dossiers_workspace_linked_new' ||
      isDispositionDrillMetric(metric)
    ) {
      let rows = drillDossiersAsIncidentRows(drillItems);
      if (isDispositionDrillMetric(metric) && drillSelection.statusFilter) {
        rows = rows.filter((row) =>
          rowMatchesDrillStatusFilter(row, drillSelection.statusFilter ?? 'all'),
        );
      }
      csv = buildIncidentDossiersCsv(rows, {
        jiraBrowseBase: jiraBrowseBase,
        pageOrigin: origin,
        labels: {
          issueKey: t('admin.incidents.exportCsv.column.issueKey'),
          jiraUrl: t('admin.incidents.exportCsv.column.jiraUrl'),
          title: t('admin.incidents.exportCsv.column.title'),
          dossierId: t('admin.incidents.exportCsv.column.dossierId'),
          dossierUrl: t('admin.incidents.exportCsv.column.dossierUrl'),
          analysisStatus: t('admin.incidents.exportCsv.column.analysisStatus'),
          dossierStatus: t('admin.incidents.exportCsv.column.dossierStatus'),
          issueCount: t('admin.incidents.exportCsv.column.issueCount'),
          derivedWorkspaceCount: t('admin.incidents.exportCsv.column.derivedWorkspaceCount'),
          manualWorkspaceCount: t('admin.incidents.exportCsv.column.manualWorkspaceCount'),
          mergedIntoDossierId: t('admin.incidents.exportCsv.column.mergedIntoDossierId'),
          mergedIntoDossierUrl: t('admin.incidents.exportCsv.column.mergedIntoDossierUrl'),
          createdAt: t('admin.incidents.exportCsv.column.createdAt'),
          updatedAt: t('admin.incidents.exportCsv.column.updatedAt'),
        },
        statusLabel: (row) => incidentAnalysisStatusForExport(row, statusLabel),
      });
    } else {
      return;
    }

    downloadCsvFile(filename, csv);
  }, [drillSelection, drillItems, trendGranularity, jiraBrowseBase, t]);

  const openAnalyzeModal = useCallback(async () => {
    if (!drillSelection) return;
    const dossierIds = drillItems
      .map((x) => {
        const row = x as unknown as Record<string, unknown>;
        const id = String((row.dossier_id ?? row.id ?? '') || '').trim();
        return id;
      })
      .filter((x) => x.length > 0);
    if (dossierIds.length === 0) return;
    setAnalyzeModalOpen(true);
    setAnalyzeLoading(true);
    setAnalyzeError(null);
    setAnalyzeData(null);
    setApplyResult(null);
    setSelectedActionIds(new Set());
    try {
      const out = await deriveWorkspaceAnalyze({
        dossier_ids: dossierIds,
        day: drillSelection.day,
        metric: drillSelection.metric,
        project: trendProject,
        granularity: trendGranularity,
        range_from: trendData?.range.start_day,
        range_to: trendData?.range.end_day,
      });
      setAnalyzeData(out);
      const ids = new Set<string>();
      out.proposals.forEach((p) => p.actions.forEach((a) => ids.add(a.action_id)));
      setSelectedActionIds(ids);
    } catch (e) {
      setAnalyzeError(e instanceof Error ? e.message : 'Analyze failed');
    } finally {
      setAnalyzeLoading(false);
    }
  }, [
    drillSelection,
    drillItems,
    trendProject,
    trendGranularity,
    trendData?.range.start_day,
    trendData?.range.end_day,
  ]);

  const applySelectedActions = useCallback(async () => {
    if (!analyzeData || selectedActionIds.size === 0) return;
    const selections: ApplySelection[] = analyzeData.proposals
      .map((p) => ({
        dossier_id: p.dossier_id,
        actions: p.actions.filter((a) => selectedActionIds.has(a.action_id)),
      }))
      .filter((x) => x.actions.length > 0);
    if (selections.length === 0) return;
    setApplyLoading(true);
    setApplyResult(null);
    try {
      const res = await deriveWorkspaceApply(selections);
      const applied = res.applied?.length ?? 0;
      const skipped = res.skipped?.length ?? 0;
      const errors = res.errors?.length ?? 0;
      const skipDetail =
        skipped > 0
          ? (res.skipped ?? [])
              .map((s) => String((s as { reason?: string }).reason ?? 'unknown'))
              .join(', ')
          : '';
      setApplyResult(
        `Applied ${applied}, skipped ${skipped}${skipDetail ? ` (${skipDetail})` : ''}, errors ${errors}`
      );
      void refreshTrendDrill();
      void loadTrends();
    } catch (e) {
      setApplyResult(e instanceof Error ? e.message : 'Apply failed');
    } finally {
      setApplyLoading(false);
    }
  }, [analyzeData, selectedActionIds, refreshTrendDrill, loadTrends]);

  useEffect(() => {
    void loadTrends();
  }, [loadTrends]);

  useEffect(() => {
    let cancelled = false;
    void getAdminJiraSiteUrl().then((b) => {
      if (!cancelled) setJiraBrowseBase(b);
    });
    return () => {
      cancelled = true;
    };
  }, []);

  useEffect(() => {
    if (!trendData?.projects) return;
    setTrendProject((cur) => {
      const pk = cur.trim().toUpperCase();
      if (pk === 'ALL') return 'ALL';
      return trendData.projects.some((p) => p.toUpperCase() === pk) ? cur : 'ALL';
    });
  }, [trendData]);

  return (
    <div style={{ paddingTop: 8 }}>
      <div style={{ marginBottom: 24 }}>
        <h1 style={{ margin: 0, fontSize: 22, fontWeight: 700, color: '#1a1a2e' }}>Incidents</h1>
        <p style={{ margin: '8px 0 0', fontSize: 14, color: '#6b7280', lineHeight: 1.55 }}>
          {t('admin.incidents.pageIntro')}
          <button
            type="button"
            onClick={() => setMaintenanceModalOpen(true)}
            style={{
              border: 'none',
              background: 'none',
              padding: 0,
              color: '#4f46e5',
              fontWeight: 500,
              fontSize: 'inherit',
              fontFamily: 'inherit',
              cursor: 'pointer',
              textDecoration: 'underline',
            }}
          >
            {t('admin.incidents.maintenanceLink')}
          </button>
          .
        </p>
      </div>

      <div style={{ marginBottom: 28 }}>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'center', marginBottom: 12 }}>
          <label style={{ fontSize: 13, color: '#374151', display: 'flex', alignItems: 'center', gap: 8 }}>
            Bucket
            <select
              value={trendGranularity}
              onChange={(e) => {
                const g = e.target.value === 'month' ? 'month' : 'week';
                setTrendGranularity(g);
                setLookbackPeriods((cur) => {
                  if (g === 'month') {
                    return LOOKBACK_MONTHS.includes(cur as (typeof LOOKBACK_MONTHS)[number]) ? cur : 6;
                  }
                  return LOOKBACK_WEEKS.includes(cur as (typeof LOOKBACK_WEEKS)[number]) ? cur : 12;
                });
              }}
              style={{ padding: '8px 10px', borderRadius: 8, border: '1px solid #e5e7eb', fontSize: 14 }}
            >
              <option value="week">Week</option>
              <option value="month">Month</option>
            </select>
          </label>
          <label style={{ fontSize: 13, color: '#374151', display: 'flex', alignItems: 'center', gap: 8 }}>
            {trendGranularity === 'week' ? 'Recent weeks' : 'Recent months'}
            <select
              value={lookbackPeriods}
              onChange={(e) => setLookbackPeriods(Number(e.target.value))}
              style={{ padding: '8px 10px', borderRadius: 8, border: '1px solid #e5e7eb', fontSize: 14 }}
            >
              {(trendGranularity === 'week' ? LOOKBACK_WEEKS : LOOKBACK_MONTHS).map((n) => (
                <option key={n} value={n}>
                  Last {n} {trendGranularity === 'week' ? 'weeks' : 'months'}
                </option>
              ))}
            </select>
          </label>
          <label style={{ fontSize: 13, color: '#374151', display: 'flex', alignItems: 'center', gap: 8 }}>
            Jira project
            <select
              value={trendProject}
              onChange={(e) => setTrendProject(e.target.value)}
              style={{ padding: '8px 10px', borderRadius: 8, border: '1px solid #e5e7eb', fontSize: 14 }}
            >
              <option value="ALL">ALL</option>
              {(trendData?.projects ?? []).map((pk) => (
                <option key={pk} value={pk}>
                  {pk}
                </option>
              ))}
            </select>
          </label>
          <button
            type="button"
            disabled={trendLoading}
            onClick={() => void loadTrends()}
            style={{
              padding: '8px 16px',
              borderRadius: 8,
              border: 'none',
              background: 'linear-gradient(135deg, #5c5fbc, #7c7fdc)',
              color: '#fff',
              fontWeight: 600,
              fontSize: 13,
              cursor: trendLoading ? 'wait' : 'pointer',
              opacity: trendLoading ? 0.7 : 1,
            }}
          >
            {trendLoading ? 'Loading…' : 'Refresh'}
          </button>
        </div>
        {trendErr && (
          <div
            style={{
              padding: 10,
              borderRadius: 8,
              background: '#fef2f2',
              color: '#991b1b',
              fontSize: 13,
              marginBottom: 12,
            }}
          >
            {trendErr}
          </div>
        )}
        <p style={{ margin: '0 0 12px', fontSize: 12, color: '#9ca3af', lineHeight: 1.55 }}>
          {t('admin.incidents.volumeTrends.chartHint')}
        </p>
        {trendLoading && !trendData && <p style={{ color: '#6b7280' }}>Loading chart…</p>}
        {trendData && (
          <>
            {sectionCard(<TrendRangeSummary data={trendData} />)}
            {trendData.buckets.length > 0 ? (
              <>
                {sectionCard(
                  <IncidentCoverageTrendChart
                    key={`cov-${trendData.range.start_day}-${trendData.range.end_day}-${trendGranularity}-${lookbackPeriods}`}
                    buckets={trendData.buckets}
                    chartWidth={chartWidth}
                    granularity={trendGranularity}
                  />
                )}
                {sectionCard(
                  <div ref={chartWrapRef} style={{ width: '100%' }}>
                    <IncidentBarTrendsChart
                      key={`bar-${trendData.range.start_day}-${trendData.range.end_day}-${trendProject}-${trendGranularity}-${lookbackPeriods}`}
                      buckets={trendData.buckets}
                      chartWidth={chartWidth}
                      granularity={trendGranularity}
                      selection={drillSelection}
                      onBarClick={(d, m) => void selectTrendDrill(d, m)}
                    />
                  </div>
                )}
                {drillSelection &&
                  sectionCard(
                    <>
                      <div
                        style={{
                          display: 'flex',
                          flexWrap: 'wrap',
                          alignItems: 'center',
                          justifyContent: 'space-between',
                          gap: 10,
                          marginBottom: 4,
                        }}
                      >
                        <div style={{ fontSize: 14, fontWeight: 700, color: '#1a1a2e' }}>
                          {isDispositionDrillMetric(drillSelection.metric) && drillSelection.statusFilter
                            ? drillStatusTitle(t, drillSelection.statusFilter)
                            : drillMetricTitle(t, drillSelection.metric)}{' '}
                          ·{' '}
                          {trendGranularity === 'week'
                            ? `week of ${drillSelection.day}`
                            : `month ${drillSelection.day.slice(0, 7)}`}{' '}
                          <span style={{ fontWeight: 500, fontSize: 12, color: '#9ca3af' }}>UTC+8</span>
                        </div>
                        <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 }}>
                          {(() => {
                            const canAnalyze =
                              drillSelection.metric === 'dossiers_created' ||
                              drillSelection.metric === 'dossiers_workspace_linked_new' ||
                              (isDispositionDrillMetric(drillSelection.metric) &&
                                (drillSelection.statusFilter === 'not_analyzed' ||
                                  drillSelection.statusFilter === 'all'));
                            if (!canAnalyze) return null;
                            return (
                              <button
                                type="button"
                                disabled={drillLoading || drillItems.length === 0}
                                onClick={() => void openAnalyzeModal()}
                                style={{
                                  padding: '6px 12px',
                                  borderRadius: 8,
                                  border: '1px solid #d1d5db',
                                  background: '#eef2ff',
                                  fontSize: 13,
                                  color: '#3730a3',
                                  cursor:
                                    drillLoading || drillItems.length === 0 ? 'not-allowed' : 'pointer',
                                }}
                              >
                                {t('admin.incidents.analyzeWithWorkspace')}
                              </button>
                            );
                          })()}
                          <button
                            type="button"
                            disabled={drillLoading || drillItems.length === 0}
                            onClick={exportDrillCsv}
                            style={{
                              padding: '6px 12px',
                              borderRadius: 8,
                              border: '1px solid #d1d5db',
                              background: '#fff',
                              fontSize: 13,
                              color: '#374151',
                              cursor:
                                drillLoading || drillItems.length === 0 ? 'not-allowed' : 'pointer',
                            }}
                          >
                            {t('admin.incidents.exportCsv')}
                          </button>
                          <button
                            type="button"
                            onClick={() => {
                              setDrillSelection(null);
                              setDrillItems([]);
                              setDrillErr(null);
                            }}
                            style={{
                              padding: '6px 12px',
                              borderRadius: 8,
                              border: '1px solid #e5e7eb',
                              background: '#fff',
                              fontSize: 13,
                              color: '#374151',
                              cursor: 'pointer',
                            }}
                          >
                            {t('admin.incidents.clearSelection')}
                          </button>
                        </div>
                      </div>
                      <TrendDrillDetail
                        metric={drillSelection.metric}
                        items={drillItems}
                        loading={drillLoading}
                        error={drillErr}
                        onTicketsDrillRefresh={refreshTrendDrill}
                        jiraBrowseBase={jiraBrowseBase}
                        statusFilter={drillSelection.statusFilter}
                        onStatusFilterChange={
                          isDispositionDrillMetric(drillSelection.metric)
                            ? setDrillStatusFilter
                            : undefined
                        }
                      />
                    </>
                  )}
              </>
            ) : (
              <p style={{ color: '#9ca3af', marginTop: 8 }}>No buckets in the selected range.</p>
            )}
          </>
        )}
      </div>

      <AdminModal
        opened={analyzeModalOpen}
        onClose={() => setAnalyzeModalOpen(false)}
        title={t('admin.incidents.analyze')}
        width={980}
        zIndexBackdrop={1042}
        zIndexContent={1043}
      >
        {analyzeError && (
          <div style={{ padding: 8, borderRadius: 8, background: '#fef2f2', color: '#991b1b', marginBottom: 12 }}>
            {analyzeError}
          </div>
        )}
        {analyzeData &&
        !analyzeLoading &&
        (analyzeData.proposals?.length ?? 0) === 0 &&
        Number(analyzeData.summary?.dossiers_scanned ?? 0) > 0 ? (
          (() => {
            const llm = (analyzeData.summary?.llm ?? {}) as Record<string, unknown>;
            const failed = Number(llm.failed ?? 0);
            const failures = (llm.failures ?? []) as Array<{ dossier_id?: string; error?: string }>;
            if (failed > 0 && failures[0]?.error) {
              return (
                <div
                  style={{
                    padding: 8,
                    borderRadius: 8,
                    background: '#fffbeb',
                    color: '#92400e',
                    marginBottom: 12,
                    fontSize: 13,
                  }}
                >
                  <div>{t('admin.incidents.analyzeEmpty', { count: String(analyzeData.summary?.dossiers_scanned ?? 0) })}</div>
                  <div style={{ marginTop: 6 }}>
                    {t('admin.incidents.analyzeLlmFailed', {
                      failed: String(failed),
                      error: String(failures[0].error ?? ''),
                    })}
                  </div>
                </div>
              );
            }
            return (
              <div
                style={{
                  padding: 10,
                  borderRadius: 8,
                  background: '#f0fdf4',
                  color: '#166534',
                  marginBottom: 12,
                  fontSize: 13,
                }}
              >
                {t('admin.incidents.analyzeNoChange')}
              </div>
            );
          })()
        ) : null}
        <div style={{ maxHeight: 420, overflow: 'auto', border: '1px solid #eef2ff', borderRadius: 10 }}>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead style={{ position: 'sticky', top: 0, background: '#f8fafc' }}>
              <tr>
                <th style={{ textAlign: 'left', padding: 8 }}>
                  <button
                    type="button"
                    onClick={() => {
                      if (!analyzeData || analyzeLoading) return;
                      const ids = new Set<string>();
                      analyzeData.proposals.forEach((p) => p.actions.forEach((a) => ids.add(a.action_id)));
                      setSelectedActionIds(ids);
                    }}
                    disabled={analyzeLoading}
                    style={{ fontSize: 12, cursor: analyzeLoading ? 'not-allowed' : 'pointer' }}
                  >
                    {t('admin.incidents.selectAll')}
                  </button>
                </th>
                <th style={{ textAlign: 'left', padding: 8 }}>TCOC</th>
                <th style={{ textAlign: 'left', padding: 8 }}>{t('admin.incidents.analyze.columnTitle')}</th>
                <th style={{ textAlign: 'left', padding: 8 }}>{t('admin.incidents.suggestedActions')}</th>
                <th style={{ textAlign: 'left', padding: 8 }}>{t('admin.incidents.reason')}</th>
              </tr>
            </thead>
            <tbody>
              {analyzeLoading ? (
                <tr>
                  <td colSpan={6} style={{ padding: 40, textAlign: 'center', color: '#6b7280', fontSize: 13 }}>
                    {t('admin.incidents.analyzeLoading')}
                  </td>
                </tr>
              ) : (
                (analyzeData?.proposals ?? []).flatMap((p) =>
                p.actions.map((a, idx) => {
                  const actionText =
                    a.type === 'merge_into'
                      ? `${t('admin.incidents.action.mergeInto')} ${a.target_dossier_id ?? ''}`
                      : a.type === 'link_workspaces'
                        ? `${t('admin.incidents.action.linkWorkspaces')} ${(a.workspace_ids ?? []).join(', ')}`
                        : a.type === 'mark_not_incident'
                          ? t('admin.incidents.action.markNotIncident')
                          : a.type === 'mark_unsupported_incident'
                            ? t('admin.incidents.action.markUnsupported')
                            : t('admin.incidents.action.markNoAnalysisNeeded');
                  const issueKey = p.primary_issue_key || '';
                  const jiraHref = idx === 0 ? jiraIssueBrowseUrl(issueKey, jiraBrowseBase) : null;
                  const showDossier = idx === 0;
                  return (
                    <tr key={`${p.dossier_id}-${a.action_id}`} style={{ borderTop: '1px solid #f1f5f9' }}>
                      <td style={{ padding: 8 }}>
                        <input
                          type="checkbox"
                          checked={selectedActionIds.has(a.action_id)}
                          onChange={(e) => {
                            setSelectedActionIds((prev) => {
                              const nx = new Set(prev);
                              if (e.target.checked) nx.add(a.action_id);
                              else nx.delete(a.action_id);
                              return nx;
                            });
                          }}
                        />
                      </td>
                      <td style={{ padding: 8, whiteSpace: 'nowrap' }}>
                        {showDossier ? (
                          jiraHref ? (
                            <a
                              href={jiraHref}
                              target="_blank"
                              rel="noopener noreferrer"
                              style={{ color: '#4f46e5', fontWeight: 600, textDecoration: 'none' }}
                            >
                              {issueKey || p.dossier_id}
                            </a>
                          ) : (
                            <span style={{ fontWeight: 600 }}>{issueKey || p.dossier_id}</span>
                          )
                        ) : null}
                      </td>
                      <td style={{ padding: 8, color: '#374151', maxWidth: 220 }}>
                        {showDossier ? (
                          <Link
                            href={`/admin/incidents/${encodeURIComponent(p.dossier_id)}`}
                            style={{
                              display: 'block',
                              overflow: 'hidden',
                              textOverflow: 'ellipsis',
                              color: '#4f46e5',
                              fontWeight: 500,
                              textDecoration: 'none',
                            }}
                            title={p.title || p.dossier_id}
                          >
                            {p.title || '—'}
                          </Link>
                        ) : null}
                      </td>
                      <td style={{ padding: 8 }}>{actionText}</td>
                      <td style={{ padding: 8, color: '#6b7280' }}>{a.reason || '-'}</td>
                    </tr>
                  );
                })
                )
              )}
            </tbody>
          </table>
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 14 }}>
          <button
            type="button"
            onClick={() => setSelectedActionIds(new Set())}
            style={{ padding: '6px 12px', borderRadius: 8, border: '1px solid #e5e7eb', background: '#fff', fontSize: 12 }}
          >
            {t('admin.incidents.clearAll')}
          </button>
          <button
            type="button"
            disabled={applyLoading || analyzeLoading || selectedActionIds.size === 0}
            onClick={() => void applySelectedActions()}
            style={{
              padding: '8px 14px',
              borderRadius: 8,
              border: 'none',
              background: '#4f46e5',
              color: '#fff',
              fontSize: 13,
              cursor: applyLoading || selectedActionIds.size === 0 ? 'not-allowed' : 'pointer',
              opacity: applyLoading || selectedActionIds.size === 0 ? 0.7 : 1,
            }}
          >
            {applyLoading ? 'Applying…' : t('admin.incidents.applySelected')}
          </button>
        </div>
        {applyResult && <p style={{ marginTop: 8, fontSize: 12, color: '#374151' }}>{applyResult}</p>}
      </AdminModal>

      <AdminModal
        opened={maintenanceModalOpen}
        onClose={() => setMaintenanceModalOpen(false)}
        title="Import & maintenance"
        width={720}
        zIndexBackdrop={1040}
        zIndexContent={1041}
      >
        <AdminMaintenancePanel />
      </AdminModal>
    </div>
  );
}
