'use client';

import { useEffect, useState, useCallback } from 'react';
import {
  Megaphone, Plus, Loader2, RefreshCw, Send, Mail, MessageCircle,
  CheckCircle2, XCircle, Clock, AlertTriangle, Trash2, X, Layers,
  Download, ChevronLeft, ChevronRight, RefreshCcw, WifiOff,
} from 'lucide-react';
import Link from 'next/link';
import FeatureGuard from '@client/components/FeatureGuard';
import { isFeatureLockedError } from '@client/utils/isFeatureLockedError';
import { isForbiddenError } from '@client/utils/isForbiddenError';
import PermissionDenied from '@client/components/ui/PermissionDenied';
import { usePageHeader } from '@client/contexts/PageHeaderContext';
import { useLanguage } from '@client/contexts/LanguageContext';
import { toast } from 'sonner';
import {
  listCampaigns, createCampaign, deleteCampaign, launchCampaign, cancelCampaign,
  testCampaign, getCampaignReport, listCampaignDeliveries, exportCampaignDeliveriesCsv,
  listSegments, previewSegment,
  type Campaign, type Segment, type AudienceRules, type CampaignReport,
  type ListDeliveriesResult,
} from '@client/api/marketing';
import { listBranches, type Branch } from '@client/api/branches';
import {
  listWhatsAppTemplates, syncWhatsAppTemplates,
  extractBodyVars, getHeaderText, getFooterText,
  type WhatsAppTemplate, type TemplateListResult,
} from '@client/api/whatsapp-templates';
import { WhatsAppTemplateBubble } from '@client/components/marketing/WhatsAppTemplateBubble';
import { useAuth } from '@client/contexts/AuthContext';
import { useCurrency } from '@client/hooks/useCurrency';
import { RuleEditor } from '@client/components/marketing/RuleEditor';

type ChannelChoice = 'email' | 'whatsapp' | 'both';

const STATUS_BADGE: Record<Campaign['status'], string> = {
  draft: 'bg-gray-100 text-gray-700',
  scheduled: 'bg-blue-100 text-blue-700',
  sending: 'bg-amber-100 text-amber-700',
  completed: 'bg-emerald-100 text-emerald-700',
  cancelled: 'bg-gray-100 text-gray-500',
  failed: 'bg-red-100 text-red-700',
};

function StatusBadge({ status }: { status: Campaign['status'] }) {
  return <span className={`text-xs px-2 py-0.5 rounded-full font-medium ${STATUS_BADGE[status]}`}>{status}</span>;
}

function ChannelIcon({ channel }: { channel: 'email' | 'whatsapp' }) {
  return channel === 'email' ? <Mail size={14} /> : <MessageCircle size={14} />;
}

/** Parse a positive-integer text input into a number for the wizard's
 *  optional throttle controls; returns null when blank or non-positive so
 *  the API treats it as "no per-campaign cap". */
function parsePositiveInt(raw: string): number | null {
  const trimmed = raw.trim();
  if (!trimmed) return null;
  const n = Number(trimmed);
  if (!Number.isFinite(n) || n <= 0) return null;
  return Math.floor(n);
}

/** crypto.randomUUID is supported in all evergreen browsers; tiny RFC4122 v4
 *  fallback for older runtimes (jsdom etc.) keeps the wizard working in tests. */
function newUuid(): string {
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
    return crypto.randomUUID();
  }
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    const r = (Math.random() * 16) | 0;
    return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
  });
}

// ─── Wizard ────────────────────────────────────────────────────────────────

interface WizardProps {
  segments: Segment[];
  branches: Branch[];
  /**
   * Pre-fill for the WhatsApp "send from branch" picker. Pass the literal
   * `sessionBranchId` from `useAuth()` so a specific-branch session keeps
   * its branch auto-selected, while an owner in "All branches" mode lands
   * on an empty placeholder and must explicitly pick a branch — otherwise
   * we'd silently ship a WhatsApp blast from `branches[0]`.
   */
  defaultBranchId: string | null;
  onClose: () => void;
  onCreated: () => void;
  t: (key: string, fallback?: string) => string;
}

