← back to Patty

components/petitions/PetitionsTab.tsx

1102 lines

'use client';

import { useState, useEffect, useCallback } from 'react';
import { SkeletonList } from '../Skeleton';
import {
  Plus, Megaphone, Search, ChevronDown, ChevronUp,
  Sparkles, Target, Users, Share2, Calendar, Edit3,
  Send, Loader2, X, Tag, Mail as MailIcon, DollarSign, ExternalLink,
} from 'lucide-react';
import { Grant, formatAmount } from '../grants/types';
import { useToast } from '../ToastProvider';
import { useDebounce } from '@/hooks/useDebounce';
import SortDropdown from '../shared/SortDropdown';
import { useClientSort, SortConfig } from '@/hooks/useClientSort';

const PET_SORT_OPTIONS = [
  { value: 'sigs_desc', label: 'Most Signatures' },
  { value: 'date_desc', label: 'Newest First' },
  { value: 'date_asc', label: 'Oldest First' },
  { value: 'status', label: 'Status A-Z' },
];

const PET_SORT_CONFIGS: Record<string, SortConfig> = {
  sigs_desc: { key: 'signature_count', direction: 'desc', type: 'number' },
  date_desc: { key: 'created_at', direction: 'desc', type: 'date' },
  date_asc: { key: 'created_at', direction: 'asc', type: 'date' },
  status: { key: 'status', direction: 'asc', type: 'string' },
};

/* ─── Types ─────────────────────────────────────────────────── */
interface Petition {
  id: string;
  title: string;
  slug: string;
  summary: string | null;
  body_html: string;
  body_text: string | null;
  target: string | null;
  target_emails: string[] | null;
  status: string;
  category: string | null;
  tags: string[] | null;
  signature_count: number;
  signature_goal: number;
  share_count: number;
  org_type: string | null;
  compliance_flags: Record<string, unknown> | null;
  external_url?: string | null;
  distribution_platform?: string;
  created_at: string;
}

/**
 * Basic HTML tag stripper for display purposes.
 * All petition content is created by the admin user or generated by Gemini AI,
 * so it is trusted internal content. This function strips tags for plain text display.
 */
function stripHtml(html: string): string {
  return html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
}

