import { useState, useEffect, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Megaphone, Clapperboard, BarChart3, Globe, Settings2,
Zap, TrendingUp, Eye, MousePointerClick, DollarSign,
Upload, Play, Image, Film, RefreshCw, ArrowRight, Plus, X,
AlertTriangle, ArrowRightLeft, PlusCircle, SlidersHorizontal,
Activity, Check, Link2, WandSparkles,
type LucideIcon,
} from 'lucide-react';
import {
AreaChart, Area, BarChart, Bar,
XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
} from 'recharts';
import { useMarketingStore } from '@/store/useMarketingStore';
import { useCurrency } from '@/store/useCurrencyStore';
import type { Campaign, MarketingAsset, LiveOptimizationEvent, LiveEventType } from '@/types';
import { GroundTruthPicker } from './GroundTruthPicker';
import { CatalystMarketingTab } from './CatalystMarketingTab';
import { CatalystDreamWeaverTab } from './CatalystDreamWeaverTab';
import type { GroundTruthSelection } from './GroundTruthPicker';
// ── Design tokens ─────────────────────────────────────────────────────────────
const GLASS = {
background: 'rgba(8, 10, 18, 0.82)',
border: '1px solid rgba(59,130,246,0.14)',
backdropFilter: 'blur(24px)',
WebkitBackdropFilter: 'blur(24px)',
boxShadow: '0 0 0 1px rgba(255,255,255,0.04), 0 4px 32px rgba(0,0,0,0.55)',
} as const;
const INNER = {
background: 'rgba(255,255,255,0.04)',
border: '1px solid rgba(255,255,255,0.07)',
} as const;
// ── Shared primitives ─────────────────────────────────────────────────────────
function Widget({ children, className = '', delay = 0, colSpan = 1, rowSpan = 1, style }: {
children: React.ReactNode; className?: string; delay?: number; colSpan?: number; rowSpan?: number; style?: React.CSSProperties;
}) {
return (
{children}
);
}
function GhostButton({ children, onClick, danger = false, className = '' }: {
children: React.ReactNode; onClick?: () => void; danger?: boolean; className?: string;
}) {
return (
{children}
);
}
function DarkInput({ value, onChange, placeholder, type = 'text', readOnly = false }: {
value: string; onChange?: (v: string) => void; placeholder?: string; type?: string; readOnly?: boolean;
}) {
return (
onChange?.(e.target.value)}
placeholder={placeholder}
className="rounded-xl px-3 py-2 text-sm text-white w-full focus:outline-none transition-all"
style={{ ...INNER, caretColor: 'hsl(var(--accent))' }}
onFocus={(e) => { if (!readOnly) e.currentTarget.style.border = '1px solid rgba(59,130,246,0.4)'; }}
onBlur={(e) => { e.currentTarget.style.border = '1px solid rgba(255,255,255,0.07)'; }}
/>
);
}
function LiveBadge() {
return (
Live
);
}
// ── KPI Stat card ─────────────────────────────────────────────────────────────
function StatCard({ icon: Icon, label, value, sub, glowColor = 'rgba(59,130,246,0.2)', delay = 0 }: {
icon: LucideIcon; label: string; value: string; sub: string; glowColor?: string; delay?: number;
}) {
return (
);
}
// ── Campaign status badge ─────────────────────────────────────────────────────
function CampaignStatusBadge({ status }: { status: Campaign['status'] }) {
const map: Record = {
ACTIVE: { label: 'Active', color: '#4ade80', bg: 'rgba(74,222,128,0.12)' },
PAUSED: { label: 'Paused', color: '#fbbf24', bg: 'rgba(251,191,36,0.12)' },
DELETED: { label: 'Deleted', color: '#f87171', bg: 'rgba(248,113,113,0.12)' },
ARCHIVED: { label: 'Archived', color: '#94a3b8', bg: 'rgba(148,163,184,0.12)' },
};
const { label, color, bg } = map[status];
return (
{label}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// TAB 1 — The Studio
// ─────────────────────────────────────────────────────────────────────────────
const RENDER_MESSAGES: Record = {
rendering: [
'Wan 2.2 is rendering cinematic lighting...',
'Wan 2.2 is compositing the infinity pool reflection...',
'Qwen-Image 2512 is synthesizing Arabic typography...',
'Flux-Schnell is upscaling to 4K resolution...',
],
queued: [
'Qwen-Image 2512 queued for cinematic poster render...',
'Wan 2.2 14B queued — GPU resources freeing up...',
],
};
function AssetCard({ asset }: { asset: MarketingAsset }) {
const isProcessing = asset.status === 'rendering' || asset.status === 'queued';
const [msgIdx, setMsgIdx] = useState(0);
useEffect(() => {
if (!isProcessing) return;
const msgs = RENDER_MESSAGES[asset.status] ?? [];
if (msgs.length === 0) return;
const t = setInterval(() => setMsgIdx((i) => (i + 1) % msgs.length), 2800);
return () => clearInterval(t);
}, [isProcessing, asset.status]);
const statusColor: Record = {
queued: 'rgba(148,163,184,0.8)',
rendering: '#fbbf24',
ready: '#4ade80',
uploaded: '#60a5fa',
failed: '#f87171',
};
return (
{/* Thumbnail / shimmer area */}
{isProcessing ? (
{asset.type === 'video'
?
: }
{(RENDER_MESSAGES[asset.status] ?? [])[msgIdx] ?? asset.renderMessage}
) : (
{asset.type === 'video'
?
: }
{asset.metaAssetId ? `Meta ID: ${asset.metaAssetId}` : 'Ready for upload'}
)}
{/* Info footer */}
{asset.name}
{asset.status.toUpperCase()}
{asset.type === 'video' ? 'Wan 2.2' : 'Qwen-2512'}
{asset.language && (
{asset.language}
)}
);
}
// ── Reference Slot ───────────────────────────────────────────────────────────
interface RefSelection { name: string; preview: string; }
function ReferenceSlot({ value, onSelect, onClear, onRemove }: {
value: RefSelection | null;
onSelect: (sel: RefSelection) => void;
onClear: () => void;
onRemove?: () => void;
}) {
const inputRef = useRef(null);
function handleFile(e: React.ChangeEvent) {
const file = e.target.files?.[0];
if (!file) return;
onSelect({ name: file.name, preview: URL.createObjectURL(file) });
e.target.value = '';
}
// Show X when: slot has content (clear it), or slot is removable (remove it)
const showX = !!value || !!onRemove;
const handleX = (e: React.MouseEvent) => {
e.stopPropagation();
if (onRemove) onRemove();
else onClear();
};
return (
);
}
function WorkflowInput() {
const [mode, setMode] = useState<'image' | 'video'>('image');
const [prompt, setPrompt] = useState('');
const [keywords, setKeywords] = useState('');
const [textCopy, setTextCopy] = useState('');
const [groundTruth, setGroundTruth] = useState(null);
const [pickerOpen, setPickerOpen] = useState(false);
const [refs, setRefs] = useState<(RefSelection | null)[]>([null, null]);
const anchorRef = useRef(null);
return (
{/* Top Section: Ground Truth & References */}
{/* Ground Truth slot */}
setPickerOpen(v => !v)}
className="w-[60px] h-[60px] rounded-2xl flex flex-col items-center justify-center gap-1 overflow-hidden transition-colors"
style={{
background: groundTruth?.preview ? 'transparent' : pickerOpen ? 'rgba(59,130,246,0.12)' : 'rgba(255,255,255,0.03)',
border: pickerOpen ? '1px solid rgba(59,130,246,0.4)' : '1px solid rgba(255,255,255,0.08)',
}}
whileHover={{ scale: 1.04 }}
whileTap={{ scale: 0.96 }}
title="Select Ground Truth image"
>
{groundTruth?.preview ? (
) : groundTruth?.name ? (
<>
{groundTruth.name}
>
) : (
{pickerOpen ? '✕' : '+'}
)}
{/* Clear selection button */}
{groundTruth && (
{ e.stopPropagation(); setGroundTruth(null); setPickerOpen(false); }}
initial={{ opacity: 0, scale: 0.6 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.6 }}
transition={{ duration: 0.12 }}
whileHover={{ scale: 1.2, backgroundColor: 'rgba(239,68,68,0.8)' }}
title="Remove selection"
>
)}
{pickerOpen && (
{ setGroundTruth(sel); setPickerOpen(false); }}
onClose={() => setPickerOpen(false)}
/>
)}
Ground Truth
{refs.map((ref, i) => (
setRefs(prev => prev.map((r, idx) => idx === i ? sel : r))}
onClear={() => setRefs(prev => prev.map((r, idx) => idx === i ? null : r))}
onRemove={i >= 2 ? () => setRefs(prev => prev.filter((_, idx) => idx !== i)) : undefined}
/>
))}
setRefs(prev => prev.length < 6 ? [...prev, null] : prev)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
References
{/* Bottom Section: Input & Controls */}
{/* Toggle */}
{/* Send Button */}
);
}
function TheStudio() {
const { activeAssets } = useMarketingStore();
return (
{/* Header row */}
Visual AI Engine
ComfyUI workflows (Wan 2.2 & Qwen-Image 2512) feed directly into the Meta Asset Library
Sync to Meta
Trigger Render
{/* ComfyUI Workflow Input Panel */}
{/* Asset grid */}
{activeAssets.map((asset, i) => (
))}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// TAB 2 — Campaign Command
// ─────────────────────────────────────────────────────────────────────────────
function CampaignCommand() {
const { campaigns, adInsights } = useMarketingStore();
const { formatAmount, rate } = useCurrency();
const totalSpend = campaigns.reduce((s, c) => s + c.lifetimeSpend / 100, 0);
const totalImpr = campaigns.reduce((s, c) => s + c.impressions, 0);
const avgCtr = campaigns.reduce((s, c) => s + c.ctr, 0) / campaigns.length;
const avgCpa = campaigns.reduce((s, c) => s + c.cpa, 0) / campaigns.length;
// Build spend-over-time from insights (last 14 days)
const spendData = adInsights.slice(0, 14).map((d) => ({
date: d.date.slice(5), // MM-DD
spend: Math.round(d.spend * rate),
impressions: Math.round(d.impressions / 1000),
})).reverse();
return (
{/* KPI row */}
c.status === 'ACTIVE').length)} sub={`${campaigns.length} total campaigns`} glowColor="rgba(59,130,246,0.2)" delay={0} />
{/* Spend chart + campaign list */}
{/* Spend over time chart */}
Spend vs Impressions
{/* Campaign list */}
{campaigns.map((c) => (
{c.name}
{formatAmount(c.lifetimeSpend / 100)} · {c.impressions.toLocaleString()} impr.
20 ? '#4ade80' : '#fbbf24' }}>
{c.roi.toFixed(1)}% ROI
))}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// TAB 3 — Intelligence & ROI
// ─────────────────────────────────────────────────────────────────────────────
function IntelligenceROI() {
const { campaigns, adInsights } = useMarketingStore();
const { formatAmount, symbol, rate } = useCurrency();
const cpaData = adInsights.slice(0, 7).map((d) => ({
date: d.date.slice(5),
cpa: Math.round(d.cpa * rate),
roi: d.roi,
})).reverse();
const adSetPerf = [
{ name: '3BHK Dubai', ctr: 2.1, cpa: Math.round(210 * rate), spend: Math.round(3400 * rate) },
{ name: 'PH Retarget', ctr: 3.2, cpa: Math.round(2100 * rate), spend: Math.round(5800 * rate) },
{ name: '1BHK Lookalike', ctr: 1.8, cpa: Math.round(270 * rate), spend: Math.round(980 * rate) },
];
return (
{/* CPA / ROI KPIs */}
s+c.cpa,0)/campaigns.length)} sub="Cost per acquisition" glowColor="rgba(34,211,238,0.2)" delay={0} />
s+c.roi,0)/campaigns.length).toFixed(1)}%`} sub="Blended across all ad sets" glowColor="rgba(74,222,128,0.2)" delay={0.06} />
s+c.ctr,0)/campaigns.length).toFixed(2)}%`} sub="Click-through rate" glowColor="rgba(167,139,250,0.2)" delay={0.12} />
{/* CPA trend */}
CPA Trend (7 Days)
{/* Ad-set CPA bar chart */}
Ad Set Performance
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Live Optimization Feed (shared across tabs)
// ─────────────────────────────────────────────────────────────────────────────
const EVENT_ICON: Record = {
pause: { icon: AlertTriangle, color: '#fbbf24', bg: 'rgba(251,191,36,0.12)', label: 'Paused' },
shift: { icon: ArrowRightLeft, color: '#60a5fa', bg: 'rgba(96,165,250,0.12)', label: 'Budget Shift' },
create: { icon: PlusCircle, color: '#4ade80', bg: 'rgba(74,222,128,0.12)', label: 'Created' },
optimize: { icon: Zap, color: '#a78bfa', bg: 'rgba(167,139,250,0.12)', label: 'Optimized' },
alert: { icon: AlertTriangle, color: '#f87171', bg: 'rgba(248,113,113,0.12)', label: 'Alert' },
rotate: { icon: RefreshCw, color: '#22d3ee', bg: 'rgba(34,211,238,0.12)', label: 'Rotated' },
};
function LiveEventItem({ event }: { event: LiveOptimizationEvent }) {
const cfg = EVENT_ICON[event.type];
const { formatText } = useCurrency();
const Icon = cfg.icon;
return (
{cfg.label}
{event.campaignName && (
{event.campaignName}
)}
{event.value && (
{formatText(event.value)}
)}
{formatText(event.message)}
{event.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
);
}
function LiveOptimizationFeed() {
const { liveEvents } = useMarketingStore();
return (
{liveEvents.length === 0 ? (
No live optimization events are available. Connect the production ad-platform integrations to populate this stream.
) : (
{liveEvents.slice(0, 8).map((ev) => (
))}
)}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// TAB 4 — War Room (Meta Graph Settings)
// ─────────────────────────────────────────────────────────────────────────────
function WarRoom() {
const { settings, setMetaSettings } = useMarketingStore();
const [saved, setSaved] = useState(false);
const handleSave = () => {
setMetaSettings({ isConnected: true });
setSaved(true);
setTimeout(() => setSaved(false), 2500);
};
const fields: Array<{ key: keyof typeof settings; label: string; desc: string; placeholder: string; type?: string }> = [
{ key: 'metaAccessToken', label: 'Meta System User Access Token', desc: 'Long-lived system user token from Meta Business Manager', placeholder: 'EAAxxxxxxxx...' , type: 'password' },
{ key: 'metaAdAccountId', label: 'Ad Account ID', desc: 'Format: act_XXXXXXXXXX', placeholder: 'act_123456789' },
{ key: 'metaBusinessId', label: 'Business Portfolio ID', desc: 'Found in Meta Business Settings → Business Info', placeholder: '1234567890' },
{ key: 'metaAppId', label: 'App ID', desc: 'From Meta Developers → Your App', placeholder: '9876543210' },
{ key: 'whatsappPhoneNumberId',label: 'WhatsApp Phone Number ID', desc: 'Required for WhatsApp Business API integration', placeholder: '1098765432' },
];
const assetLinks = [
{ label: 'Instagram Business Page', href: '#', icon: Link2 },
{ label: 'Facebook Business Page', href: '#', icon: Link2 },
{ label: 'WhatsApp Business Account', href: '#', icon: Link2 },
{ label: 'Ad Library (Creative Hub)', href: '#', icon: Link2 },
];
return (
{/* Connection status banner */}
{settings.isConnected &&
}
Meta Graph API
{settings.isConnected ? 'Connected — System User Token Active' : 'Not connected — enter credentials below'}
{settings.isConnected ? 'ONLINE' : 'OFFLINE'}
{/* API Credentials */}
API Credentials
{fields.map(({ key, label, desc, placeholder, type }) => (
{label}
{desc}
setMetaSettings({ [key]: v } as Partial)}
placeholder={placeholder}
/>
))}
{saved ? <> Saved & Connected> : 'Save & Connect to Meta'}
{/* Business Asset Links */}
Business Asset Management
Link WhatsApp, Instagram, and Facebook Pages to your Meta Business Portfolio for unified campaign management.
{assetLinks.map(({ label, icon: Icon }) => (
))}
{/* Permissions */}
Required Scopes
{['ads_management', 'ads_read', 'business_management', 'pages_manage_ads', 'whatsapp_business_management'].map((scope) => (
{scope}
))}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Tab Bar
// ─────────────────────────────────────────────────────────────────────────────
type TabId = 'studio' | 'command' | 'intelligence' | 'war-room' | 'marketing' | 'dream-weaver';
const TABS: Array<{ id: TabId; label: string; icon: LucideIcon }> = [
{ id: 'studio', label: 'The Studio', icon: Clapperboard },
{ id: 'command', label: 'Campaign Command', icon: Megaphone },
{ id: 'intelligence', label: 'Intelligence & ROI', icon: BarChart3 },
{ id: 'war-room', label: 'War Room', icon: Globe },
{ id: 'marketing', label: 'Marketing', icon: TrendingUp },
{ id: 'dream-weaver', label: 'Dream Weaver', icon: WandSparkles },
];
// ─────────────────────────────────────────────────────────────────────────────
// Root export
// ─────────────────────────────────────────────────────────────────────────────
export function Catalyst() {
const { activeTab, setActiveTab } = useMarketingStore();
const panels: Record = {
'studio': ,
'command': ,
'intelligence': ,
'war-room': ,
'marketing': ,
'dream-weaver': ,
};
return (
{/* Header */}
The Catalyst
Autonomous Deployment Engine · Meta Marketing API
Claw Agent Active
{/* Tab bar */}
{TABS.map((tab) => {
const Icon = tab.icon;
const isActive = activeTab === tab.id;
return (
);
})}
{/* Tab panel */}
{panels[activeTab]}
{/* Live Optimization Feed — hidden on Dream Weaver because generation has its own status surface. */}
{activeTab !== 'dream-weaver' && }
);
}