function CampaignWizard({ segments, branches, defaultBranchId, onClose, onCreated, t }: WizardProps) {
  const { symbol: currencySymbol } = useCurrency();
  const [step, setStep] = useState<1 | 2 | 3 | 4>(1);
  const [name, setName] = useState('');
  const [channel, setChannel] = useState<ChannelChoice>('email');
  const [branchId, setBranchId] = useState<string>(defaultBranchId ?? '');
  const [segmentId, setSegmentId] = useState<string | ''>('');
  const [rules, setRules] = useState<AudienceRules>({ channel: 'any' });
  const [previewCount, setPreviewCount] = useState<number>(0);
  const [subject, setSubject] = useState('');
  const [bodyHtml, setBodyHtml] = useState('');
  const [templateName, setTemplateName] = useState('');
  const [templateLang, setTemplateLang] = useState('en');
  const [templateVarsRaw, setTemplateVarsRaw] = useState('');
  // Template picker state (Task #472)
  const [wabaConnected, setWabaConnected] = useState<boolean | null>(null);
  const [wabaChecking, setWabaChecking] = useState(false);
  const [wabaBannerDismissed, setWabaBannerDismissed] = useState(false);
  const [tplList, setTplList] = useState<WhatsAppTemplate[]>([]);
  const [tplLoading, setTplLoading] = useState(false);
  const [tplSyncing, setTplSyncing] = useState(false);
  const [selectedTpl, setSelectedTpl] = useState<WhatsAppTemplate | null>(null);
  const [tplVarInputs, setTplVarInputs] = useState<string[]>([]);
  const [tplSearch, setTplSearch] = useState('');
  const [scheduleMode, setScheduleMode] = useState<'now' | 'later'>('now');
  const [scheduledAt, setScheduledAt] = useState('');
  const [ignoreQuietHours, setIgnoreQuietHours] = useState(false);
  // Per-campaign throttle controls (Task #240). Empty string = no per-campaign
  // cap; the dispatcher's global per-(branch,channel) batch cap still applies.
  const [showAdvanced, setShowAdvanced] = useState(false);
  const [maxPerMinute, setMaxPerMinute] = useState<string>('');
  const [maxPerHour, setMaxPerHour] = useState<string>('');
  const [busy, setBusy] = useState(false);

  // Map the wizard's channel choice to the audience-side channel filter.
  // 'both' must request 'any' so we don't double-exclude customers who only
  // have one of email/phone — each fan-out campaign re-applies its own
  // channel filter at delivery time.
  const audienceChannel: AudienceRules['channel'] = channel === 'both' ? 'any' : channel;

  // Re-evaluate audience whenever the rule set / segment / channel changes.
  useEffect(() => {
    let cancelled = false;
    const effective: AudienceRules = segmentId
      ? { ...(segments.find((s) => s.id === segmentId)?.rules ?? {}), channel: audienceChannel }
      : { ...rules, channel: audienceChannel };
    previewSegment(effective, false).then((r) => { if (!cancelled) setPreviewCount(r.count); }).catch(() => { /* */ });
    return () => { cancelled = true; };
  }, [rules, segmentId, audienceChannel, segments]);

  // When branchId changes, reset all template picker state so a previous
  // selection from another branch can't leak into the new one.
  useEffect(() => {
    setSelectedTpl(null);
    setTemplateName('');
    setTemplateLang('en');
    setTplVarInputs([]);
    setTemplateVarsRaw('');
    setTplList([]);
    setTplSearch('');
    setWabaConnected(null);
    setWabaBannerDismissed(false);
  }, [branchId]);

  // When the wizard enters Step 3 and a WhatsApp channel is selected, check
  // whether the branch has credentials (WABA connected) and load cached templates.
  useEffect(() => {
    if (step !== 3) return;
    const needsWa = channel === 'whatsapp' || channel === 'both';
    if (!needsWa || !branchId) return;

    let cancelled = false;
    setWabaChecking(true);
    setTplLoading(true);

    listWhatsAppTemplates(branchId)
      .then((result: TemplateListResult) => {
        if (cancelled) return;
        setWabaConnected(result.connected);
        setTplList(result.templates);
      })
      .catch(() => {
        if (!cancelled) setWabaConnected(false);
      })
      .finally(() => {
        if (!cancelled) {
          setWabaChecking(false);
          setTplLoading(false);
        }
      });

    return () => { cancelled = true; };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [step, branchId, channel]);

  // Derive body variables whenever a template is selected.
  const bodyVarNums = selectedTpl ? extractBodyVars(selectedTpl) : [];

  function handleSelectTemplate(tpl: WhatsAppTemplate | null) {
    setSelectedTpl(tpl);
    if (!tpl) {
      setTemplateName('');
      setTemplateLang('en');
      setTplVarInputs([]);
      setTemplateVarsRaw('');
      return;
    }
    setTemplateName(tpl.name);
    setTemplateLang(tpl.language);
    const vars = extractBodyVars(tpl);
    setTplVarInputs(Array(vars.length).fill(''));
    setTemplateVarsRaw('');
  }

  function handleVarInput(idx: number, value: string) {
    const next = [...tplVarInputs];
    next[idx] = value;
    setTplVarInputs(next);
  }

  async function handleSyncTemplates() {
    if (!branchId) return;
    setTplSyncing(true);
    try {
      const list = await syncWhatsAppTemplates(branchId);
      setTplList(list);
      setWabaConnected(true);
      toast.success(t('marketing.campaign.tplSynced', `${list.length} templates synced`));
    } catch (e) {
      toast.error(e instanceof Error ? e.message : t('marketing.campaign.tplSyncFailed', 'Template sync failed'));
    } finally {
      setTplSyncing(false);
    }
  }

  // Filtered template list for the searchable dropdown.
  const filteredTpls = tplSearch.trim()
    ? tplList.filter((t) => `${t.name} ${t.language}`.toLowerCase().includes(tplSearch.toLowerCase()))
    : tplList;

  // Step gating — advancing requires step-specific minimum input so the
  // user can't reach Review with an obviously-broken draft.
  function canAdvance(): boolean {
    if (step === 1) {
      if (!name.trim()) return false;
      if ((channel === 'whatsapp' || channel === 'both') && !branchId) return false;
      return true;
    }
    if (step === 3) {
      const needsEmail = channel === 'email' || channel === 'both';
      const needsWhatsapp = channel === 'whatsapp' || channel === 'both';
      if (needsEmail && (!subject.trim() || !bodyHtml.trim())) return false;
      if (needsWhatsapp) {
        // Block if WABA is disconnected
        if (wabaConnected === false) return false;
        if (!templateName.trim()) return false;
      }
      return true;
    }
    return true;
  }

  async function onLaunch() {
    if (!name.trim()) return toast.error(t('marketing.campaign.nameRequired', 'Campaign name is required'));
    if ((channel === 'whatsapp' || channel === 'both') && !branchId) {
      return toast.error(t('marketing.campaign.branchRequired', 'Pick a branch — WhatsApp credentials live on the branch row'));
    }
    if ((channel === 'email' || channel === 'both') && (!subject.trim() || !bodyHtml.trim())) {
      return toast.error(t('marketing.campaign.emailContentRequired', 'Email subject and body are required'));
    }
    if ((channel === 'whatsapp' || channel === 'both') && !templateName.trim()) {
      return toast.error(t('marketing.campaign.templateRequired', 'WhatsApp template name is required'));
    }

    setBusy(true);
    try {
      const scheduleIso = scheduleMode === 'later' && scheduledAt ? new Date(scheduledAt).toISOString() : null;
      const launchGroupId = channel === 'both' ? newUuid() : null;
      const targets: Array<{ ch: 'email' | 'whatsapp'; suffix: string }> = channel === 'both'
        ? [{ ch: 'email', suffix: ' (email)' }, { ch: 'whatsapp', suffix: ' (whatsapp)' }]
        : [{ ch: channel, suffix: '' }];

      const created: Campaign[] = [];
      for (const tgt of targets) {
        const draft = await createCampaign({
          name: name.trim() + tgt.suffix,
          channel: tgt.ch,
          branchId: tgt.ch === 'whatsapp' ? branchId : (branchId || null),
          segmentId: segmentId || null,
          audienceRules: segmentId ? undefined : rules,
          subject:  tgt.ch === 'email' ? (subject.trim() || null) : null,
          bodyHtml: tgt.ch === 'email' ? (bodyHtml || null) : null,
          templateName: tgt.ch === 'whatsapp' ? (templateName.trim() || null) : null,
          templateLang: tgt.ch === 'whatsapp' ? (templateLang.trim() || null) : null,
          templateVars: tgt.ch === 'whatsapp'
            ? tplVarInputs.map((v) => v.trim())
            : [],
          scheduledAt: scheduleIso,
          ignoreQuietHours,
          launchGroupId,
          maxPerMinute: parsePositiveInt(maxPerMinute),
          maxPerHour:   parsePositiveInt(maxPerHour),
        });
        created.push(draft);
      }

      // Launch each created campaign. If any launch fails after others
      // succeeded, surface the partial state — the user can still cancel
      // the launched siblings from the list view.
      const launched = await Promise.allSettled(created.map((c) => launchCampaign(c.id, {
        sendNow: scheduleMode === 'now',
        scheduledAt: scheduleIso,
      })));
      const failed = launched.filter((r) => r.status === 'rejected');
      if (failed.length === 0) {
        toast.success(channel === 'both'
          ? t('marketing.campaign.launchedBoth', 'Email + WhatsApp campaigns launched — they will send shortly.')
          : t('marketing.campaign.launched', 'Campaign launched — dispatcher will start sending shortly'));
      } else {
        toast.error(t('marketing.campaign.launchPartial', 'Some siblings failed to launch — review the campaign list.'));
      }
      onCreated();
      onClose();
    } catch (e) {
      toast.error(e instanceof Error ? e.message : t('marketing.campaign.launchFailed', 'Failed to launch'));
    } finally { setBusy(false); }
  }

  const stepLabels = [
    t('marketing.campaign.stepChannel', 'Channel'),
    t('marketing.campaign.stepAudience', 'Audience'),
    t('marketing.campaign.stepContent', 'Content'),
    t('marketing.campaign.stepReview', 'Review & schedule'),
  ];

  return (
    <div className="fixed inset-0 z-50 bg-black/40 flex items-center justify-center p-4">
      <div className="bg-white rounded-xl w-full max-w-2xl max-h-[90vh] overflow-hidden flex flex-col">
        <div className="p-5 border-b flex items-center justify-between">
          <div>
            <div className="font-bold text-lg">{t('marketing.campaign.newTitle', 'New campaign')}</div>
            <div className="text-xs text-gray-500 mt-0.5">
              {t('marketing.campaign.stepOf', 'Step')} {step} {t('marketing.campaign.of', 'of')} 4 · {stepLabels[step - 1]}
            </div>
          </div>
          <button onClick={onClose} className="p-2 hover:bg-gray-100 rounded"><X size={18} /></button>
        </div>

        <div className="p-5 overflow-y-auto flex-1 space-y-4">
          {step === 1 && (
            <div className="space-y-4">
              <div>
                <label className="text-sm font-medium block mb-1">{t('marketing.campaign.name', 'Campaign name')}</label>
                <input className="input-field w-full" value={name} onChange={(e) => setName(e.target.value)}
                  placeholder={t('marketing.campaign.namePlaceholder', 'e.g. October VIP discount')} />
              </div>
              <div>
                <label className="text-sm font-medium block mb-2">{t('marketing.campaign.channel', 'Channel')}</label>
                <div className="grid grid-cols-3 gap-3">
                  {(['email', 'whatsapp', 'both'] as ChannelChoice[]).map((c) => (
                    <button key={c} type="button" onClick={() => setChannel(c)}
                      className={`p-4 border rounded-lg text-left flex items-center gap-3 ${channel === c ? 'border-blue-500 bg-blue-50' : 'border-gray-200'}`}>
                      {c === 'both' ? <Layers size={14} /> : <ChannelIcon channel={c} />}
                      <span className="font-medium capitalize">
                        {c === 'both' ? t('marketing.campaign.channelBoth', 'Email + WhatsApp') : c}
                      </span>
                    </button>
                  ))}
                </div>
                {channel === 'both' && (
                  <p className="text-xs text-gray-500 mt-2">
                    {t('marketing.campaign.bothHint', "Two linked campaigns will be created — one per channel — sharing a single report.")}
                  </p>
                )}
              </div>
              {(channel === 'whatsapp' || channel === 'both') && (
                <div>
                  <label className="text-sm font-medium block mb-1">{t('marketing.campaign.sendFromBranch', 'Send from branch')}</label>
                  <select className="input-field w-full" value={branchId} onChange={(e) => setBranchId(e.target.value)}>
                    {branches.length === 0
                      ? <option value="">{t('marketing.campaign.noBranches', 'No branches available')}</option>
                      : <option value="">{t('marketing.campaign.chooseBranch', '— Choose a branch —')}</option>}
                    {branches.map((b) => <option key={b.id} value={b.id}>{b.name}</option>)}
                  </select>
                  <p className="text-xs text-gray-500 mt-1">
                    {t('marketing.campaign.branchHint', 'WhatsApp credentials are configured per branch. Pick the branch whose WhatsApp number should appear as the sender.')}
                  </p>
                </div>
              )}
            </div>
          )}

          {step === 2 && (
            <div className="space-y-4">
              <div>
                <label className="text-sm font-medium block mb-1">{t('marketing.campaign.useAudience', 'Use a saved audience')}</label>
                <select className="input-field w-full" value={segmentId} onChange={(e) => setSegmentId(e.target.value)}>
                  <option value="">{t('marketing.campaign.defineRules', '— Define rules below —')}</option>
                  {segments.map((s) => <option key={s.id} value={s.id}>{s.name}</option>)}
                </select>
              </div>
              {!segmentId && (
                <div className="border-t pt-4">
                  <RuleEditor
                    rules={rules}
                    onChange={setRules}
                    branches={branches}
                    t={t}
                    currencySymbol={currencySymbol}
                    showChannel={false}
                    showOptOut={false}
                  />
                </div>
              )}
              <div className="text-sm text-gray-600 border-t pt-3">
                {t('marketing.campaign.reachableVia', 'Reachable customers via')} {channel === 'both'
                  ? t('marketing.campaign.eitherChannel', 'either channel')
                  : channel}: <strong>{previewCount}</strong>
              </div>
            </div>
          )}

          {step === 3 && (
            <div className="space-y-5">
              {(channel === 'email' || channel === 'both') && (
                <div className="space-y-3">
                  {channel === 'both' && (
                    <div className="text-xs font-semibold text-gray-700 flex items-center gap-2">
                      <Mail size={14} /> {t('marketing.campaign.emailSection', 'Email content')}
                    </div>
                  )}
                  <div>
                    <label className="text-sm font-medium block mb-1">{t('marketing.campaign.emailSubject', 'Subject')}</label>
                    <input className="input-field w-full" value={subject} onChange={(e) => setSubject(e.target.value)} />
                  </div>
                  <div>
                    <label className="text-sm font-medium block mb-1">{t('marketing.campaign.emailBody', 'HTML body')}</label>
                    <textarea className="input-field w-full font-mono text-sm" rows={8} value={bodyHtml} onChange={(e) => setBodyHtml(e.target.value)}
                      placeholder={t('marketing.campaign.emailBodyPlaceholder', "<p>Hi! Here's our latest offer…</p>")} />
                    <p className="text-xs text-gray-500 mt-1">{t('marketing.campaign.unsubscribeNote', 'An unsubscribe footer + List-Unsubscribe header are added automatically.')}</p>
                  </div>
                </div>
              )}
              {(channel === 'whatsapp' || channel === 'both') && (
                <div className="space-y-3">
                  {channel === 'both' && (
                    <div className="text-xs font-semibold text-gray-700 flex items-center gap-2 border-t pt-4">
                      <MessageCircle size={14} /> {t('marketing.campaign.whatsappSection', 'WhatsApp content')}
                    </div>
                  )}

                  {/* WABA connection check */}
                  {wabaChecking && (
                    <div className="flex items-center gap-2 text-xs text-gray-500">
                      <Loader2 size={13} className="animate-spin" />
                      {t('marketing.campaign.checkingWaba', 'Checking WhatsApp connection…')}
                    </div>
                  )}

                  {!wabaChecking && wabaConnected === false && !wabaBannerDismissed && (
                    <div className="flex items-start gap-2 bg-red-50 border border-red-200 rounded p-3 text-sm text-red-800">
                      <WifiOff size={16} className="mt-0.5 shrink-0" />
                      <div className="flex-1">
                        {t('marketing.campaign.wabaDisconnected', 'Your WhatsApp account is not connected.')}{' '}
                        <Link href="/whatsapp" className="underline font-medium" onClick={() => onClose()}>
                          {t('marketing.campaign.wabaGoConnect', 'Go to Settings → Channels → WhatsApp to connect')}
                        </Link>
                        {' '}{t('marketing.campaign.wabaThenReturn', 'and then come back.')}
                      </div>
                      <button
                        type="button"
                        className="shrink-0 text-red-500 hover:text-red-700"
                        onClick={() => setWabaBannerDismissed(true)}
                        aria-label="Dismiss"
                      >
                        <X size={14} />
                      </button>
                    </div>
                  )}

                  {!wabaChecking && wabaConnected !== false && (
                    <>
                      {/* Sync button + template list */}
                      <div className="flex items-center gap-2">
                        <button
                          type="button"
                          className="btn-ghost text-xs flex items-center gap-1.5"
                          onClick={handleSyncTemplates}
                          disabled={tplSyncing || !branchId}
                        >
                          {tplSyncing
                            ? <Loader2 size={13} className="animate-spin" />
                            : <RefreshCcw size={13} />}
                          {t('marketing.campaign.syncTemplates', 'Sync from Meta')}
                        </button>
                        {tplList.length > 0 && (
                          <span className="text-xs text-gray-500">
                            {tplList.length} {t('marketing.campaign.tplCount', 'templates cached')}
                          </span>
                        )}
                      </div>

                      {tplLoading && (
                        <div className="flex items-center gap-2 text-xs text-gray-400">
                          <Loader2 size={12} className="animate-spin" /> {t('marketing.campaign.loadingTpls', 'Loading templates…')}
                        </div>
                      )}

                      {!tplLoading && tplList.length === 0 && (
                        <p className="text-xs text-gray-500">
                          {t('marketing.campaign.noTplsCached', 'No templates cached yet — click "Sync from Meta" to load your approved templates.')}
                        </p>
                      )}

                      {!tplLoading && tplList.length > 0 && (
                        <div className="space-y-2">
                          <label className="text-sm font-medium block mb-1">
                            {t('marketing.campaign.selectTemplate', 'Template')}
                          </label>
                          <input
                            className="input-field w-full text-sm"
                            value={tplSearch}
                            onChange={(e) => setTplSearch(e.target.value)}
                            placeholder={t('marketing.campaign.searchTpls', 'Search templates…')}
                          />
                          <select
                            className="input-field w-full"
                            value={selectedTpl ? `${selectedTpl.name}|${selectedTpl.language}` : ''}
                            onChange={(e) => {
                              const val = e.target.value;
                              if (!val) { handleSelectTemplate(null); return; }
                              const found = tplList.find((tp) => `${tp.name}|${tp.language}` === val);
                              handleSelectTemplate(found ?? null);
                            }}
                          >
                            <option value="">{t('marketing.campaign.chooseTpl', '— Choose a template —')}</option>
                            {filteredTpls.map((tp) => (
                              <option key={`${tp.name}|${tp.language}`} value={`${tp.name}|${tp.language}`}>
                                {tp.name} · {tp.language}
                              </option>
                            ))}
                          </select>
                        </div>
                      )}

                      {/* Template detail: header/footer reference + variable inputs */}
                      {selectedTpl && (
                        <div className="space-y-3 border rounded-lg p-3 bg-gray-50">
                          {getHeaderText(selectedTpl) && (
                            <div>
                              <div className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-0.5">
                                {t('marketing.campaign.tplHeader', 'Header')}
                              </div>
                              <div className="text-sm text-gray-700">{getHeaderText(selectedTpl)}</div>
                            </div>
                          )}
                          {getFooterText(selectedTpl) && (
                            <div>
                              <div className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-0.5">
                                {t('marketing.campaign.tplFooter', 'Footer')}
                              </div>
                              <div className="text-sm text-gray-700">{getFooterText(selectedTpl)}</div>
                            </div>
                          )}
                          {bodyVarNums.length > 0 && (
                            <div className="space-y-2">
                              <div className="text-xs font-semibold text-gray-500 uppercase tracking-wide">
                                {t('marketing.campaign.tplVariables', 'Body variables')}
                              </div>
                              {bodyVarNums.map((n, idx) => (
                                <div key={n}>
                                  <label className="text-xs font-medium block mb-0.5 text-gray-700">
                                    {t('marketing.campaign.tplVar', 'Variable')} {`{{${n}}}`}
                                  </label>
                                  <input
                                    className="input-field w-full text-sm"
                                    value={tplVarInputs[idx] ?? ''}
                                    onChange={(e) => handleVarInput(idx, e.target.value)}
                                    placeholder={t('marketing.campaign.tplVarPlaceholder', 'e.g. Pizza Palace')}
                                  />
                                </div>
                              ))}
                            </div>
                          )}
                          {bodyVarNums.length === 0 && (
                            <p className="text-xs text-gray-500">
                              {t('marketing.campaign.tplNoVars', 'This template has no body variables.')}
                            </p>
                          )}
                        </div>
                      )}
                    </>
                  )}
                </div>
              )}
            </div>
          )}

          {step === 4 && (
            <div className="space-y-4">
              <div className="bg-gray-50 rounded p-3 text-sm space-y-1">
                <div><strong>{t('common.name', 'Name')}:</strong> {name}</div>
                <div>
                  <strong>{t('marketing.campaign.channel', 'Channel')}:</strong>{' '}
                  {channel === 'both' ? t('marketing.campaign.channelBoth', 'Email + WhatsApp') : channel}
                </div>
                <div><strong>{t('marketing.campaign.audience', 'Audience')}:</strong> {previewCount} {t('marketing.campaign.customers', 'customers')}</div>
                {channel === 'both' && (
                  <div className="text-xs text-gray-500 mt-2">
                    {t('marketing.campaign.bothNote', 'Two campaigns will be created and linked. Their reports merge into one view.')}
                  </div>
                )}
              </div>
              <div className="space-y-2">
                <label className="text-sm font-medium block">{t('marketing.campaign.send', 'Send')}</label>
                <label className="flex items-center gap-2 text-sm">
                  <input type="radio" checked={scheduleMode === 'now'} onChange={() => setScheduleMode('now')} />
                  {t('marketing.campaign.sendNow', 'Immediately')}
                </label>
                <label className="flex items-center gap-2 text-sm">
                  <input type="radio" checked={scheduleMode === 'later'} onChange={() => setScheduleMode('later')} />
                  {t('marketing.campaign.sendLater', 'Schedule for…')}
                </label>
                {scheduleMode === 'later' && (
                  <input type="datetime-local" className="input-field w-full" value={scheduledAt} onChange={(e) => setScheduledAt(e.target.value)} />
                )}
              </div>
              <label className="flex items-center gap-2 text-sm">
                <input type="checkbox" checked={ignoreQuietHours} onChange={(e) => setIgnoreQuietHours(e.target.checked)} />
                {t('marketing.campaign.ignoreQuiet', 'Ignore branch quiet hours (defaults to 10 PM – 8 AM local, configurable per branch)')}
              </label>

              {/* Per-campaign throttle controls (Task #240). Hidden behind a
                  disclosure because most operators won't need them — only
                  large sends benefit from a per-campaign cap. */}
              <div className="border-t pt-3">
                <button
                  type="button"
                  className="text-sm font-medium text-gray-700 hover:text-gray-900 flex items-center gap-1"
                  onClick={() => setShowAdvanced((v) => !v)}
                  aria-expanded={showAdvanced}
                >
                  <span>{showAdvanced ? '▾' : '▸'}</span>
                  {t('marketing.campaign.advancedToggle', 'Advanced')}
                </button>
                {showAdvanced && (
                  <div className="mt-3 space-y-3">
                    <p className="text-xs text-gray-500">
                      {t('marketing.campaign.throttleHint',
                        "Cap how fast this campaign sends so a big audience doesn't overwhelm WhatsApp. Leave blank to use the global default. Other campaigns from the same branch are not affected.")}
                    </p>
                    <div className="grid grid-cols-2 gap-3">
                      <div>
                        <label className="text-sm font-medium block mb-1">
                          {t('marketing.campaign.maxPerMinute', 'Max messages per minute')}
                        </label>
                        <input
                          type="number"
                          min={1}
                          step={1}
                          className="input-field w-full"
                          value={maxPerMinute}
                          onChange={(e) => setMaxPerMinute(e.target.value)}
                          placeholder={t('marketing.campaign.noCap', 'No cap')}
                        />
                      </div>
                      <div>
                        <label className="text-sm font-medium block mb-1">
                          {t('marketing.campaign.maxPerHour', 'Max messages per hour')}
                        </label>
                        <input
                          type="number"
                          min={1}
                          step={1}
                          className="input-field w-full"
                          value={maxPerHour}
                          onChange={(e) => setMaxPerHour(e.target.value)}
                          placeholder={t('marketing.campaign.noCap', 'No cap')}
                        />
                      </div>
                    </div>
                  </div>
                )}
              </div>
            </div>
          )}
        </div>

        <div className="p-4 border-t flex justify-between">
          <button className="btn-ghost" onClick={() => step > 1 ? setStep((s) => (s - 1) as 1 | 2 | 3) : onClose()}>
            {step === 1 ? t('common.cancel', 'Cancel') : t('common.back', 'Back')}
          </button>
          {step < 4 ? (
            <button
              className="btn-primary disabled:opacity-50"
              disabled={!canAdvance()}
              onClick={() => setStep((s) => (s + 1) as 2 | 3 | 4)}
            >
              {t('common.next', 'Next')}
            </button>
          ) : (
            <button className="btn-primary" onClick={onLaunch} disabled={busy}>
              {busy ? <Loader2 size={16} className="animate-spin" /> : t('marketing.campaign.launchAction', 'Launch')}
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── Test send dialog ──────────────────────────────────────────────────────

interface TestDialogProps {
  campaign: Campaign;
  onClose: () => void;
  t: (key: string, fallback?: string) => string;
}

function TestSendDialog({ campaign, onClose, t }: TestDialogProps) {
  const isEmail = campaign.channel === 'email';
  const [to, setTo] = useState('');
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Fetch the WhatsApp template for preview when applicable.
  // null = not yet fetched, false = fetched but not found, WhatsAppTemplate = found
  const [waTemplate, setWaTemplate] = useState<WhatsAppTemplate | null | false>(null);

  useEffect(() => {
    if (isEmail || !campaign.template_name || !campaign.branch_id) return;
    let alive = true;
    listWhatsAppTemplates(campaign.branch_id)
      .then((result) => {
        if (!alive) return;
        const found = result.templates.find(
          (tpl) => tpl.name === campaign.template_name && tpl.language === (campaign.template_lang ?? 'en'),
        ) ?? false;
        setWaTemplate(found);
      })
      .catch(() => { if (alive) setWaTemplate(false); });
    return () => { alive = false; };
  }, [isEmail, campaign.template_name, campaign.template_lang, campaign.branch_id]);

  async function onSend() {
    setError(null);
    setBusy(true);
    try {
      await testCampaign(campaign.id, to.trim() || undefined);
      toast.success(t('marketing.test.sent', 'Test message dispatched'));
      onClose();
    } catch (e) {
      // Surface the server-provided message inline so the user knows
      // exactly what to fix (e.g. "A recipient email is required for the
      // test send.") without dismissing the dialog.
      setError(e instanceof Error ? e.message : t('marketing.test.failed', 'Test failed'));
    } finally { setBusy(false); }
  }

  return (
    <div className="fixed inset-0 z-50 bg-black/40 flex items-center justify-center p-4">
      <div className="bg-white rounded-xl w-full max-w-md overflow-hidden flex flex-col">
        <div className="p-4 border-b flex items-center justify-between">
          <div className="font-bold flex items-center gap-2">
            <Send size={16} /> {t('marketing.test.title', 'Send test')}
          </div>
          <button onClick={onClose} className="p-2 hover:bg-gray-100 rounded"><X size={16} /></button>
        </div>
        <div className="p-4 space-y-3">
          <div className="text-sm text-gray-600">
            {isEmail
              ? t('marketing.test.emailIntro', 'Enter the email address that should receive this test. Leave blank to use your account email.')
              : t('marketing.test.whatsappIntro', 'Enter a WhatsApp phone number (with country code, e.g. +14155551234) for the test message.')}
          </div>
          <input
            className="input-field w-full"
            value={to}
            onChange={(e) => setTo(e.target.value)}
            placeholder={isEmail ? 'you@example.com' : '+14155551234'}
            type={isEmail ? 'email' : 'tel'}
            autoFocus
          />
          {!isEmail && campaign.template_name && (
            <div className="border-t pt-3">
              <div className="text-xs font-medium text-gray-500 mb-2 flex items-center gap-1">
                <MessageCircle size={12} />
                {t('marketing.test.templatePreview', 'Message preview')}
              </div>
              <WhatsAppTemplateBubble
                template={waTemplate}
                vars={campaign.template_vars}
                templateName={campaign.template_name}
                templateLang={campaign.template_lang}
                t={t}
                loadingText={t('marketing.test.loadingPreview', 'Loading preview…')}
              />
            </div>
          )}
          {error && (
            <div className="text-xs bg-red-50 border border-red-200 rounded p-2 text-red-700">
              <AlertTriangle size={12} className="inline mr-1" /> {error}
            </div>
          )}
        </div>
        <div className="p-3 border-t flex justify-end gap-2">
          <button className="btn-ghost" onClick={onClose}>{t('common.cancel', 'Cancel')}</button>
          <button
            className="btn-primary"
            onClick={onSend}
            disabled={busy || (!isEmail && !to.trim())}
          >
            {busy ? <Loader2 size={16} className="animate-spin" /> : t('marketing.test.send', 'Send test')}
          </button>
        </div>
      </div>
    </div>
  );
}

// ─── Report drawer ─────────────────────────────────────────────────────────

const PAGE_SIZE = 50;

function FailedTab({
  campaignId,
  t,
}: {
  campaignId: string;
  t: (key: string, fallback?: string) => string;
}) {
  const [data, setData] = useState<ListDeliveriesResult | null>(null);
  const [loading, setLoading] = useState(true);
  const [page, setPage] = useState(1);

  useEffect(() => {
    let alive = true;
    setLoading(true);
    listCampaignDeliveries(campaignId, { page, pageSize: PAGE_SIZE })
      .then((r) => { if (alive) setData(r); })
      .catch((e) => { if (alive) toast.error(e instanceof Error ? e.message : t('marketing.report.failedLoad', 'Failed to load deliveries')); })
      .finally(() => { if (alive) setLoading(false); });
    return () => { alive = false; };
  }, [campaignId, page, t]);

  const totalPages = data ? Math.max(1, Math.ceil(data.total / PAGE_SIZE)) : 1;

  return (
    <div className="space-y-3">
      <div className="flex items-center justify-between gap-2">
        <p className="text-xs text-gray-500">
          {t('marketing.report.failedHint',
            'Permanent failures (bad address, auth error, opted out) are shown in amber. Transient failures that exhausted retries are shown in red.')}
        </p>
        <button
          className="btn-ghost text-xs flex items-center gap-1 shrink-0"
          onClick={() => exportCampaignDeliveriesCsv(campaignId)}
          title={t('marketing.report.exportCsv', 'Export as CSV')}
        >
          <Download size={13} />
          {t('marketing.report.exportCsv', 'Export CSV')}
        </button>
      </div>

      {loading ? (
        <div className="text-center py-6"><Loader2 size={18} className="inline animate-spin" /></div>
      ) : !data || data.items.length === 0 ? (
        <div className="text-center py-8 text-sm text-gray-500">
          {t('marketing.report.noFailures', 'No failed or skipped deliveries.')}
        </div>
      ) : (
        <>
          <div className="border rounded divide-y text-sm overflow-hidden">
            <div className="grid grid-cols-[1fr_auto_auto] gap-x-3 px-3 py-2 bg-gray-50 text-xs font-medium text-gray-600">
              <span>{t('marketing.report.colRecipient', 'Recipient')}</span>
              <span>{t('marketing.report.colStatus', 'Status')}</span>
              <span className="text-right">{t('marketing.report.colAttempts', 'Tries')}</span>
            </div>
            {data.items.map((item) => (
              <div key={item.id} className="px-3 py-2 space-y-0.5">
                <div className="grid grid-cols-[1fr_auto_auto] gap-x-3 items-center">
                  <span className="truncate font-mono text-xs text-gray-800">{item.recipient}</span>
                  <span className={`text-xs px-2 py-0.5 rounded-full font-medium whitespace-nowrap ${
                    item.status === 'skipped'
                      ? 'bg-amber-100 text-amber-800'
                      : 'bg-red-100 text-red-700'
                  }`}>
                    {item.status === 'skipped'
                      ? t('marketing.report.statusPermanent', 'permanent')
                      : t('marketing.report.statusFailed', 'failed')}
                  </span>
                  <span className="text-xs text-gray-500 text-right">{item.attempts}</span>
                </div>
                {item.last_error && (
                  <p className="text-xs text-gray-500 truncate" title={item.last_error}>
                    {item.last_error}
                  </p>
                )}
              </div>
            ))}
          </div>

          <div className="flex items-center justify-between text-xs text-gray-500">
            <span>
              {t('marketing.report.showing', 'Showing')}{' '}
              {(page - 1) * PAGE_SIZE + 1}–{Math.min(page * PAGE_SIZE, data.total)}{' '}
              {t('marketing.report.of', 'of')} {data.total}
            </span>
            <div className="flex items-center gap-1">
              <button
                className="p-1 hover:bg-gray-100 rounded disabled:opacity-40"
                disabled={page <= 1}
                onClick={() => setPage((p) => p - 1)}
                aria-label={t('common.previous', 'Previous page')}
              >
                <ChevronLeft size={14} />
              </button>
              <span>{page} / {totalPages}</span>
              <button
                className="p-1 hover:bg-gray-100 rounded disabled:opacity-40"
                disabled={page >= totalPages}
                onClick={() => setPage((p) => p + 1)}
                aria-label={t('common.next', 'Next page')}
              >
                <ChevronRight size={14} />
              </button>
            </div>
          </div>
        </>
      )}
    </div>
  );
}

function ReportDrawer({ campaignId, onClose, t }: { campaignId: string; onClose: () => void; t: (key: string, fallback?: string) => string }) {
  const [report, setReport] = useState<CampaignReport | null>(null);
  const [loading, setLoading] = useState(true);
  const [activeTab, setActiveTab] = useState<'overview' | 'failures'>('overview');
  const [waTemplate, setWaTemplate] = useState<WhatsAppTemplate | null | false>(null);

  useEffect(() => {
    let alive = true;
    const tick = async () => {
      try { const r = await getCampaignReport(campaignId); if (alive) setReport(r); }
      catch (e) { if (alive) toast.error(e instanceof Error ? e.message : t('marketing.report.failed', 'Report failed')); }
      finally { if (alive) setLoading(false); }
    };
    void tick();
    const id = setInterval(tick, 5000);
    return () => { alive = false; clearInterval(id); };
  }, [campaignId, t]);

  // Stable identifiers extracted from the first loaded report — these don't
  // change across the 5-second polling ticks, so we can use them as effect
  // dependencies to avoid re-fetching the template list on every refresh.
  const waCampaignChannel  = report?.campaign.channel ?? null;
  const waTemplateName     = report?.campaign.template_name ?? null;
  const waTemplateLang     = report?.campaign.template_lang ?? null;
  const waBranchId         = report?.campaign.branch_id ?? null;

  // Fetch the cached template once the identifiers are known for a WhatsApp
  // campaign.
  // waTemplate === null  → not yet fetched
  // waTemplate === false → fetched but not found in cache (deleted / never synced)
  // waTemplate === WhatsAppTemplate → found
  useEffect(() => {
    if (waCampaignChannel !== 'whatsapp' || !waTemplateName || !waBranchId) return;
    let alive = true;
    listWhatsAppTemplates(waBranchId)
      .then((result) => {
        if (!alive) return;
        const found = result.templates.find(
          (tpl) => tpl.name === waTemplateName && tpl.language === (waTemplateLang ?? 'en'),
        ) ?? false;
        setWaTemplate(found);
      })
      .catch(() => { if (alive) setWaTemplate(false); });
    return () => { alive = false; };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [waCampaignChannel, waTemplateName, waTemplateLang, waBranchId]);

  const failureCount = report ? report.totals.failed + report.totals.skipped : 0;

  return (
    <div className="fixed inset-0 z-50 bg-black/40 flex items-center justify-center p-4">
      <div className="bg-white rounded-xl w-full max-w-2xl max-h-[90vh] overflow-hidden flex flex-col">
        <div className="p-5 border-b flex items-center justify-between">
          <div className="font-bold text-lg">
            {t('marketing.report.title', 'Campaign report')}
            {report?.campaign.launch_group_id && (
              <span className="ml-2 text-xs text-gray-500 inline-flex items-center gap-1">
                <Layers size={12} /> {t('marketing.report.combined', 'combined Email + WhatsApp')}
              </span>
            )}
          </div>
          <button onClick={onClose} className="p-2 hover:bg-gray-100 rounded"><X size={18} /></button>
        </div>

        <div className="border-b px-5 flex gap-1">
          <button
            onClick={() => setActiveTab('overview')}
            className={`py-2.5 px-3 text-sm font-medium border-b-2 -mb-px transition-colors ${
              activeTab === 'overview'
                ? 'border-blue-600 text-blue-700'
                : 'border-transparent text-gray-500 hover:text-gray-700'
            }`}
          >
            {t('marketing.report.tabOverview', 'Overview')}
          </button>
          <button
            onClick={() => setActiveTab('failures')}
            className={`py-2.5 px-3 text-sm font-medium border-b-2 -mb-px transition-colors flex items-center gap-1.5 ${
              activeTab === 'failures'
                ? 'border-red-500 text-red-700'
                : 'border-transparent text-gray-500 hover:text-gray-700'
            }`}
          >
            {t('marketing.report.tabFailures', 'Failed / Skipped')}
            {failureCount > 0 && (
              <span className="text-xs bg-red-100 text-red-700 px-1.5 py-0.5 rounded-full font-medium">
                {failureCount}
              </span>
            )}
          </button>
        </div>

        <div className="p-5 overflow-y-auto flex-1 space-y-4">
          {activeTab === 'overview' && (
            <>
              {loading || !report ? (
                <div className="text-center py-8"><Loader2 size={20} className="inline animate-spin" /></div>
              ) : (
                <>
                  <div className="grid grid-cols-5 gap-3">
                    <div className="text-center"><div className="text-xs text-gray-500">{t('marketing.report.queued', 'Queued')}</div><div className="text-xl font-bold">{report.totals.queued}</div></div>
                    <div className="text-center"><div className="text-xs text-gray-500">{t('marketing.report.sending', 'Sending')}</div><div className="text-xl font-bold">{report.totals.sending}</div></div>
                    <div className="text-center"><div className="text-xs text-gray-500">{t('marketing.report.sent', 'Sent')}</div><div className="text-xl font-bold text-emerald-600">{report.totals.sent}</div></div>
                    <div className="text-center"><div className="text-xs text-gray-500">{t('marketing.report.failed', 'Failed')}</div><div className="text-xl font-bold text-red-600">{report.totals.failed}</div></div>
                    <div className="text-center"><div className="text-xs text-gray-500">{t('marketing.report.skipped', 'Skipped')}</div><div className="text-xl font-bold">{report.totals.skipped}</div></div>
                  </div>
                  {failureCount > 0 && (
                    <div className="text-xs bg-amber-50 border border-amber-200 rounded p-2 text-amber-800 flex items-center justify-between gap-2">
                      <span>
                        <AlertTriangle size={12} className="inline mr-1" />
                        {t('marketing.report.failureHint',
                          'Some recipients could not be reached. Open the "Failed / Skipped" tab to see details and export bad addresses.')}
                      </span>
                      <button
                        className="text-xs font-medium text-amber-900 underline whitespace-nowrap"
                        onClick={() => setActiveTab('failures')}
                      >
                        {t('marketing.report.viewFailures', 'View details')}
                      </button>
                    </div>
                  )}
                  {report.campaign.channel === 'whatsapp' && report.campaign.template_name && (
                    <div className="border-t pt-3">
                      <div className="text-sm font-medium mb-2 flex items-center gap-1.5">
                        <MessageCircle size={14} />
                        {t('marketing.report.templatePreview', 'Message template')}
                      </div>
                      <WhatsAppTemplateBubble
                        template={waTemplate}
                        vars={report.campaign.template_vars}
                        templateName={report.campaign.template_name}
                        templateLang={report.campaign.template_lang}
                        t={t}
                      />
                    </div>
                  )}
                  <div className="border-t pt-3">
                    <div className="text-sm font-medium mb-2">{t('marketing.report.recent', 'Recent deliveries')}</div>
                    <div className="divide-y border rounded">
                      {report.recent.length === 0 && <div className="p-3 text-sm text-gray-500">{t('marketing.report.noDeliveries', 'No deliveries yet.')}</div>}
                      {report.recent.map((r) => (
                        <div key={r.id} className="p-2 text-sm flex items-center justify-between gap-2">
                          <span className="truncate flex-1">{r.recipient}</span>
                          <span className={`text-xs px-2 py-0.5 rounded ${r.status === 'sent' ? 'bg-emerald-100 text-emerald-700' : r.status === 'failed' ? 'bg-red-100 text-red-700' : r.status === 'skipped' ? 'bg-amber-100 text-amber-800' : 'bg-gray-100 text-gray-700'}`}>
                            {r.status}
                          </span>
                        </div>
                      ))}
                    </div>
                  </div>
                  {report.campaign.last_error && (
                    <div className="text-xs bg-red-50 border border-red-200 rounded p-2 text-red-700">
                      <AlertTriangle size={12} className="inline mr-1" /> {report.campaign.last_error}
                    </div>
                  )}
                </>
              )}
            </>
          )}

          {activeTab === 'failures' && (
            <FailedTab campaignId={campaignId} t={t} />
          )}
        </div>
      </div>
    </div>
  );
}

// ─── Main page ─────────────────────────────────────────────────────────────

function CampaignsPageInner() {
  const { t } = useLanguage();
  usePageHeader(
    t('marketing.campaign.title', 'Campaigns'),
    t('marketing.campaign.subtitle', 'Email and WhatsApp marketing broadcasts'),
  );
  // Pass the literal session branch (`sessionBranchId`) into the wizard so a
  // specific-branch session keeps its branch auto-selected, while an owner
  // in "All branches" mode is forced to explicitly pick one.
  const { sessionBranchId } = useAuth();
  const [campaigns, setCampaigns] = useState<Campaign[]>([]);
  const [segments, setSegments] = useState<Segment[]>([]);
  const [branches, setBranches] = useState<Branch[]>([]);
  const [loading, setLoading] = useState(true);
  const [forbidden, setForbidden] = useState(false);
  const [showWizard, setShowWizard] = useState(false);
  const [reportFor, setReportFor] = useState<string | null>(null);
  const [testFor, setTestFor] = useState<Campaign | null>(null);
  // "audience deleted" filter chip — when on, only campaigns whose source
  // segment was deleted are returned. Server-side filter so we don't ship
  // the full list to the client just to hide most of it.
  const [audienceDeletedOnly, setAudienceDeletedOnly] = useState(false);

  const refresh = useCallback(async () => {
    setLoading(true);
    try {
      const [cs, ss, bs] = await Promise.all([
        listCampaigns({ audienceDeleted: audienceDeletedOnly }),
        listSegments(),
        listBranches({ limit: '100' }),
      ]);
      setCampaigns(cs); setSegments(ss); setBranches(bs.branches);
    } catch (e) {
      if (isForbiddenError(e)) {
        setForbidden(true);
      } else if (!isFeatureLockedError(e)) {
        toast.error(e instanceof Error ? e.message : t('marketing.campaign.loadFailed', 'Failed to load campaigns'));
      }
    } finally { setLoading(false); }
  }, [t, audienceDeletedOnly]);
  useEffect(() => { void refresh(); }, [refresh]);

  async function onCancel(id: string) {
    if (!confirm(t('marketing.campaign.confirmCancel', 'Cancel this broadcast? Queued deliveries will be skipped.'))) return;
    try {
      await cancelCampaign(id);
      toast.success(t('marketing.campaign.cancelled', 'Campaign cancelled'));
      await refresh();
    } catch (e) {
      toast.error(e instanceof Error ? e.message : t('marketing.campaign.cancelFailed', 'Cancel failed'));
    }
  }
  async function onDelete(id: string) {
    if (!confirm(t('marketing.campaign.confirmDelete', 'Delete this campaign?'))) return;
    try {
      await deleteCampaign(id);
      toast.success(t('common.deleted', 'Deleted'));
      await refresh();
    } catch (e) {
      toast.error(e instanceof Error ? e.message : t('marketing.campaign.deleteFailed', 'Delete failed'));
    }
  }

  if (forbidden) {
    return <PermissionDenied sectionName="the Marketing section" />;
  }

  // Render: surface the launch_group_id link visually so siblings cluster
  // together. Each row still has its own controls — group-aware aggregation
  // happens server-side inside the report endpoint.
  return (
    <div className="p-6 space-y-6">
      <div className="flex justify-between items-center gap-3 flex-wrap">
        <p className="text-sm text-gray-500">
          {t('marketing.campaign.intro', 'Marketing broadcasts respect customer opt-outs and branch quiet hours by default.')}
        </p>
        <div className="flex gap-2 items-center">
          <button
            type="button"
            onClick={() => setAudienceDeletedOnly((v) => !v)}
            aria-pressed={audienceDeletedOnly}
            title={t(
              'marketing.campaign.filterAudienceDeletedTooltip',
              'Show only campaigns whose source audience was deleted, so you can re-link a fresh segment.',
            )}
            className={`text-xs px-2.5 py-1.5 rounded-full font-medium inline-flex items-center gap-1 border transition-colors ${
              audienceDeletedOnly
                ? 'bg-amber-100 text-amber-800 border-amber-300'
                : 'bg-white text-gray-700 border-gray-300 hover:border-amber-300 hover:text-amber-800'
            }`}
          >
            <AlertTriangle size={12} />
            {t('marketing.campaign.filterAudienceDeleted', 'Audience deleted only')}
            {audienceDeletedOnly && <X size={12} className="ml-0.5" />}
          </button>
          <button className="btn-ghost flex items-center gap-2" onClick={() => void refresh()}>
            <RefreshCw size={16} /> {t('common.refresh', 'Refresh')}
          </button>
          <button className="btn-primary flex items-center gap-2" onClick={() => setShowWizard(true)}>
            <Plus size={16} /> {t('marketing.campaign.new', 'New campaign')}
          </button>
        </div>
      </div>

      <div className="rounded-xl border bg-white">
        {loading ? (
          <div className="p-10 text-center"><Loader2 size={20} className="inline animate-spin" /></div>
        ) : campaigns.length === 0 ? (
          <div className="p-10 text-center text-gray-500">
            <Megaphone size={28} className="mx-auto mb-3 opacity-60" />
            {audienceDeletedOnly
              ? t('marketing.campaign.emptyAudienceDeleted', 'No campaigns with a deleted audience — nothing to clean up.')
              : t('marketing.campaign.empty', 'No campaigns yet.')}
          </div>
        ) : (
          <div className="divide-y">
            {campaigns.map((c) => (
              <div key={c.id} className="p-4 flex items-center justify-between gap-3">
                <div className="flex-1 min-w-0">
                  <div className="flex items-center gap-2">
                    <ChannelIcon channel={c.channel} />
                    <Link href={`/marketing/campaigns/${c.id}`} className="font-semibold truncate hover:text-blue-600 hover:underline">
                      {c.name}
                    </Link>
                    <StatusBadge status={c.status} />
                    {c.launch_group_id && (
                      <span className="text-xs px-2 py-0.5 rounded-full font-medium bg-purple-100 text-purple-700 inline-flex items-center gap-1" title={t('marketing.campaign.linkedTooltip', 'Part of a linked Email + WhatsApp launch')}>
                        <Layers size={10} /> {t('marketing.campaign.linked', 'linked')}
                      </span>
                    )}
                    {c.segment_deleted_at && (
                      <span
                        className="text-xs px-2 py-0.5 rounded-full font-medium bg-amber-100 text-amber-800 inline-flex items-center gap-1"
                        title={t('marketing.campaign.segmentDeletedTooltip', 'The saved audience for this campaign was deleted. Sending continues against the snapshotted rules taken at the time of deletion.')}
                      >
                        <AlertTriangle size={10} /> {t('marketing.campaign.segmentDeleted', 'audience deleted')}
                      </span>
                    )}
                  </div>
                  <div className="text-xs text-gray-500 mt-1 flex items-center gap-3 flex-wrap">
                    <span><Clock size={12} className="inline mr-1" />{c.scheduled_at ? new Date(c.scheduled_at).toLocaleString() : t('marketing.campaign.notScheduled', 'not scheduled')}</span>
                    <span>{t('marketing.campaign.audience', 'Audience')} {c.audience_count}</span>
                    <span><CheckCircle2 size={12} className="inline mr-1 text-emerald-600" />{c.sent_count}</span>
                    {c.failed_count > 0 && <span><XCircle size={12} className="inline mr-1 text-red-600" />{c.failed_count}</span>}
                  </div>
                </div>
                <div className="flex items-center gap-1">
                  <button className="btn-ghost text-xs px-2" onClick={() => setReportFor(c.id)}>
                    {t('marketing.campaign.report', 'Report')}
                  </button>
                  {c.status === 'draft' && (
                    <button className="btn-ghost text-xs px-2 flex items-center gap-1" onClick={() => setTestFor(c)}>
                      <Send size={12} /> {t('marketing.campaign.test', 'Test')}
                    </button>
                  )}
                  {(c.status === 'scheduled' || c.status === 'sending') && (
                    <button className="btn-ghost text-xs px-2 text-amber-700" onClick={() => onCancel(c.id)}>
                      {t('common.cancel', 'Cancel')}
                    </button>
                  )}
                  <button className="text-red-600 hover:bg-red-50 p-2 rounded" onClick={() => onDelete(c.id)}>
                    <Trash2 size={14} />
                  </button>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>

      {showWizard && <CampaignWizard segments={segments} branches={branches} defaultBranchId={sessionBranchId} t={t} onClose={() => setShowWizard(false)} onCreated={() => void refresh()} />}
      {reportFor && <ReportDrawer campaignId={reportFor} t={t} onClose={() => setReportFor(null)} />}
      {testFor && <TestSendDialog campaign={testFor} t={t} onClose={() => setTestFor(null)} />}
    </div>
  );
}

export default function CampaignsPage() {
  return <FeatureGuard feature="marketing" featureName="Marketing"><CampaignsPageInner /></FeatureGuard>;
}
