/**
 * Upstream Command Center — React SPA
 * Hash-based routing, no react-router required.
 *
 * Pages: Overview, Dashboard, Intelligence, Batch Review, Ads, Pipeline,
 *        Newsletter, Digest, Analytics, Activity Log, + Sales/Contact placeholders
 *
 * Security note: Blog tab renders pipeline-generated HTML. DOMPurify.sanitize()
 * is applied before any DOM mutation (defense-in-depth per PREV-04 spec).
 *
 * Sales page components (OverviewPage, AnalyticsPage, ActivityLogPage, etc.)
 * are defined in sales_app.jsx which loads before this file.
 */
const { useState, useEffect, useCallback, useRef } = React;

// ---------------------------------------------------------------------------
// API base URL — empty string for same-origin (local dev), set for Cloud Run
// ---------------------------------------------------------------------------
const API_BASE = window.__UPSTREAM_API_BASE || "";

function apiFetch(path, options = {}) {
  return fetch(API_BASE + path, {
    ...options,
    credentials: "include",
    headers: { ...options.headers, "Content-Type": "application/json" },
  });
}

// ---------------------------------------------------------------------------
// Shared components
// ---------------------------------------------------------------------------

function Badge({ status }) {
  const cls = {
    active: "badge badge-ok",
    idle:   "badge badge-pending",
    ok:     "badge badge-ok",
    error:  "badge badge-error",
  }[status] || "badge";
  return React.createElement("span", { className: cls }, status);
}

function QualityBadge({ score }) {
  if (score == null) return null;
  const n = parseFloat(score);
  const cls = n >= 8 ? "badge badge-ok" : n >= 6 ? "badge badge-pending" : "badge badge-error";
  return React.createElement("span", { className: cls }, n.toFixed(1));
}

function PillarBadge({ pillar }) {
  const label = (pillar || "").replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
  return React.createElement("span", { className: "pillar-badge" }, label);
}

function CopyButton({ text }) {
  const [copied, setCopied] = useState(false);
  function handleCopy() {
    navigator.clipboard.writeText(text || "").then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 1500);
    });
  }
  return (
    <button className="copy-btn" onClick={handleCopy} title="Copy to clipboard">
      {copied ? <span className="copy-feedback">Copied!</span> : "Copy"}
    </button>
  );
}