/* ─── Component ─────────────────────────────────────────────── */
export default function PetitionsTab({ selectedId, onSelectedConsumed, onViewGrant }: { selectedId?: number | null; onSelectedConsumed?: () => void; onViewGrant?: (id: string) => void } = {}) {
  const { addToast } = useToast();
  const [petitions, setPetitions] = useState<Petition[]>([]);
  const [loading, setLoading] = useState(true);
  const [filter, setFilter] = useState('all');
  const [search, setSearch] = useState('');
  const debouncedSearch = useDebounce(search, 300);
  const [statusCounts, setStatusCounts] = useState<Record<string, number>>({});
  const [expanded, setExpanded] = useState<string | null>(null);
  const [sortBy, setSortBy] = useState('date_desc');
  const sortedPetitions = useClientSort(petitions, sortBy, PET_SORT_CONFIGS);

  // AI modal state
  const [showAiModal, setShowAiModal] = useState(false);
  const [aiTopic, setAiTopic] = useState('');
  const [aiTarget, setAiTarget] = useState('');
  const [aiTone, setAiTone] = useState('formal');
  const [aiCategory, setAiCategory] = useState('policy');
  const [aiGenerating, setAiGenerating] = useState(false);
  const [aiResult, setAiResult] = useState<{
    title: string; summary: string; body_html: string; body_text: string;
    suggested_target: string; suggested_tags: string[]; suggested_goal: number;
  } | null>(null);
  const [savingDraft, setSavingDraft] = useState(false);

  // Related grants state
  const [petitionGrants, setPetitionGrants] = useState<Record<string, Grant[]>>({});
  const [loadingGrants, setLoadingGrants] = useState<string | null>(null);

  // Edit modal state
  const [editPetition, setEditPetition] = useState<Petition | null>(null);
  const [editTitle, setEditTitle] = useState('');
  const [editSummary, setEditSummary] = useState('');
  const [editTarget, setEditTarget] = useState('');
  const [editGoal, setEditGoal] = useState(100);
  const [editStatus, setEditStatus] = useState('draft');
  const [editSaving, setEditSaving] = useState(false);

  // Deploy modal state
  const [showDeployModal, setShowDeployModal] = useState(false);
  const [deployPetition, setDeployPetition] = useState<Petition | null>(null);
  const [deployPlatforms, setDeployPlatforms] = useState<Record<string, boolean>>({ moveon: true, change_org: true, we_the_people: false, custom: false });
  const [deploying, setDeploying] = useState(false);
  const [deployResults, setDeployResults] = useState<Array<{ platform: string; platform_name: string; creation_url: string; formatted_content: Record<string, unknown>; instructions: string }>>([]);
  const [deployCustomUrl, setDeployCustomUrl] = useState('');
  const [copiedField, setCopiedField] = useState<string | null>(null);

  /* ── Fetch ──────────────────────────────────────────────────── */
  const fetchPetitions = useCallback(async () => {
    setLoading(true);
    try {
      const params = new URLSearchParams();
      if (filter !== 'all') params.set('status', filter);
      if (debouncedSearch) params.set('search', debouncedSearch);
      const res = await fetch(`/api/petitions?${params.toString()}`);
      if (res.ok) {
        const data = await res.json();
        setPetitions(data.rows || []);
        setStatusCounts(data.statusCounts || {});
      }
    } catch (err) {
      console.error('Fetch petitions error:', err);
    } finally {
      setLoading(false);
    }
  }, [filter, debouncedSearch]);

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

  // Auto-expand petition from cross-tab navigation (Topics → Petitions)
  useEffect(() => {
    if (selectedId && petitions.length > 0) {
      setExpanded(String(selectedId));
      onSelectedConsumed?.();
      // Scroll into view after render
      setTimeout(() => {
        document.getElementById(`petition-${selectedId}`)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
      }, 100);
    }
  }, [selectedId, petitions.length, onSelectedConsumed]);

  /* ── Escape key handler for modals ──────────────────────────── */
  useEffect(() => {
    if (!showAiModal && !editPetition && !showDeployModal) return;
    const handleKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        if (showAiModal) setShowAiModal(false);
        if (editPetition) setEditPetition(null);
        if (showDeployModal) { setShowDeployModal(false); setDeployResults([]); }
      }
    };
    window.addEventListener('keydown', handleKey);
    return () => window.removeEventListener('keydown', handleKey);
  }, [showAiModal, editPetition, showDeployModal]);

  /* ── AI Generate ────────────────────────────────────────────── */
  async function handleAiGenerate() {
    if (!aiTopic.trim()) return;
    setAiGenerating(true);
    setAiResult(null);
    try {
      const res = await fetch('/api/petitions/generate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ topic: aiTopic, target: aiTarget, tone: aiTone, category: aiCategory }),
      });
      if (res.ok) {
        const data = await res.json();
        setAiResult(data);
        addToast('Petition generated', 'success');
      } else {
        addToast('AI generation failed', 'error');
      }
    } catch (err) {
      console.error('AI generate error:', err);
      addToast('AI generation failed', 'error');
    } finally {
      setAiGenerating(false);
    }
  }

  /* ── Save Draft ─────────────────────────────────────────────── */
  async function handleSaveDraft() {
    if (!aiResult) return;
    setSavingDraft(true);
    try {
      const res = await fetch('/api/petitions', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          title: aiResult.title,
          summary: aiResult.summary,
          body_html: aiResult.body_html,
          body_text: aiResult.body_text,
          target: aiResult.suggested_target,
          category: aiCategory,
          tags: aiResult.suggested_tags,
          signature_goal: aiResult.suggested_goal,
          status: 'draft',
        }),
      });
      if (res.ok) {
        setShowAiModal(false);
        setAiResult(null);
        setAiTopic('');
        setAiTarget('');
        fetchPetitions();
        addToast('Draft saved', 'success');
      } else {
        addToast('Failed to save draft', 'error');
      }
    } catch (err) {
      console.error('Save draft error:', err);
      addToast('Failed to save draft', 'error');
    } finally {
      setSavingDraft(false);
    }
  }

  /* ── Edit Petition ──────────────────────────────────────────── */
  function openEdit(p: Petition) {
    setEditPetition(p);
    setEditTitle(p.title);
    setEditSummary(p.summary || '');
    setEditTarget(p.target || '');
    setEditGoal(p.signature_goal);
    setEditStatus(p.status);
  }

  async function handleSaveEdit() {
    if (!editPetition) return;
    setEditSaving(true);
    try {
      const res = await fetch('/api/petitions', {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          id: editPetition.id,
          title: editTitle,
          summary: editSummary,
          target: editTarget,
          signature_goal: editGoal,
          status: editStatus,
        }),
      });
      if (res.ok) {
        setEditPetition(null);
        fetchPetitions();
        addToast('Petition updated', 'success');
      } else {
        addToast('Failed to update petition', 'error');
      }
    } catch (err) {
      console.error('Save edit error:', err);
      addToast('Failed to update petition', 'error');
    } finally {
      setEditSaving(false);
    }
  }

  /* ── Deploy to Platforms ──────────────────────────────────── */
  const DEPLOY_PLATFORMS = [
    { key: 'moveon', name: 'MoveOn', url: 'sign.moveon.org', icon: '🟡' },
    { key: 'change_org', name: 'Change.org', url: 'change.org', icon: '🔴' },
    { key: 'we_the_people', name: 'We The People', url: 'petitions.whitehouse.gov', icon: '🏛️' },
    { key: 'custom', name: 'Custom URL', url: '', icon: '🔗' },
  ];

  async function handleDeploy() {
    if (!deployPetition) return;
    const selected = Object.entries(deployPlatforms).filter(([, v]) => v).map(([k]) => k);
    if (selected.length === 0) { addToast('Select at least one platform', 'error'); return; }
    setDeploying(true);
    setDeployResults([]);
    const results: typeof deployResults = [];
    for (const platform of selected) {
      try {
        const res = await fetch('/api/orbit/post', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            petition_id: deployPetition.id,
            platform,
            custom_url: platform === 'custom' ? deployCustomUrl : undefined,
          }),
        });
        if (res.ok) {
          const data = await res.json();
          results.push({
            platform,
            platform_name: data.platform_name,
            creation_url: data.creation_url,
            formatted_content: data.formatted_content,
            instructions: data.instructions,
          });
        } else {
          addToast(`Failed to deploy to ${platform}`, 'error');
        }
      } catch {
        addToast(`Error deploying to ${platform}`, 'error');
      }
    }
    setDeployResults(results);
    setDeploying(false);
    if (results.length > 0) addToast(`Generated content for ${results.length} platform(s)`, 'success');
  }

  function copyToClipboard(text: string, label: string) {
    navigator.clipboard.writeText(String(text)).then(() => {
      setCopiedField(label);
      setTimeout(() => setCopiedField(null), 2000);
    });
  }

  /* ── Find Related Grants ──────────────────────────────────── */
  async function findRelatedGrants(petitionId: string) {
    setLoadingGrants(petitionId);
    try {
      const res = await fetch('/api/grants-proxy/grants');
      if (res.ok) {
        const data = await res.json();
        setPetitionGrants(prev => ({ ...prev, [petitionId]: data.rows || [] }));
      }
    } catch (err) {
      console.error('Find grants error:', err);
      addToast('Failed to find related grants', 'error');
    } finally {
      setLoadingGrants(null);
    }
  }

  /* ── Helpers ────────────────────────────────────────────────── */
  const statusFilters = ['all', 'draft', 'active', 'paused', 'closed', 'delivered'];

  const statusBadgeClass: Record<string, string> = {
    draft: 'badge-draft',
    active: 'badge-active',
    paused: 'badge-paused',
    closed: 'badge-closed',
    delivered: 'badge-delivered',
  };

  const categoryLabel = (c: string | null) => {
    if (!c) return '';
    return c.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
  };

  const formatDate = (d: string) => {
    return new Date(d).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
  };

  const sigPct = (count: number, goal: number) => {
    if (goal <= 0) return 0;
    return Math.min(Math.round((count / goal) * 100), 100);
  };

  /* ─── Render ────────────────────────────────────────────────── */
  return (
    <div style={{ padding: '24px' }}>
      {/* Header Row */}
      <div className="flex items-center justify-between mb-6">
        <div>
          <h2 className="text-lg font-semibold" style={{ color: 'var(--color-text)' }}>
            My Petitions
          </h2>
          <p className="text-sm mt-1" style={{ color: 'var(--color-text-muted)' }}>
            Create, manage, and track your advocacy campaigns
          </p>
        </div>
        <button className="btn btn-primary" onClick={() => setShowAiModal(true)}>
          <Sparkles size={16} />
          AI Create Petition
        </button>
      </div>

      {/* Filter Row */}
      <div className="flex items-center gap-3 mb-6 flex-wrap">
        <SortDropdown options={PET_SORT_OPTIONS} value={sortBy} onChange={setSortBy} />
        <div className="flex-1 relative" style={{ minWidth: 200 }}>
          <Search
            size={16}
            style={{ position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)', color: 'var(--color-text-muted)' }}
          />
          <input
            className="input"
            placeholder="Search by title or target..."
            style={{ paddingLeft: 36 }}
            value={search}
            onChange={(e) => setSearch(e.target.value)}
          />
        </div>
        <div className="flex items-center gap-1 flex-wrap">
          {statusFilters.map((s) => (
            <button
              key={s}
              onClick={() => setFilter(s)}
              className={`btn btn-sm ${filter === s ? 'btn-primary' : 'btn-ghost'}`}
              style={{ textTransform: 'capitalize' }}
            >
              {s}{statusCounts[s] !== undefined ? ` (${statusCounts[s]})` : ''}
            </button>
          ))}
        </div>
      </div>

      {/* Loading */}
      {loading && (
        <SkeletonList count={4} />
      )}

      {/* Empty State */}
      {!loading && petitions.length === 0 && (
        <div className="card flex flex-col items-center justify-center py-16" style={{ textAlign: 'center' }}>
          <div
            className="w-16 h-16 rounded-2xl flex items-center justify-center mb-4"
            style={{
              background: 'linear-gradient(135deg, rgba(124, 58, 237, 0.15), rgba(167, 139, 250, 0.1))',
              border: '1px solid rgba(124, 58, 237, 0.2)',
            }}
          >
            <Megaphone size={28} style={{ color: 'var(--color-primary)' }} />
          </div>
          <h3 className="text-base font-semibold mb-2" style={{ color: 'var(--color-text)' }}>
            No petitions yet
          </h3>
          <p className="text-sm mb-6 max-w-md" style={{ color: 'var(--color-text-muted)' }}>
            Start your first advocacy campaign. Use AI to generate a compelling petition from trending topics.
          </p>
          <button className="btn btn-primary" onClick={() => setShowAiModal(true)}>
            <Sparkles size={16} />
            AI Create Petition
          </button>
        </div>
      )}

      {/* Petition Cards */}
      {!loading && sortedPetitions.length > 0 && (
        <div className="flex flex-col gap-3">
          {sortedPetitions.map((p) => {
            const pct = sigPct(p.signature_count, p.signature_goal);
            const isExpanded = expanded === p.id;
            return (
              <div key={p.id} className="card" style={{ overflow: 'hidden' }}>
                {/* Top Row */}
                <div className="flex items-start justify-between gap-4">
                  <div className="flex-1 min-w-0">
                    <div className="flex items-center gap-2 mb-1 flex-wrap">
                      <h3
                        className="text-sm font-semibold truncate cursor-pointer"
                        style={{ color: 'var(--color-text)' }}
                        onClick={() => setExpanded(isExpanded ? null : p.id)}
                      >
                        {p.title}
                      </h3>
                      <span className={`badge ${statusBadgeClass[p.status] ?? 'badge-draft'}`}>
                        {p.status}
                      </span>
                      {p.category && (
                        <span
                          className="badge"
                          style={{
                            backgroundColor: 'rgba(124, 58, 237, 0.1)',
                            color: 'var(--color-secondary)',
                            border: '1px solid rgba(124, 58, 237, 0.2)',
                          }}
                        >
                          {categoryLabel(p.category)}
                        </span>
                      )}
                    </div>

                    {/* Signature Progress */}
                    <div className="flex items-center gap-3 mt-2">
                      <div
                        style={{
                          flex: 1,
                          maxWidth: 200,
                          height: 6,
                          borderRadius: 3,
                          backgroundColor: 'var(--color-surface-el)',
                          overflow: 'hidden',
                        }}
                      >
                        <div
                          style={{
                            width: `${pct}%`,
                            height: '100%',
                            borderRadius: 3,
                            background: 'linear-gradient(90deg, #7c3aed, #a78bfa)',
                            transition: 'width 0.3s ease',
                          }}
                        />
                      </div>
                      <span className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
                        {p.signature_count}/{p.signature_goal} ({pct}%)
                      </span>
                    </div>

                    {/* Meta Row */}
                    <div className="flex items-center gap-4 mt-2">
                      {p.target && (
                        <span className="flex items-center gap-1 text-xs" style={{ color: 'var(--color-text-muted)' }}>
                          <Target size={12} /> {p.target}
                        </span>
                      )}
                      <span className="flex items-center gap-1 text-xs" style={{ color: 'var(--color-text-muted)' }}>
                        <Share2 size={12} /> {p.share_count} shares
                      </span>
                      <span className="flex items-center gap-1 text-xs" style={{ color: 'var(--color-text-muted)' }}>
                        <Calendar size={12} /> {formatDate(p.created_at)}
                      </span>
                    </div>
                  </div>

                  {/* Actions */}
                  <div className="flex items-center gap-1 shrink-0">
                    {p.status === 'draft' && (
                      <button className="btn btn-ghost btn-sm" onClick={() => openEdit(p)} title="Edit">
                        <Edit3 size={14} />
                      </button>
                    )}
                    <button
                      className="btn btn-sm btn-primary"
                      title="Deploy to Platforms"
                      onClick={() => {
                        setDeployPetition(p);
                        setDeployPlatforms({ moveon: true, change_org: true, we_the_people: false, custom: false });
                        setDeployResults([]);
                        setDeployCustomUrl('');
                        setShowDeployModal(true);
                      }}
                    >
                      <Send size={14} />
                      <span className="hidden sm:inline">Campaign</span>
                    </button>
                    <button
                      className="btn btn-ghost btn-sm"
                      onClick={() => setExpanded(isExpanded ? null : p.id)}
                      title={isExpanded ? 'Collapse' : 'Expand'}
                    >
                      {isExpanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
                    </button>
                  </div>
                </div>

                {/* Expanded Details */}
                {isExpanded && (
                  <div
                    style={{
                      marginTop: 12,
                      paddingTop: 12,
                      borderTop: '1px solid var(--color-border)',
                    }}
                  >
                    {p.summary && (
                      <p className="text-sm mb-3" style={{ color: 'var(--color-text-secondary)' }}>
                        {p.summary}
                      </p>
                    )}

                    {/* Body Preview -- displayed as plain text for safety */}
                    <div
                      className="text-xs mb-3"
                      style={{
                        color: 'var(--color-text-muted)',
                        maxHeight: 120,
                        overflow: 'hidden',
                        padding: 8,
                        borderRadius: 6,
                        backgroundColor: 'var(--color-surface-el)',
                        whiteSpace: 'pre-wrap',
                      }}
                    >
                      {stripHtml(p.body_html).slice(0, 500)}
                    </div>

                    {/* Target Emails */}
                    {p.target_emails && p.target_emails.length > 0 && (
                      <div className="flex items-center gap-2 mb-2 flex-wrap">
                        <MailIcon size={12} style={{ color: 'var(--color-text-muted)' }} />
                        {p.target_emails.map((em, i) => (
                          <span key={i} className="text-xs" style={{
                            color: 'var(--color-text-secondary)',
                            padding: '2px 6px',
                            borderRadius: 4,
                            backgroundColor: 'var(--color-surface-el)',
                          }}>
                            {em}
                          </span>
                        ))}
                      </div>
                    )}

                    {/* Tags */}
                    {p.tags && p.tags.length > 0 && (
                      <div className="flex items-center gap-1 flex-wrap mb-3">
                        <Tag size={12} style={{ color: 'var(--color-text-muted)' }} />
                        {p.tags.map((t, i) => (
                          <span key={i} className="text-xs" style={{
                            color: 'var(--color-secondary)',
                            padding: '2px 8px',
                            borderRadius: 9999,
                            backgroundColor: 'rgba(124, 58, 237, 0.1)',
                          }}>
                            {t}
                          </span>
                        ))}
                      </div>
                    )}

                    {/* Related Grants Section */}
                    <div style={{ marginTop: 8 }}>
                      {!petitionGrants[p.id] ? (
                        <button
                          onClick={() => findRelatedGrants(p.id)}
                          disabled={loadingGrants === p.id}
                          style={{
                            display: 'flex', alignItems: 'center', gap: 5,
                            padding: '6px 14px', borderRadius: 8,
                            border: '1px solid #059669',
                            backgroundColor: 'transparent', color: '#10b981',
                            cursor: loadingGrants === p.id ? 'wait' : 'pointer',
                            fontSize: 12, fontWeight: 600,
                          }}
                        >
                          {loadingGrants === p.id
                            ? <><Loader2 size={12} style={{ animation: 'spin 1s linear infinite' }} /> Finding…</>
                            : <><DollarSign size={12} /> Find Related Grants</>
                          }
                        </button>
                      ) : petitionGrants[p.id].length > 0 ? (
                        <div>
                          <div style={{ fontSize: 11, fontWeight: 600, color: '#10b981', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 6 }}>
                            Related Grants ({petitionGrants[p.id].length})
                          </div>
                          <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                            {petitionGrants[p.id].slice(0, 5).map(g => (
                              <div key={g.id}
                                onClick={() => onViewGrant?.(g.id)}
                                style={{
                                  display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px',
                                  borderRadius: 6, backgroundColor: 'var(--color-surface-el)',
                                  cursor: 'pointer', transition: 'background-color 0.15s',
                                }}
                                onMouseEnter={e => { e.currentTarget.style.backgroundColor = '#059669' + '18'; }}
                                onMouseLeave={e => { e.currentTarget.style.backgroundColor = 'var(--color-surface-el)'; }}
                              >
                                <DollarSign size={12} style={{ color: '#10b981', flexShrink: 0 }} />
                                <div style={{ flex: 1, minWidth: 0 }}>
                                  <span style={{ fontSize: 12, fontWeight: 500, color: 'var(--color-text)' }}>{g.title}</span>
                                  <span style={{ fontSize: 11, color: 'var(--color-text-muted)', marginLeft: 6 }}>
                                    {g.funder} · {formatAmount(g.amount_min, g.amount_max)}
                                  </span>
                                </div>
                                {g.ai_fit_score != null && (
                                  <span style={{ fontSize: 10, fontWeight: 600, color: '#f59e0b' }}>
                                    {Math.round(g.ai_fit_score * 100)}%
                                  </span>
                                )}
                                <ExternalLink size={11} style={{ color: 'var(--color-text-muted)', flexShrink: 0 }} />
                              </div>
                            ))}
                          </div>
                        </div>
                      ) : (
                        <div style={{ fontSize: 12, color: 'var(--color-text-muted)', fontStyle: 'italic' }}>
                          No related grants found yet. Try discovering grants first.
                        </div>
                      )}
                    </div>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}

      {/* ─── AI Create Modal ─────────────────────────────────────── */}
      {showAiModal && (
        <div
          style={{
            position: 'fixed', inset: 0, zIndex: 50,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            backgroundColor: 'rgba(0,0,0,0.6)',
            backdropFilter: 'blur(4px)',
          }}
          onClick={(e) => { if (e.target === e.currentTarget) setShowAiModal(false); }}
        >
          <div
            style={{
              width: '100%', maxWidth: 600, maxHeight: '90vh', overflow: 'auto',
              backgroundColor: 'var(--color-surface)',
              border: '1px solid var(--color-border)',
              borderRadius: 12, padding: 24,
            }}
          >
            <div className="flex items-center justify-between mb-4">
              <h3 className="text-base font-semibold" style={{ color: 'var(--color-text)' }}>
                <Sparkles size={16} style={{ display: 'inline', marginRight: 6, color: 'var(--color-primary)' }} />
                AI Create Petition
              </h3>
              <button className="btn btn-ghost btn-sm" onClick={() => setShowAiModal(false)}>
                <X size={16} />
              </button>
            </div>

            {!aiResult ? (
              <div className="flex flex-col gap-4">
                <div>
                  <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>
                    Topic *
                  </label>
                  <input
                    className="input"
                    placeholder="e.g., Student loan interest rate reduction for federal loans"
                    value={aiTopic}
                    onChange={(e) => setAiTopic(e.target.value)}
                  />
                </div>
                <div>
                  <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>
                    Target (optional)
                  </label>
                  <input
                    className="input"
                    placeholder="e.g., Department of Education"
                    value={aiTarget}
                    onChange={(e) => setAiTarget(e.target.value)}
                  />
                </div>
                <div className="grid grid-cols-2 gap-4">
                  <div>
                    <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>
                      Tone
                    </label>
                    <select
                      className="input"
                      value={aiTone}
                      onChange={(e) => setAiTone(e.target.value)}
                    >
                      <option value="urgent">Urgent</option>
                      <option value="formal">Formal</option>
                      <option value="passionate">Passionate</option>
                      <option value="grassroots">Grassroots</option>
                    </select>
                  </div>
                  <div>
                    <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>
                      Category
                    </label>
                    <select
                      className="input"
                      value={aiCategory}
                      onChange={(e) => setAiCategory(e.target.value)}
                    >
                      <option value="debt_relief">Debt Relief</option>
                      <option value="policy">Policy</option>
                      <option value="education_access">Education Access</option>
                      <option value="consumer_protection">Consumer Protection</option>
                      <option value="federal_budget">Federal Budget</option>
                    </select>
                  </div>
                </div>
                <button
                  className="btn btn-primary w-full"
                  onClick={handleAiGenerate}
                  disabled={aiGenerating || !aiTopic.trim()}
                >
                  {aiGenerating ? (
                    <>
                      <Loader2 size={16} className="animate-spin" />
                      Generating...
                    </>
                  ) : (
                    <>
                      <Sparkles size={16} />
                      Generate Petition
                    </>
                  )}
                </button>
              </div>
            ) : (
              /* ── AI Result Preview ─── */
              <div className="flex flex-col gap-4">
                <div>
                  <div className="text-xs font-medium mb-1" style={{ color: 'var(--color-text-muted)' }}>Title</div>
                  <div className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>{aiResult.title}</div>
                </div>
                <div>
                  <div className="text-xs font-medium mb-1" style={{ color: 'var(--color-text-muted)' }}>Summary</div>
                  <div className="text-sm" style={{ color: 'var(--color-text-secondary)' }}>{aiResult.summary}</div>
                </div>
                <div>
                  <div className="text-xs font-medium mb-1" style={{ color: 'var(--color-text-muted)' }}>Target</div>
                  <div className="text-sm" style={{ color: 'var(--color-text-secondary)' }}>{aiResult.suggested_target}</div>
                </div>
                <div>
                  <div className="text-xs font-medium mb-1" style={{ color: 'var(--color-text-muted)' }}>Body Preview</div>
                  <div
                    className="text-xs"
                    style={{
                      color: 'var(--color-text-muted)',
                      maxHeight: 200, overflow: 'auto',
                      padding: 8, borderRadius: 6,
                      backgroundColor: 'var(--color-surface-el)',
                      whiteSpace: 'pre-wrap',
                    }}
                  >
                    {stripHtml(aiResult.body_html)}
                  </div>
                </div>
                <div className="flex items-center gap-2 flex-wrap">
                  <span className="text-xs" style={{ color: 'var(--color-text-muted)' }}>Tags:</span>
                  {aiResult.suggested_tags.map((t, i) => (
                    <span key={i} className="text-xs" style={{
                      color: 'var(--color-secondary)',
                      padding: '2px 8px', borderRadius: 9999,
                      backgroundColor: 'rgba(124, 58, 237, 0.1)',
                    }}>
                      {t}
                    </span>
                  ))}
                </div>
                <div className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
                  Suggested Goal: {aiResult.suggested_goal} signatures
                </div>
                <div className="flex items-center gap-3">
                  <button className="btn btn-primary flex-1" onClick={handleSaveDraft} disabled={savingDraft}>
                    {savingDraft ? (
                      <><Loader2 size={16} className="animate-spin" /> Saving...</>
                    ) : (
                      <><Plus size={16} /> Save as Draft</>
                    )}
                  </button>
                  <button className="btn btn-ghost" onClick={() => setAiResult(null)}>
                    Regenerate
                  </button>
                </div>
              </div>
            )}
          </div>
        </div>
      )}

      {/* ─── Edit Modal ──────────────────────────────────────────── */}
      {editPetition && (
        <div
          style={{
            position: 'fixed', inset: 0, zIndex: 50,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            backgroundColor: 'rgba(0,0,0,0.6)',
            backdropFilter: 'blur(4px)',
          }}
          onClick={(e) => { if (e.target === e.currentTarget) setEditPetition(null); }}
        >
          <div
            style={{
              width: '100%', maxWidth: 500, maxHeight: '90vh', overflow: 'auto',
              backgroundColor: 'var(--color-surface)',
              border: '1px solid var(--color-border)',
              borderRadius: 12, padding: 24,
            }}
          >
            <div className="flex items-center justify-between mb-4">
              <h3 className="text-base font-semibold" style={{ color: 'var(--color-text)' }}>
                <Edit3 size={16} style={{ display: 'inline', marginRight: 6 }} />
                Edit Petition
              </h3>
              <button className="btn btn-ghost btn-sm" onClick={() => setEditPetition(null)}>
                <X size={16} />
              </button>
            </div>
            <div className="flex flex-col gap-4">
              <div>
                <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Title</label>
                <input className="input" value={editTitle} onChange={(e) => setEditTitle(e.target.value)} />
              </div>
              <div>
                <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Summary</label>
                <textarea
                  className="input"
                  rows={3}
                  value={editSummary}
                  onChange={(e) => setEditSummary(e.target.value)}
                  style={{ resize: 'vertical' }}
                />
              </div>
              <div>
                <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Target</label>
                <input className="input" value={editTarget} onChange={(e) => setEditTarget(e.target.value)} />
              </div>
              <div className="grid grid-cols-2 gap-4">
                <div>
                  <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Signature Goal</label>
                  <input className="input" type="number" value={editGoal} onChange={(e) => setEditGoal(Number(e.target.value))} />
                </div>
                <div>
                  <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Status</label>
                  <select className="input" value={editStatus} onChange={(e) => setEditStatus(e.target.value)}>
                    <option value="draft">Draft</option>
                    <option value="active">Active</option>
                    <option value="paused">Paused</option>
                    <option value="closed">Closed</option>
                    <option value="delivered">Delivered</option>
                  </select>
                </div>
              </div>
              <button className="btn btn-primary w-full" onClick={handleSaveEdit} disabled={editSaving}>
                {editSaving ? <><Loader2 size={16} className="animate-spin" /> Saving...</> : 'Save Changes'}
              </button>
            </div>
          </div>
        </div>
      )}

      {/* ─── Deploy Modal ──────────────────────────────────────────── */}
      {showDeployModal && deployPetition && (
        <div
          style={{
            position: 'fixed', inset: 0, zIndex: 50,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            backgroundColor: 'rgba(0,0,0,0.6)',
            backdropFilter: 'blur(4px)',
          }}
          onClick={(e) => { if (e.target === e.currentTarget) { setShowDeployModal(false); setDeployResults([]); } }}
        >
          <div
            style={{
              width: '100%', maxWidth: 640, maxHeight: '90vh', overflow: 'auto',
              backgroundColor: 'var(--color-surface)',
              border: '1px solid var(--color-border)',
              borderRadius: 12, padding: 24,
            }}
          >
            <div className="flex items-center justify-between mb-4">
              <h3 className="text-base font-semibold" style={{ color: 'var(--color-text)' }}>
                <Send size={16} style={{ display: 'inline', marginRight: 6, color: 'var(--color-primary)' }} />
                Deploy to Platforms
              </h3>
              <button className="btn btn-ghost btn-sm" onClick={() => { setShowDeployModal(false); setDeployResults([]); }}>
                <X size={16} />
              </button>
            </div>

            {/* Petition Info */}
            <div style={{ padding: '10px 14px', borderRadius: 8, backgroundColor: 'var(--color-surface-el)', marginBottom: 16 }}>
              <div className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>{deployPetition.title}</div>
              <div className="text-xs mt-1" style={{ color: 'var(--color-text-muted)' }}>
                {categoryLabel(deployPetition.category)} · {deployPetition.signature_count} signatures · {deployPetition.status}
              </div>
            </div>

            {/* Platform Checkboxes */}
            {deployResults.length === 0 && (
              <>
                <div className="text-xs font-medium mb-2" style={{ color: 'var(--color-text-secondary)' }}>Select Platforms:</div>
                <div className="flex flex-col gap-2 mb-4">
                  {DEPLOY_PLATFORMS.map(plat => (
                    <label
                      key={plat.key}
                      style={{
                        display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px',
                        borderRadius: 8, cursor: 'pointer',
                        border: `1px solid ${deployPlatforms[plat.key] ? 'var(--color-primary)' : 'var(--color-border)'}`,
                        backgroundColor: deployPlatforms[plat.key] ? 'rgba(124, 58, 237, 0.08)' : 'transparent',
                        transition: 'all 0.15s ease',
                      }}
                    >
                      <input
                        type="checkbox"
                        checked={!!deployPlatforms[plat.key]}
                        onChange={(e) => setDeployPlatforms(prev => ({ ...prev, [plat.key]: e.target.checked }))}
                        style={{ accentColor: 'var(--color-primary)', width: 16, height: 16 }}
                      />
                      <span style={{ fontSize: 18 }}>{plat.icon}</span>
                      <div style={{ flex: 1 }}>
                        <div className="text-sm font-medium" style={{ color: 'var(--color-text)' }}>{plat.name}</div>
                        {plat.url && <div className="text-xs" style={{ color: 'var(--color-text-muted)' }}>{plat.url}</div>}
                      </div>
                    </label>
                  ))}
                  {deployPlatforms.custom && (
                    <input
                      className="input mt-1"
                      placeholder="Enter custom platform URL..."
                      value={deployCustomUrl}
                      onChange={(e) => setDeployCustomUrl(e.target.value)}
                    />
                  )}
                </div>

                {/* Select/Clear Buttons */}
                <div className="flex items-center gap-2 mb-4">
                  <button
                    className="btn btn-ghost btn-sm"
                    onClick={() => setDeployPlatforms({ moveon: true, change_org: true, we_the_people: true, custom: true })}
                  >
                    Select All
                  </button>
                  <button
                    className="btn btn-ghost btn-sm"
                    onClick={() => setDeployPlatforms({ moveon: false, change_org: false, we_the_people: false, custom: false })}
                  >
                    Clear All
                  </button>
                </div>

                {/* Deploy Button */}
                <button
                  className="btn btn-primary w-full"
                  onClick={handleDeploy}
                  disabled={deploying || !Object.values(deployPlatforms).some(Boolean)}
                >
                  {deploying ? (
                    <><Loader2 size={16} className="animate-spin" /> Generating...</>
                  ) : (
                    <><Send size={16} /> Generate &amp; Deploy</>
                  )}
                </button>
              </>
            )}

            {/* Deploy Results */}
            {deployResults.length > 0 && (
              <div className="flex flex-col gap-4">
                <div className="text-xs font-medium" style={{ color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em' }}>
                  Results ({deployResults.length} platform{deployResults.length > 1 ? 's' : ''})
                </div>
                {deployResults.map((r) => (
                  <div key={r.platform} style={{ borderRadius: 8, border: '1px solid var(--color-border)', overflow: 'hidden' }}>
                    <div style={{ padding: '10px 14px', backgroundColor: 'rgba(124, 58, 237, 0.08)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                      <div className="flex items-center gap-2">
                        <span style={{ color: '#10b981', fontSize: 14 }}>✓</span>
                        <span className="text-sm font-medium" style={{ color: 'var(--color-text)' }}>{r.platform_name}</span>
                        <span className="text-xs" style={{ color: 'var(--color-text-muted)' }}>— Content ready</span>
                      </div>
                      <button
                        className="btn btn-sm btn-primary"
                        onClick={() => window.open(r.creation_url, '_blank')}
                      >
                        <ExternalLink size={12} /> Open Platform
                      </button>
                    </div>
                    <div style={{ padding: 14 }}>
                      {Object.entries(r.formatted_content).map(([key, value]) => (
                        <div key={key} style={{ marginBottom: 8 }}>
                          <div className="flex items-center justify-between mb-1">
                            <span className="text-xs font-medium" style={{ color: 'var(--color-text-muted)', textTransform: 'capitalize' }}>
                              {key.replace(/_/g, ' ')}
                            </span>
                            <button
                              className="text-xs"
                              style={{
                                padding: '2px 8px', borderRadius: 4, cursor: 'pointer',
                                border: '1px solid var(--color-border)',
                                backgroundColor: copiedField === `${r.platform}-${key}` ? 'rgba(16, 185, 129, 0.15)' : 'transparent',
                                color: copiedField === `${r.platform}-${key}` ? '#10b981' : 'var(--color-text-muted)',
                                transition: 'all 0.15s',
                              }}
                              onClick={() => copyToClipboard(
                                Array.isArray(value) ? (value as string[]).join(', ') : String(value ?? ''),
                                `${r.platform}-${key}`
                              )}
                            >
                              {copiedField === `${r.platform}-${key}` ? 'Copied!' : 'Copy'}
                            </button>
                          </div>
                          <div
                            className="text-xs"
                            style={{
                              color: 'var(--color-text-secondary)',
                              padding: '6px 8px', borderRadius: 4,
                              backgroundColor: 'var(--color-surface-el)',
                              maxHeight: 80, overflow: 'auto',
                              whiteSpace: 'pre-wrap', wordBreak: 'break-word',
                            }}
                          >
                            {Array.isArray(value) ? (value as string[]).join(', ') : String(value ?? '—')}
                          </div>
                        </div>
                      ))}
                    </div>
                  </div>
                ))}
                <button
                  className="btn btn-ghost w-full"
                  onClick={() => setDeployResults([])}
                >
                  ← Back to Platform Selection
                </button>
              </div>
            )}
          </div>
        </div>
      )}
    </div>
  );
}