function Table({ columns, rows, emptyMessage }) {
  if (!rows || rows.length === 0) {
    return <div className="empty-state">{emptyMessage || "No data"}</div>;
  }
  return (
    <table className="data-table">
      <thead>
        <tr>{columns.map(c => <th key={c.key || c.label}>{c.label}</th>)}</tr>
      </thead>
      <tbody>
        {rows.map((row, i) => (
          <tr key={i}>
            {columns.map(c => (
              <td key={c.key || c.label}>{c.render ? c.render(row) : row[c.key]}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

// ---------------------------------------------------------------------------
// Dashboard page
// ---------------------------------------------------------------------------

function TrackCard({ track }) {
  const accentColors = {
    "Intelligence": "#2D7DD2",
    "Digest":       "#4CAF7D",
    "Blog":         "#9B59B6",
  };
  const accent = accentColors[track.name] || "#2D7DD2";

  return (
    <div className="track-card" style={{ borderLeftColor: accent }}>
      <div className="track-card-header">
        <div className="track-name">{track.name}</div>
        <Badge status={track.status} />
      </div>
      <div className="track-metrics">
        {track.brief_count != null && (
          <div className="metric-row">
            <span className="metric-label">Briefs</span>
            <span className="metric-value mono">{track.brief_count}</span>
          </div>
        )}
        {track.articles_published != null && (
          <div className="metric-row">
            <span className="metric-label">Published</span>
            <span className="metric-value mono">{track.articles_published}</span>
          </div>
        )}
        {track.articles_pending != null && (
          <div className="metric-row">
            <span className="metric-label">Pending</span>
            <span className="metric-value mono">{track.articles_pending}</span>
          </div>
        )}
      </div>
      <div className="track-last-run">
        {track.last_run ? `Last run: ${track.last_run}` : "No runs yet"}
      </div>
    </div>
  );
}

function DashboardPage() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    apiFetch("/api/dashboard")
      .then(r => r.json())
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  }, []);

  if (loading) return <div className="loading">Loading dashboard...</div>;
  if (error) return <div className="error-state">Error: {error}</div>;

  const { tracks = [], pipeline_health = [], analytics = {} } = data;

  const healthColumns = [
    { key: "module", label: "Module" },
    { key: "last_success", label: "Last Success", render: r => r.last_success || "—" },
    { key: "next_scheduled", label: "Next Scheduled" },
    { key: "status", label: "Status", render: r => <Badge status={r.status} /> },
  ];

  return (
    <div>
      <div className="page-header">
        <h2>Dashboard</h2>
        <p className="subtitle">Pipeline health and 30-day activity at a glance</p>
      </div>

      <div className="track-grid">
        {tracks.map(track => <TrackCard key={track.name} track={track} />)}
      </div>

      <div className="section-block">
        <h3 className="section-title">Pipeline Health</h3>
        <Table columns={healthColumns} rows={pipeline_health} emptyMessage="No pipeline data yet" />
      </div>

      <div className="analytics-row">
        <div className="analytics-card">
          <div className="card-title">Monthly Cost</div>
          <div className="card-value mono">${(analytics.total_cost_month || 0).toFixed(2)}</div>
          <div className="card-meta">This month (USD)</div>
        </div>
        <div className="analytics-card">
          <div className="card-title">Articles Published</div>
          <div className="card-value mono">{analytics.articles_this_month || 0}</div>
          <div className="card-meta">This month</div>
        </div>
        <div className="analytics-card">
          <div className="card-title">Social Posts Queued</div>
          <div className="card-value mono">{analytics.social_posts_queued || 0}</div>
          <div className="card-meta">Typefully queue</div>
        </div>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Intelligence page
// ---------------------------------------------------------------------------

function IntelligencePage() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [sortField, setSortField] = useState("novelty_score");
  const [sortDir, setSortDir] = useState("desc");

  useEffect(() => {
    apiFetch("/api/intelligence")
      .then(r => r.json())
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  }, []);

  if (loading) return <div className="loading">Loading intelligence brief...</div>;
  if (error) return <div className="error-state">Error: {error}</div>;

  const { brief = {}, signals = [], topic_ideas = [] } = data;

  const sortedSignals = [...signals].sort((a, b) => {
    const av = a[sortField] ?? 0;
    const bv = b[sortField] ?? 0;
    return sortDir === "desc" ? bv - av : av - bv;
  });

  function toggleSort(field) {
    if (sortField === field) {
      setSortDir(d => d === "desc" ? "asc" : "desc");
    } else {
      setSortField(field);
      setSortDir("desc");
    }
  }

  const sortIcon = (field) => sortField === field ? (sortDir === "desc" ? " ↓" : " ↑") : "";

  return (
    <div>
      <div className="page-header">
        <h2>Intelligence</h2>
        <p className="subtitle">Latest weekly brief, signal table, and topic ideas</p>
      </div>

      {brief && (brief.community_pulse || brief.policy_and_industry || brief.connecting_thread) && (
        <div className="brief-card section-block">
          <h3 className="section-title">Weekly Brief</h3>
          {brief.week_id && <div className="brief-week">Week: {brief.week_id}</div>}
          <div className="brief-sections">
            {brief.community_pulse && (
              <div className="brief-section">
                <div className="brief-section-label">Community Pulse</div>
                <div className="brief-section-body">{brief.community_pulse}</div>
              </div>
            )}
            {brief.policy_and_industry && (
              <div className="brief-section">
                <div className="brief-section-label">Policy and Industry</div>
                <div className="brief-section-body">{brief.policy_and_industry}</div>
              </div>
            )}
            {brief.connecting_thread && (
              <div className="brief-section">
                <div className="brief-section-label">Connecting Thread</div>
                <div className="brief-section-body">{brief.connecting_thread}</div>
              </div>
            )}
          </div>
        </div>
      )}

      <div className="section-block">
        <h3 className="section-title">Signals ({signals.length})</h3>
        {signals.length === 0 ? (
          <div className="empty-state">No signals yet — run uv run pipeline/run.py monitor</div>
        ) : (
          <table className="data-table signals-table">
            <thead>
              <tr>
                <th>Source</th>
                <th>Title</th>
                <th>Category</th>
                <th className="sortable-col" onClick={() => toggleSort("novelty_score")}>
                  Novelty{sortIcon("novelty_score")}
                </th>
              </tr>
            </thead>
            <tbody>
              {sortedSignals.map((s, i) => (
                <tr key={i}>
                  <td className="mono text-muted">{s.source || "—"}</td>
                  <td>{s.title || s.headline || "—"}</td>
                  <td>{s.category || s.pillar || "—"}</td>
                  <td className="mono"><QualityBadge score={s.novelty_score} /></td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </div>

      {topic_ideas && topic_ideas.length > 0 && (
        <div className="section-block">
          <h3 className="section-title">Topic Suggestions ({topic_ideas.length})</h3>
          <div className="topic-grid">
            {topic_ideas.map((t, i) => (
              <div key={i} className="topic-card">
                <div className="topic-title">{t.title || t.topic || "Untitled"}</div>
                {t.pillar && <PillarBadge pillar={t.pillar} />}
                {t.seo_angle && <div className="topic-seo">{t.seo_angle}</div>}
                {t.novelty_score != null && (
                  <div className="topic-meta">Novelty: <QualityBadge score={t.novelty_score} /></div>
                )}
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

// ---------------------------------------------------------------------------
// Batch page — master-detail with 8-tab article preview
// ---------------------------------------------------------------------------

const TAB_LABELS = [
  { id: "blog",                  label: "Blog" },
  { id: "intelligence_edition",  label: "Intelligence Edition" },
  { id: "executive_briefing",    label: "Executive Briefing" },
  { id: "linkedin_post",         label: "LinkedIn Post" },
  { id: "linkedin_article",      label: "LinkedIn Article" },
  { id: "substack",              label: "Substack" },
  { id: "video_script",          label: "Video Script" },
  { id: "ads",                   label: "Ads" },
];

/**
 * Renders blog HTML in a sandboxed preview div.
 * All content is pipeline-generated (not user input).
 * DOMPurify sanitization applied before DOM update — defense-in-depth per PREV-04.
 */
function BlogTab({ article }) {
  const containerRef = useRef(null);
  const rawHtml = article.blog_post || article.blog_html || "";

  useEffect(() => {
    if (!containerRef.current || !rawHtml) return;
    // Sanitize pipeline-generated HTML before rendering (defense-in-depth)
    const sanitized = typeof DOMPurify !== "undefined"
      ? DOMPurify.sanitize(rawHtml, { USE_PROFILES: { html: true } })
      : rawHtml;
    // Safe: sanitized content has been processed by DOMPurify
    containerRef.current.textContent = "";
    const wrapper = document.createElement("div");
    wrapper.className = "blog-preview prose";
    // DOMParser approach: parse sanitized HTML into a doc, then append childNodes
    const parsed = new DOMParser().parseFromString(sanitized, "text/html");
    Array.from(parsed.body.childNodes).forEach(node => wrapper.appendChild(node.cloneNode(true)));
    containerRef.current.appendChild(wrapper);
  }, [rawHtml]);

  if (!rawHtml) {
    return <div className="tab-empty">No blog content — run generate first.</div>;
  }

  return <div ref={containerRef} className="blog-preview-wrapper" />;
}

function TextTab({ content, label }) {
  if (!content) return <div className="tab-empty">No {label} content yet.</div>;
  if (Array.isArray(content)) {
    return (
      <div className="text-content">
        {content.map((item, i) => (
          <div key={i} className="thread-item">
            <span className="thread-index mono">{i + 1}.</span>
            <span>{typeof item === "string" ? item : JSON.stringify(item, null, 2)}</span>
          </div>
        ))}
      </div>
    );
  }
  return (
    <div className="text-content">
      <pre className="text-pre">{content}</pre>
    </div>
  );
}

function VideoScriptTab({ article }) {
  const script = article.video_script || article.video_narration || "";
  if (!script) return <div className="tab-empty">No video script yet.</div>;
  return (
    <div className="text-content">
      <pre className="text-pre">{script}</pre>
    </div>
  );
}

function AdsTab({ article, batchId }) {
  const [adData, setAdData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (article.ad_brief) {
      setAdData(article.ad_brief);
      setLoading(false);
      return;
    }
    const month = batchId || new Date().toISOString().slice(0, 7);
    apiFetch(`/api/ads/${month}`)
      .then(r => r.json())
      .then(d => {
        const briefs = d.briefs || [];
        const match = briefs.find(b => b.article_id === article.article_id);
        setAdData(match || null);
        setLoading(false);
      })
      .catch(() => { setAdData(null); setLoading(false); });
  }, [article.article_id, batchId]);

  if (loading) return <div className="tab-empty">Loading ad variants...</div>;

  if (!adData) {
    return (
      <div className="tab-empty">
        No ad briefs yet — run: uv run pipeline/run.py ads --month {batchId}
      </div>
    );
  }

  const variants = adData.variants || [];
  const targeting = adData.targeting || {};

  return (
    <div className="ads-tab-content">
      {adData.boost_candidate && <div className="boost-badge">Boost Candidate</div>}

      {adData.recommended_image && (
        <div className="ad-image-row">
          <span className="ad-field-label">Social card: </span>
          <span className="mono">{adData.recommended_image}</span>
        </div>
      )}

      {Object.keys(targeting).length > 0 && (
        <div className="ad-targeting section-block">
          <div className="section-title">Targeting</div>
          {targeting.job_titles && (
            <div className="targeting-row">
              <span className="ad-field-label">Titles:</span>
              <span>{targeting.job_titles.join(", ")}</span>
            </div>
          )}
          {targeting.industries && (
            <div className="targeting-row">
              <span className="ad-field-label">Industries:</span>
              <span>{targeting.industries.join(", ")}</span>
            </div>
          )}
          {targeting.company_size && (
            <div className="targeting-row">
              <span className="ad-field-label">Company size:</span>
              <span>{targeting.company_size}</span>
            </div>
          )}
        </div>
      )}

      <div className="ad-variants">
        {variants.map((v, i) => (
          <div key={i} className="ad-variant-card">
            <div className="ad-variant-header">
              <span className="ad-variant-label">Variant {i + 1}</span>
              <CopyButton text={[v.headline, v.body, v.cta].filter(Boolean).join("\n\n")} />
            </div>
            <div className="ad-field">
              <div className="ad-field-label">Headline</div>
              <div className="ad-field-value">{v.headline}</div>
            </div>
            <div className="ad-field">
              <div className="ad-field-label">Body</div>
              <div className="ad-field-value">{v.body}</div>
            </div>
            <div className="ad-field">
              <div className="ad-field-label">CTA</div>
              <div className="ad-field-value">{v.cta}</div>
            </div>
          </div>
        ))}
        {variants.length === 0 && <div className="tab-empty">No ad variants in this brief.</div>}
      </div>
    </div>
  );
}

function ArticleDetail({ article, batchId, articleIndex, onArticleUpdate }) {
  const [activeTab, setActiveTab] = useState("blog");
  const [editTitle, setEditTitle] = useState(article.title || "");
  const [editSummary, setEditSummary] = useState(article.summary || "");
  const [editCta, setEditCta] = useState(article.cta_text || "");
  const [saving, setSaving] = useState(false);

  useEffect(() => {
    setEditTitle(article.title || "");
    setEditSummary(article.summary || "");
    setEditCta(article.cta_text || "");
  }, [article.article_id]);

  function saveEdits(overrides) {
    if (saving) return;
    setSaving(true);
    const resolvedId = batchId || "latest";
    apiFetch(`/api/batch/${resolvedId}/article/${articleIndex}`, {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(Object.assign({ title: editTitle, summary: editSummary, cta_text: editCta }, overrides || {})),
    })
      .then(r => r.json())
      .then(updated => { onArticleUpdate(articleIndex, updated); setSaving(false); })
      .catch(() => setSaving(false));
  }

  return (
    <div className="article-detail">
      <div className="article-detail-header">
        <div className="article-title-area">
          <input
            className="edit-title-input"
            value={editTitle}
            onChange={e => setEditTitle(e.target.value)}
            onBlur={() => saveEdits()}
            placeholder="Article title"
          />
          <PillarBadge pillar={article.pillar_tag || article.pillar} />
          {article.quality_score != null && <QualityBadge score={article.quality_score} />}
        </div>
        <div className="article-edit-fields">
          <textarea
            className="edit-summary-input"
            value={editSummary}
            onChange={e => setEditSummary(e.target.value)}
            onBlur={() => saveEdits()}
            placeholder="Summary (SEO description, shown in newsletter)"
            rows={2}
          />
          <input
            className="edit-cta-input"
            value={editCta}
            onChange={e => setEditCta(e.target.value)}
            onBlur={() => saveEdits()}
            placeholder="CTA button text (default: Book Your Payer Intelligence Audit)"
          />
        </div>
      </div>

      <div className="tab-bar">
        {TAB_LABELS.map(t => (
          <button
            key={t.id}
            className={"tab-btn" + (activeTab === t.id ? " active" : "")}
            onClick={() => setActiveTab(t.id)}
          >
            {t.label}
          </button>
        ))}
      </div>

      <div className="tab-content">
        {activeTab === "blog" && <BlogTab article={article} />}
        {activeTab === "intelligence_edition" && (
          <TextTab content={article.intelligence_section} label="Intelligence Edition" />
        )}
        {activeTab === "executive_briefing" && (
          <TextTab content={article.briefing_section} label="Executive Briefing" />
        )}
        {activeTab === "linkedin_post" && (
          <div className="text-content-with-copy">
            <CopyButton text={article.linkedin_post} />
            <TextTab content={article.linkedin_post} label="LinkedIn Post" />
          </div>
        )}
        {activeTab === "linkedin_article" && (
          <TextTab content={article.linkedin_article} label="LinkedIn Article" />
        )}
        {activeTab === "substack" && (
          <TextTab content={article.substack_post} label="Substack" />
        )}
        {activeTab === "video_script" && <VideoScriptTab article={article} />}
        {activeTab === "ads" && (
          <AdsTab article={article} batchId={batchId} articleIndex={articleIndex} />
        )}
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Publish workflow components — Plan 03 (PREV-07)
// ---------------------------------------------------------------------------

function PublishLogPanel({ publishId, onComplete }) {
  const [logs, setLogs] = useState([]);
  const [progress, setProgress] = useState(0);
  const logsEndRef = useRef(null);

  useEffect(() => {
    if (!publishId) return;
    const es = new EventSource("/api/status/" + publishId);
    es.onmessage = (e) => {
      const event = JSON.parse(e.data);
      setLogs(prev => [...prev, event]);
      if (event.step === "publishing_blog" && event.status === "success") {
        setProgress(prev => prev + 1);
      }
      if (event.step === "complete") {
        es.close();
        onComplete(event.summary);
      }
    };
    es.onerror = () => es.close();
    return () => es.close();
  }, [publishId]);

  useEffect(() => {
    if (logsEndRef.current) {
      logsEndRef.current.scrollIntoView({ behavior: "smooth" });
    }
  }, [logs]);

  return (
    <div className="publish-log-panel">
      <div className="publish-progress-bar">
        <div
          className="publish-progress-fill"
          style={{ width: Math.min(progress * 20, 95) + "%", transition: "width .4s ease" }}
        />
      </div>
      <div className="publish-log-header">Publish Log</div>
      <div className="publish-log-body">
        {logs.filter(l => l.step !== "complete").map((log, i) => (
          <div key={i} className={"log-line log-" + log.status}>
            <span className="log-icon">
              {log.status === "success" ? "✓" : log.status === "error" ? "✗" : "…"}
            </span>
            <span className="log-step">{(log.step || "").replace(/_/g, " ")}</span>
            {log.article && <span className="log-article">{log.article}</span>}
            <span className="log-detail">{log.detail}</span>
            {log.url && (
              <a href={log.url} target="_blank" rel="noreferrer" className="log-url">{log.url}</a>
            )}
          </div>
        ))}
        <div ref={logsEndRef} />
      </div>
    </div>
  );
}

function PostPublishSummary({ summary, onClose }) {
  const blogUrls = (summary.blog_urls || []).filter(Boolean);
  const typefullyIds = (summary.typefully_ids || []).filter(Boolean);
  const videoUrls = (summary.video_urls || []).filter(Boolean);

  return (
    <div className="post-publish-summary">
      <div className="post-publish-banner">All approved articles published successfully</div>

      {blogUrls.length > 0 && (
        <div className="summary-section">
          <div className="summary-section-title">Ghost Blog Posts</div>
          <div className="link-grid">
            {blogUrls.map((url, i) => (
              <a key={i} href={url} target="_blank" rel="noreferrer" className="link-card">{url}</a>
            ))}
          </div>
        </div>
      )}

      {typefullyIds.length > 0 && (
        <div className="summary-section">
          <div className="summary-section-title">Typefully Social Queue</div>
          <div className="link-grid">
            {typefullyIds.map((id, i) => (
              <a key={i} href={"https://typefully.com/drafts/" + id} target="_blank" rel="noreferrer" className="link-card">
                Draft {id}
              </a>
            ))}
          </div>
        </div>
      )}

      {videoUrls.length > 0 && (
        <div className="summary-section">
          <div className="summary-section-title">R2 Video URLs</div>
          <div className="link-grid">
            {videoUrls.map((url, i) => (
              <a key={i} href={url} target="_blank" rel="noreferrer" className="link-card">{url}</a>
            ))}
          </div>
        </div>
      )}

      <div className="summary-footer">
        <div className="summary-cost">
          Total cost this run: <strong>${(summary.total_cost || 0).toFixed(2)}</strong>
          <span className="text-muted"> · Summary also sent to Telegram</span>
        </div>
        <button className="secondary-btn" onClick={onClose}>Close</button>
      </div>
    </div>
  );
}

function BatchPage() {
  const [batchData, setBatchData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [selectedIndex, setSelectedIndex] = useState(0);
  const [publishing, setPublishing] = useState(false);
  const [publishId, setPublishId] = useState(null);
  const [publishSummary, setPublishSummary] = useState(null);

  useEffect(() => {
    apiFetch("/api/batch/latest")
      .then(r => r.json())
      .then(d => { setBatchData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  }, []);

  function handleApproveToggle(index) {
    const article = batchData.articles[index];
    const newApproved = !article.approved;
    const resolvedId = batchData.batch_id || "latest";

    apiFetch("/api/batch/" + resolvedId + "/article/" + index, {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ approved: newApproved }),
    })
      .then(r => r.json())
      .then(updated => {
        setBatchData(prev => {
          const articles = [...prev.articles];
          articles[index] = Object.assign({}, articles[index], updated);
          return Object.assign({}, prev, { articles });
        });
      })
      .catch(() => {});
  }

  function handleArticleUpdate(index, updatedArticle) {
    setBatchData(prev => {
      const articles = [...prev.articles];
      articles[index] = Object.assign({}, articles[index], updatedArticle);
      return Object.assign({}, prev, { articles });
    });
  }

  async function handlePublish() {
    if (!batchData) return;
    setPublishing(true);
    setPublishSummary(null);
    setPublishId(null);
    try {
      const r = await apiFetch("/api/publish", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ batch_id: batchData.batch_id || "latest" }),
      });
      const { publish_id } = await r.json();
      setPublishId(publish_id);
    } catch (e) {
      console.error("Publish failed:", e);
      setPublishing(false);
    }
  }

  if (loading) return <div className="loading">Loading batch...</div>;
  if (error) return <div className="error-state">Error: {error}</div>;

  if (!batchData || !batchData.articles || batchData.articles.length === 0) {
    return (
      <div>
        <div className="page-header">
          <h2>Batch Review</h2>
          <p className="subtitle">Review and approve articles before publishing</p>
        </div>
        <div className="empty-state">
          No batch found. Run: uv run pipeline/run.py generate
        </div>
      </div>
    );
  }

  const { articles, batch_id } = batchData;
  const approvedCount = articles.filter(a => a.approved).length;
  const selectedArticle = articles[selectedIndex] || articles[0];

  return (
    <div>
      <div className="page-header">
        <h2>Batch Review — {batch_id}</h2>
        <p className="subtitle">
          {approvedCount} of {articles.length} articles approved
        </p>
      </div>

      <div className="batch-action-bar">
        <div className="approve-count">
          <span className="mono">{approvedCount}</span> approved
        </div>
        <button
          className="publish-btn"
          disabled={approvedCount === 0 || publishing}
          title={approvedCount === 0 ? "Approve at least one article to publish" : "Publish all approved articles"}
          onClick={handlePublish}
        >
          {publishing ? "Publishing..." : "PUBLISH APPROVED"}
        </button>
      </div>

      {/* Publish log panel slides in when publish starts */}
      {publishId && !publishSummary && (
        <PublishLogPanel
          publishId={publishId}
          onComplete={s => { setPublishSummary(s); setPublishing(false); }}
        />
      )}

      {/* Post-publish summary card */}
      {publishSummary && (
        <PostPublishSummary
          summary={publishSummary}
          onClose={() => { setPublishSummary(null); setPublishId(null); }}
        />
      )}

      <div className="master-detail">
        <div className="article-list">
          {articles.map((article, i) => (
            <div
              key={i}
              className={"article-list-item" + (i === selectedIndex ? " selected" : "") + (article.approved ? " approved" : "") + (article.skipped ? " skipped" : "")}
              onClick={() => setSelectedIndex(i)}
            >
              <div className="article-list-title">{article.title || ("Article " + (i + 1))}</div>
              <div className="article-list-meta">
                <PillarBadge pillar={article.pillar_tag || article.pillar} />
                {article.quality_score != null && <QualityBadge score={article.quality_score} />}
              </div>
              <div className="article-list-actions" onClick={e => e.stopPropagation()}>
                <button
                  className={"approve-btn" + (article.approved ? " is-approved" : "")}
                  onClick={() => handleApproveToggle(i)}
                >
                  {article.approved ? "Approved" : "Approve"}
                </button>
              </div>
            </div>
          ))}
        </div>

        <div className="detail-pane">
          {selectedArticle && (
            <ArticleDetail
              key={selectedArticle.article_id || selectedIndex}
              article={selectedArticle}
              batchId={batch_id}
              articleIndex={selectedIndex}
              onArticleUpdate={handleArticleUpdate}
            />
          )}
        </div>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Ads standalone page (route: #ads)
// ---------------------------------------------------------------------------

function AdsPage() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const now = new Date();
    const month = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0");
    apiFetch("/api/ads/" + month)
      .then(r => r.json())
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  }, []);

  if (loading) return <div className="loading">Loading ad briefs...</div>;
  if (error) return <div className="error-state">Error: {error}</div>;

  const briefs = (data && data.briefs) || [];
  const month = data && data.month;

  return (
    <div>
      <div className="page-header">
        <h2>Ad Briefs</h2>
        <p className="subtitle">LinkedIn ad copy variants and social card pairings — {month || "current month"}</p>
      </div>

      {briefs.length === 0 ? (
        <div className="empty-state">
          No ad briefs yet — run: uv run pipeline/run.py ads --month {month}
        </div>
      ) : (
        <div className="ads-brief-list">
          {briefs.map((brief, i) => (
            <div key={i} className="ads-article-block">
              <div className="ads-article-header">
                <div className="ads-article-title">{brief.article_title}</div>
                <PillarBadge pillar={brief.pillar_tag} />
                {brief.boost_candidate && <span className="boost-badge">Boost Candidate</span>}
              </div>

              {brief.recommended_image && (
                <div className="ad-image-row">
                  <span className="ad-field-label">Social card: </span>
                  <span className="mono">{brief.recommended_image}</span>
                </div>
              )}

              <div className="ad-variants">
                {(brief.variants || []).map((v, j) => (
                  <div key={j} className="ad-variant-card">
                    <div className="ad-variant-header">
                      <span className="ad-variant-label">Variant {j + 1}</span>
                      <CopyButton text={[v.headline, v.body, v.cta].filter(Boolean).join("\n\n")} />
                    </div>
                    <div className="ad-field">
                      <div className="ad-field-label">Headline</div>
                      <div className="ad-field-value">{v.headline}</div>
                    </div>
                    <div className="ad-field">
                      <div className="ad-field-label">Body</div>
                      <div className="ad-field-value">{v.body}</div>
                    </div>
                    <div className="ad-field">
                      <div className="ad-field-label">CTA</div>
                      <div className="ad-field-value">{v.cta}</div>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ---------------------------------------------------------------------------
// NewsletterPage — Plan 03 (PREV-05)
// ---------------------------------------------------------------------------

function NewsletterPage() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    apiFetch("/api/newsletter/latest")
      .then(r => r.json())
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  }, []);

  if (loading) return <div className="loading">Loading newsletter previews...</div>;
  if (error) return <div className="error-state">Error: {error}</div>;

  const { intelligence_html = "", briefing_html = "" } = data || {};
  const fallback = "<p style='padding:20px;color:#6B7A8D;font-family:system-ui,sans-serif'>No newsletter HTML — run generate to build a batch first.</p>";

  return (
    <div>
      <div className="page-header">
        <h2>Newsletter</h2>
        <p className="subtitle">Intelligence Edition and Executive Briefing preview — rendered in sandboxed iframes</p>
      </div>

      <div className="newsletter-grid">
        <div>
          <div className="iframe-label">Intelligence Edition</div>
          <iframe
            className="email-iframe"
            srcDoc={intelligence_html || fallback}
            sandbox="allow-same-origin"
            title="Intelligence Edition preview"
          />
        </div>
        <div>
          <div className="iframe-label">Executive Briefing</div>
          <iframe
            className="email-iframe"
            srcDoc={briefing_html || fallback}
            sandbox="allow-same-origin"
            title="Executive Briefing preview"
          />
        </div>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// DigestPage — Plan 03 (PREV-06)
// ---------------------------------------------------------------------------

function DigestPage() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    apiFetch("/api/digest/preview")
      .then(r => r.json())
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  }, []);

  if (loading) return <div className="loading">Loading digest preview...</div>;
  if (error) return <div className="error-state">Error: {error}</div>;

  const { html = "", customer_name = "Sample Corp" } = data || {};

  return (
    <div>
      <div className="page-header">
        <h2>Customer Digest</h2>
        <p className="subtitle">Track 2: Customer Digest Preview — rendered with dummy segment data</p>
      </div>

      <div style={{ marginBottom: 12, fontSize: 13, color: "var(--text-muted)" }}>
        Preview customer: <strong>{customer_name}</strong> · Uses dummy metrics (denial_rate: 12%, drift_score: 0.63)
      </div>

      <div>
        <div className="iframe-label">Track 2: Customer Digest Preview (Sample Data)</div>
        <iframe
          className="email-iframe"
          style={{ height: 700, maxWidth: 680 }}
          srcDoc={html || "<p style='padding:20px;color:#6B7A8D;font-family:system-ui,sans-serif'>Digest HTML not available.</p>"}
          sandbox="allow-same-origin"
          title="Customer digest preview"
        />
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Pipeline visualization page
// ---------------------------------------------------------------------------

function PipelineNode({ node, onClick }) {
  const statusColors = { green: "#4CAF7D", yellow: "#F2994A", red: "#E74C3C" };
  const borderColor = statusColors[node.status] || "#6B7A8D";

  return (
    <div
      className="pipeline-node"
      style={{ borderLeftColor: borderColor }}
      onClick={() => onClick && onClick(node)}
    >
      <div className="pipeline-node-header">
        <span className="pipeline-node-label">{node.label}</span>
        <span
          className="pipeline-status-dot"
          style={{ backgroundColor: borderColor }}
        />
      </div>
      <div className="pipeline-node-count mono">{node.item_count}</div>
      <div className="pipeline-node-detail">{node.detail}</div>
      {node.last_run && (
        <div className="pipeline-node-time">{node.last_run}</div>
      )}
    </div>
  );
}

function PipelinePage() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    apiFetch("/api/pipeline")
      .then(r => r.json())
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  }, []);

  if (loading) return <div className="loading">Loading pipeline...</div>;
  if (error) return <div className="error-state">Error: {error}</div>;

  const { nodes = [], channels = [], summary = {} } = data;

  return (
    <div>
      <div className="page-header">
        <h2>Pipeline</h2>
        <p className="subtitle">Visual data flow — Sources through Publish</p>
      </div>

      <div className="pipeline-flow">
        {nodes.map((node, i) => (
          <React.Fragment key={node.id}>
            <PipelineNode node={node} />
            {i < nodes.length - 1 && <div className="pipeline-arrow">&#8594;</div>}
          </React.Fragment>
        ))}
      </div>

      <div className="section-block" style={{ marginTop: 32 }}>
        <h3 className="section-title">Distribution Channels</h3>
        <div className="channel-grid">
          {channels.map(ch => (
            <div key={ch.id} className="channel-card">
              <div className="channel-label">{ch.label}</div>
              <div className="channel-type">{ch.type}</div>
            </div>
          ))}
        </div>
      </div>

      <div className="analytics-row" style={{ marginTop: 24 }}>
        <div className="analytics-card">
          <div className="card-title">Total Articles</div>
          <div className="card-value mono">{summary.total_articles || 0}</div>
        </div>
        <div className="analytics-card">
          <div className="card-title">Approved</div>
          <div className="card-value mono">{summary.approved || 0}</div>
        </div>
        <div className="analytics-card">
          <div className="card-title">Published</div>
          <div className="card-value mono">{summary.published || 0}</div>
        </div>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Login page (password gate for deployed dashboard)
// ---------------------------------------------------------------------------

function LoginPage({ onLogin }) {
  const [password, setPassword] = useState("");
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);

  async function handleSubmit(e) {
    e.preventDefault();
    setLoading(true);
    setError(null);
    try {
      const r = await apiFetch("/api/auth/login", {
        method: "POST",
        body: JSON.stringify({ password }),
      });
      if (r.ok) {
        onLogin();
      } else {
        setError("Wrong password");
      }
    } catch (exc) {
      setError("Connection failed");
    }
    setLoading(false);
  }

  return (
    <div className="login-page">
      <div className="login-card">
        <h1>Upstream Command Center</h1>
        <p className="subtitle">Private dashboard — enter password to continue</p>
        <form onSubmit={handleSubmit}>
          <input
            type="password"
            className="login-input"
            value={password}
            onChange={e => setPassword(e.target.value)}
            placeholder="Dashboard password"
            autoFocus
          />
          <button type="submit" className="publish-btn" disabled={loading} style={{ width: "100%", marginTop: 12 }}>
            {loading ? "Checking..." : "Sign In"}
          </button>
          {error && <div className="error-state" style={{ marginTop: 12 }}>{error}</div>}
        </form>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// CommandBar — floating command palette activated by "/" keypress
// ---------------------------------------------------------------------------

function CommandBar({ onClose }) {
  const [query, setQuery] = React.useState("");
  const [result, setResult] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const inputRef = React.useRef(null);

  React.useEffect(() => { inputRef.current?.focus(); }, []);

  React.useEffect(() => {
    function handler(e) { if (e.key === "Escape") onClose(); }
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [onClose]);

  const QUICK_COMMANDS = [
    "Score leads", "Run monitor", "Status", "Find prospects", "Show pipeline"
  ];

  async function handleSubmit(e) {
    e.preventDefault();
    if (!query.trim()) return;
    setLoading(true);
    setResult(null);
    try {
      const resp = await fetch((window.__UPSTREAM_API_BASE || "") + "/api/ask", {
        method: "POST",
        credentials: "include",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ query })
      });
      const data = await resp.json();
      setResult(data);
    } catch (e) {
      setResult({ error: e.message });
    } finally {
      setLoading(false);
    }
  }

  return (
    <div style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,0.6)", zIndex: 9999,
      display: "flex", alignItems: "flex-start", justifyContent: "center", paddingTop: 120
    }} onClick={onClose}>
      <div style={{
        background: "#1a1f2e", border: "1px solid #2a3040", borderRadius: 10,
        width: "100%", maxWidth: 520, boxShadow: "0 20px 60px rgba(0,0,0,0.5)",
        overflow: "hidden"
      }} onClick={e => e.stopPropagation()}>
        <form onSubmit={handleSubmit} style={{ display: "flex", alignItems: "center", padding: "12px 16px", gap: 10 }}>
          <span style={{ color: "#6B7A8D", fontSize: 18 }}>›</span>
          <input ref={inputRef} value={query} onChange={e => setQuery(e.target.value)}
            placeholder="Score leads · Run monitor · Find prospects..."
            style={{ flex: 1, background: "none", border: "none", color: "#e2e8f0", fontSize: 15, outline: "none" }}
          />
          {loading && <span style={{ color: "#6B7A8D", fontSize: 12 }}>Running...</span>}
        </form>

        {!query && (
          <div style={{ borderTop: "1px solid #2a3040", padding: "8px 16px" }}>
            {QUICK_COMMANDS.map(cmd => (
              <div key={cmd} style={{ padding: "6px 0", color: "#6B7A8D", fontSize: 13, cursor: "pointer" }}
                onClick={() => setQuery(cmd)}>
                {cmd}
              </div>
            ))}
          </div>
        )}

        {result && (
          <div style={{ borderTop: "1px solid #2a3040", padding: "12px 16px", maxHeight: 300, overflowY: "auto" }}>
            {result.error
              ? <span style={{ color: "#e74c3c", fontSize: 13 }}>{result.error}</span>
              : <pre style={{ margin: 0, color: "#e2e8f0", fontSize: 13, whiteSpace: "pre-wrap" }}>
                  {typeof result.output === "string" ? result.output : JSON.stringify(result, null, 2)}
                </pre>
            }
          </div>
        )}
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Navigation config
// ---------------------------------------------------------------------------

const NAV_SECTIONS = [
  { label: "OVERVIEW", items: [
    { hash: "#overview", label: "Overview", component: OverviewPage },
  ]},
  { label: "CONTENT", items: [
    { hash: "#dashboard",    label: "Dashboard",        component: DashboardPage },
    { hash: "#pipeline",     label: "Pipeline",         component: PipelinePage },
    { hash: "#intelligence", label: "Intelligence",     component: IntelligencePage },
    { hash: "#batch",        label: "Batch Review",     component: BatchPage },
    { hash: "#newsletter",   label: "Newsletter",       component: NewsletterPage },
    { hash: "#digest",       label: "Customer Digest",  component: DigestPage },
    { hash: "#ads",          label: "Ad Briefs",        component: AdsPage },
  ]},
  { label: "SALES", items: [
    { hash: "#prospects",  label: "Prospect Feed",     component: ProspectFeedPage },
    { hash: "#sequences",  label: "Active Sequences",  component: SequencesPage },
    { hash: "#deals",      label: "Deal Board",        component: DealBoardPage },
    { hash: "#replies",    label: "Reply Inbox",       component: ReplyInboxPage },
  ]},
  { label: "CREATIVE", items: [
    { hash: "#create", label: "Freestyle Creator", component: FreestyleCreatorPage },
  ]},
  { label: "CONTACTS", items: [
    { hash: "#contacts",  label: "All Contacts",  component: ContactsPage },
    { hash: "#segments",  label: "Segments",       component: SegmentsPage },
  ]},
  { label: "SYSTEM", items: [
    { hash: "#analytics", label: "Analytics",     component: AnalyticsPage },
    { hash: "#activity",  label: "Activity Log",  component: ActivityLogPage },
  ]},
];

const NAV_ITEMS = NAV_SECTIONS.flatMap(s => s.items);

function resolveCurrentHash() {
  const hash = window.location.hash || "#overview";
  // Handle #contact/UUID pattern — route to ContactDetailPage
  if (hash.startsWith("#contact/")) {
    return { hash: hash, label: "Contact Detail", component: ContactDetailPage };
  }
  return NAV_ITEMS.find(item => item.hash === hash) || NAV_ITEMS[0];
}

// ---------------------------------------------------------------------------
// Sidebar
// ---------------------------------------------------------------------------

function Sidebar({ activeHash, onNavigate }) {
  return (
    <aside className="sidebar">
      <div className="sidebar-header">
        <h1>Upstream<br />Command Center</h1>
        <div className="tagline">Content + Sales — 15 min/month</div>
      </div>
      <nav className="sidebar-nav">
        {NAV_SECTIONS.map(section => (
          <div key={section.label}>
            <div className="nav-section-label">{section.label}</div>
            {section.items.map(item => (
              <button
                key={item.hash}
                className={"nav-item" + (activeHash === item.hash ? " active" : "")}
                onClick={() => onNavigate(item.hash)}
              >
                {item.label}
              </button>
            ))}
          </div>
        ))}
      </nav>
      <div style={{ padding: "12px 16px", borderTop: "1px solid #2a3040", color: "#6B7A8D", fontSize: 11 }}>
        Press <kbd style={{ background: "#2a3040", borderRadius: 3, padding: "1px 5px" }}>/</kbd> to open command bar
      </div>
    </aside>
  );
}

// ---------------------------------------------------------------------------
// App root
// ---------------------------------------------------------------------------

function App() {
  const [activeHash, setActiveHash] = useState(resolveCurrentHash().hash);
  const [authed, setAuthed] = useState(false);
  const [checkingAuth, setCheckingAuth] = useState(true);
  const [commandBarOpen, setCommandBarOpen] = useState(false);

  useEffect(() => {
    function handleKeyDown(e) {
      if (e.key === "/" && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") {
        e.preventDefault();
        setCommandBarOpen(true);
      }
    }
    window.addEventListener("keydown", handleKeyDown);
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, []);

  // Check if already authenticated (cookie-based)
  useEffect(() => {
    apiFetch("/api/health")
      .then(r => {
        setAuthed(r.ok);
        setCheckingAuth(false);
      })
      .catch(() => {
        // No backend or auth required — assume local dev
        setAuthed(true);
        setCheckingAuth(false);
      });
  }, []);

  useEffect(() => {
    function handleHashChange() {
      const hash = window.location.hash || "#overview";
      // For #contact/UUID, preserve full hash; otherwise resolve from nav
      if (hash.startsWith("#contact/")) {
        setActiveHash(hash);
      } else {
        setActiveHash(resolveCurrentHash().hash);
      }
    }
    window.addEventListener("hashchange", handleHashChange);
    return () => window.removeEventListener("hashchange", handleHashChange);
  }, []);

  function navigate(hash) {
    window.location.hash = hash;
    setActiveHash(hash);
  }

  if (checkingAuth) return <div className="loading">Loading...</div>;
  if (!authed) return <LoginPage onLogin={() => setAuthed(true)} />;

  const resolvedPage = activeHash.startsWith("#contact/")
    ? { hash: activeHash, component: ContactDetailPage }
    : (NAV_ITEMS.find(item => item.hash === activeHash) || NAV_ITEMS[0]);
  const ActivePage = resolvedPage.component;

  return (
    <div className="layout">
      {commandBarOpen && <CommandBar onClose={() => setCommandBarOpen(false)} />}
      <Sidebar activeHash={activeHash} onNavigate={navigate} />
      <main className="main">
        <ActivePage />
      </main>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Mount
// ---------------------------------------------------------------------------

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
