const { useState, useEffect, useCallback, useRef } = React;

const api = {
  get: (p) => fetch(p).then((r) => r.json()),
  post: (p, body) =>
    fetch(p, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: body ? JSON.stringify(body) : null,
    }).then((r) => r.json()),
  patch: (p, body) =>
    fetch(p, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
    }).then((r) => r.json()),
  delete: (p) => fetch(p, { method: "DELETE" }).then((r) => r.json()),
};

function fmtDate(iso) {
  if (!iso) return "";
  const d = new Date(iso);
  return d.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
}
function titleCase(s) {
  return s ? s[0].toUpperCase() + s.slice(1).replace(/_/g, " ") : "";
}

const TABS = [
  { id: "ideas",     label: "Ideas"     },
  { id: "edit",      label: "Edit"      },
  { id: "review",    label: "Review"    },
  { id: "carousels", label: "Carousels" },
  { id: "journey",   label: "Journey"   },
  { id: "longvideo", label: "Long video"},
  { id: "calendar",  label: "Calendar"  },
];

function subtitle(tab) {
  switch (tab) {
    case "ideas":     return "Type a topic and angle — AI writes the script and hooks";
    case "edit":      return "Drop raw footage to edit, or run the pipeline on a filmed idea";
    case "review":    return "Edited videos waiting for your approval";
    case "carousels": return "Topic in → research, slides, caption, render, schedule";
    case "journey":   return "Reflective image+text posts about your business journey — X, Threads, LinkedIn";
    case "longvideo": return "Weekly long-form for YouTube + auto-announcement on X / Threads / LinkedIn";
    case "calendar":  return "Everything in the queue, by day. Filter by channel or format.";
    default: return "";
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// App shell
// ─────────────────────────────────────────────────────────────────────────────
function App() {
  const [tab, setTab] = useState(localStorage.getItem("ce-tab") || "ideas");
  const [reviewCount, setReviewCount] = useState(0);

  useEffect(() => { localStorage.setItem("ce-tab", tab); }, [tab]);

  const refreshCounts = useCallback(async () => {
    try {
      const data = await api.get("/api/review");
      setReviewCount(Array.isArray(data) ? data.length : 0);
    } catch {}
  }, []);

  useEffect(() => {
    refreshCounts();
    const t = setInterval(refreshCounts, 20000);
    return () => clearInterval(t);
  }, [refreshCounts]);

  return (
    <div className="app">
      <aside className="sidebar">
        <div className="brand"><span className="dot"></span>Content Engine</div>
        <nav className="nav">
          {TABS.map((t) => (
            <button
              key={t.id}
              className={tab === t.id ? "active" : ""}
              onClick={() => setTab(t.id)}
            >
              {t.label}
              {t.id === "review" && reviewCount > 0 && (
                <span className="num">{reviewCount}</span>
              )}
            </button>
          ))}
        </nav>
      </aside>
      <main className="main">
        <div className="topbar">
          <h1>{TABS.find((t) => t.id === tab)?.label}</h1>
          <span className="subtitle">{subtitle(tab)}</span>
        </div>
        <div className="content">
          {tab === "ideas"     && <Ideas onNavigate={setTab} />}
          {tab === "edit"      && <EditQueue refresh={refreshCounts} />}
          {tab === "review"    && <Review refresh={refreshCounts} />}
          {tab === "carousels" && <Carousels />}
          {tab === "journey"   && <Journey />}
          {tab === "longvideo" && <LongVideo />}
          {tab === "calendar"  && <Calendar />}
        </div>
      </main>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Ideas tab
// ─────────────────────────────────────────────────────────────────────────────
function Ideas({ onNavigate }) {
  const [ideas, setIdeas] = useState([]);
  const [topic, setTopic] = useState("");
  const [description, setDescription] = useState("");
  const [generating, setGenerating] = useState(false);
  const [expanded, setExpanded] = useState(null);
  const [teleprompter, setTeleprompter] = useState(null);

  const load = useCallback(async () => {
    try {
      const data = await api.get("/api/ideas");
      setIdeas(Array.isArray(data) ? data : []);
    } catch {}
  }, []);

  useEffect(() => {
    load();
    const t = setInterval(load, 8000);
    return () => clearInterval(t);
  }, [load]);

  const generate = async () => {
    if (!topic.trim() || !description.trim()) return;
    setGenerating(true);
    try {
      const idea = await api.post("/api/ideas", { topic: topic.trim(), description: description.trim() });
      if (idea?.id) {
        setTopic("");
        setDescription("");
        setIdeas((prev) => [idea, ...prev]);
        setExpanded(idea.id);
      }
    } catch (e) {
      alert("Generation failed — check the server logs.");
    } finally {
      setGenerating(false);
    }
  };

  const deleteIdea = async (id) => {
    if (!confirm("Delete this idea?")) return;
    await api.delete(`/api/ideas/${id}`);
    setIdeas((prev) => prev.filter((i) => i.id !== id));
    if (expanded === id) setExpanded(null);
  };

  return (
    <>
      {/* Generation form */}
      <div className="idea-form">
        <input
          className="idea-topic-input"
          type="text"
          placeholder="What do you want to talk about?"
          value={topic}
          onChange={(e) => setTopic(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && !generating && description.trim() && generate()}
          disabled={generating}
        />
        <textarea
          className="idea-desc-input"
          placeholder={"Your angle, key point, or any specifics (2–3 sentences).\n\nExample: Most people type a one-liner into ChatGPT and get back generic rubbish. The trick is adding context — who you are, what your business does, what you actually need. I want to show how framing the question completely changes the answer."}
          value={description}
          onChange={(e) => setDescription(e.target.value)}
          disabled={generating}
          rows={5}
        />
        <button
          className="btn primary idea-gen-btn"
          disabled={generating || !topic.trim() || !description.trim()}
          onClick={generate}
        >
          {generating ? <><span className="loader"></span> Writing script...</> : "Generate Script →"}
        </button>
      </div>

      {/* Ideas list */}
      {ideas.length > 0 && (
        <div className="idea-list">
          {ideas.map((idea, i) => (
            <IdeaRow
              key={idea.id}
              idx={i + 1}
              idea={idea}
              expanded={expanded === idea.id}
              onToggle={() => setExpanded(expanded === idea.id ? null : idea.id)}
              onTele={() => setTeleprompter(idea)}
              onDelete={() => deleteIdea(idea.id)}
              onUpdate={(updated) => setIdeas((prev) => prev.map((x) => x.id === updated.id ? updated : x))}
              onNavigate={onNavigate}
            />
          ))}
        </div>
      )}

      {ideas.length === 0 && !generating && (
        <div className="empty" style={{ marginTop: 32 }}>
          <h3>No ideas yet</h3>
          <p>Type a topic above and hit Generate — your script will appear here in about 10 seconds.</p>
        </div>
      )}

      {teleprompter && (
        <Teleprompter idea={teleprompter} onClose={() => setTeleprompter(null)} />
      )}
    </>
  );
}

function IdeaRow({ idx, idea, expanded, onToggle, onTele, onDelete, onUpdate, onNavigate }) {
  const hooks = idea.hook_variants || [];
  const activeIdx = idea.active_hook_index || 0;
  const activeHook = hooks[activeIdx] || idea.script?.split("\n")[0] || "(no hook)";
  const [dragOver, setDragOver] = useState(false);
  const [uploading, setUploading] = useState(false);
  const [running, setRunning] = useState(false);
  const fileInputRef = useRef(null);

  const pickHook = async (i) => {
    const updated = await api.patch(`/api/ideas/${idea.id}`, { active_hook_index: i });
    if (updated?.id) onUpdate(updated);
  };

  const saveScript = async (newScript) => {
    const updated = await api.patch(`/api/ideas/${idea.id}`, { script: newScript });
    if (updated?.id) onUpdate(updated);
  };

  const uploadFootage = async (fileList) => {
    const files = Array.from(fileList).filter(
      (f) => f.type.startsWith("video/") || /\.(mp4|mov|avi|mkv|webm)$/i.test(f.name)
    );
    if (!files.length) return;
    setUploading(true);
    try {
      const fd = new FormData();
      files.forEach((f) => fd.append("files", f));
      const updated = await fetch(`/api/ideas/${idea.id}/footage`, {
        method: "POST",
        body: fd,
      }).then((r) => r.json());
      if (updated?.id) onUpdate(updated);
    } finally {
      setUploading(false);
    }
  };

  const runPipeline = async () => {
    setRunning(true);
    try {
      await api.post(`/api/ideas/${idea.id}/run`, {});
      const updated = await api.get(`/api/ideas/${idea.id}`);
      if (updated?.id) onUpdate(updated);
    } finally {
      setRunning(false);
    }
  };

  const statusColor = {
    draft: "var(--text-3)",
    filming: "var(--blue)",
    editing: "var(--amber)",
    review: "var(--accent)",
    error: "var(--red)",
    posted: "var(--green)",
  }[idea.status] || "var(--text-3)";

  const footagePaths = idea.footage_paths || [];

  return (
    <div className="idea-row">
      <div className="idea-head" onClick={onToggle}>
        <span className="idx mono">#{String(idx).padStart(3, "0")}</span>
        <span className="idea-topic">{idea.topic}</span>
        <span className="chip muted">{fmtDate(idea.created_at)}</span>
        <span className="chip" style={{ background: "transparent", color: statusColor, border: `1px solid ${statusColor}55` }}>
          {titleCase(idea.status)}
        </span>
        <button className="btn ghost" style={{ color: "var(--red)", padding: "2px 8px", fontSize: 11 }}
          onClick={(e) => { e.stopPropagation(); onDelete(); }}>
          ×
        </button>
      </div>

      {expanded && (
        <div className="idea-body">
          {/* Hook variants */}
          {hooks.length > 0 && (
            <>
              <h4>Hook variants — click to set active</h4>
              <div className="hooks">
                {hooks.map((h, i) => (
                  <div
                    key={i}
                    className={"hook-variant " + (i === activeIdx ? "active" : "")}
                    onClick={() => pickHook(i)}
                  >
                    <span className="mark mono">{String(i + 1).padStart(2, "0")}</span>
                    <span>{h}</span>
                  </div>
                ))}
              </div>
            </>
          )}

          {/* Full script */}
          <h4>Script</h4>
          <ScriptEditor
            initialValue={idea.script || ""}
            onSave={saveScript}
          />

          <div className="row-actions" style={{ marginTop: 8 }}>
            <button className="btn" onClick={onTele}>📖 Teleprompter</button>
          </div>

          {/* Footage drop */}
          <h4 style={{ marginTop: 18 }}>Footage</h4>
          <label
            className={"drop " + (dragOver ? "over" : "") + (uploading ? " loading" : "")}
            onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
            onDragLeave={() => setDragOver(false)}
            onDrop={(e) => { e.preventDefault(); setDragOver(false); uploadFootage(e.dataTransfer.files); }}
            onClick={() => !uploading && fileInputRef.current?.click()}
          >
            <input
              ref={fileInputRef}
              type="file" multiple accept="video/*,.mp4,.mov,.avi,.mkv,.webm"
              style={{ display: "none" }}
              onChange={(e) => uploadFootage(e.target.files)}
            />
            {uploading
              ? <><span className="loader"></span> Uploading...</>
              : <><div>Drop raw footage here, or click to browse</div><div className="drop-note">Single clip or multiple clips — stitched in filename order</div></>
            }
          </label>

          {footagePaths.length > 0 && (
            <div className="footage-chips">
              {footagePaths.map((p, i) => {
                const filename = p.split("/").pop();
                return (
                  <span key={i} className="footage-chip">
                    {filename}
                    <button
                      title="Remove file"
                      style={{ marginLeft: 6, background: "none", border: "none", cursor: "pointer", color: "var(--red)", fontWeight: 700, fontSize: 13, lineHeight: 1, padding: "0 2px" }}
                      onClick={async (e) => {
                        e.stopPropagation();
                        if (!confirm(`Remove ${filename}?`)) return;
                        const updated = await fetch(`/api/ideas/${idea.id}/footage/${encodeURIComponent(filename)}`, { method: "DELETE" }).then(r => r.json());
                        if (updated?.id) onUpdate(updated);
                      }}
                    >×</button>
                  </span>
                );
              })}
            </div>
          )}

          <div className="row-actions" style={{ marginTop: 14 }}>
            {idea.status === "review" ? (
              <button className="btn primary" onClick={() => onNavigate("review")}>
                View in Review →
              </button>
            ) : (
              <button
                className="btn primary"
                disabled={running || footagePaths.length === 0 || ["editing", "review", "posted"].includes(idea.status)}
                onClick={runPipeline}
              >
                {running || idea.status === "editing" ? <><span className="loader"></span> Processing...</> : "Run pipeline →"}
              </button>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

function ScriptEditor({ initialValue, onSave }) {
  const [value, setValue] = useState(initialValue);
  const [saved, setSaved] = useState(false);

  const save = async () => {
    await onSave(value);
    setSaved(true);
    setTimeout(() => setSaved(false), 1500);
  };

  return (
    <>
      <textarea
        className="script-text-editor"
        value={value}
        onChange={(e) => setValue(e.target.value)}
        rows={8}
      />
      <button className="btn" style={{ marginTop: 6 }} onClick={save}>
        {saved ? "✓ Saved" : "Save edits"}
      </button>
    </>
  );
}

function Teleprompter({ idea, onClose }) {
  const hooks = idea.hook_variants || [];
  const activeIdx = idea.active_hook_index || 0;
  const activeHook = hooks[activeIdx] || "";
  const script = idea.script || "";

  // Build full script: if active hook differs from script opening, prepend it
  const body = (() => {
    if (!activeHook) return script;
    const firstLine = script.split("\n")[0] || "";
    if (firstLine.trim() === activeHook.trim()) return script;
    // Replace the first paragraph with the active hook
    const rest = script.split("\n\n").slice(1).join("\n\n");
    return rest ? activeHook + "\n\n" + rest : activeHook;
  })();

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

  return (
    <div className="teleprompter">
      <button className="close" onClick={onClose}>Close (Esc)</button>
      <div className="tele-topic">{idea.topic}</div>
      <div className="text">{body}</div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Edit tab — standalone drop zone
// ─────────────────────────────────────────────────────────────────────────────
function EditQueue({ refresh }) {
  const [dragOver, setDragOver] = useState(false);
  const [uploading, setUploading] = useState(false);
  const [jobs, setJobs] = useState([]);
  const fileInputRef = useRef(null);

  const loadJobs = useCallback(async () => {
    try {
      const data = await api.get("/api/pipeline/jobs");
      setJobs(Array.isArray(data) ? data : []);
    } catch {}
  }, []);

  useEffect(() => {
    loadJobs();
    const t = setInterval(loadJobs, 4000);
    return () => clearInterval(t);
  }, [loadJobs]);

  const uploadFiles = async (fileList) => {
    const files = Array.from(fileList).filter(
      (f) => f.type.startsWith("video/") || /\.(mp4|mov|avi|mkv|webm)$/i.test(f.name)
    );
    if (!files.length) return;
    setUploading(true);
    try {
      const fd = new FormData();
      files.forEach((f) => fd.append("files", f));
      await fetch("/api/pipeline/upload", { method: "POST", body: fd });
      await loadJobs();
      refresh();
    } finally {
      setUploading(false);
    }
  };

  return (
    <>
      <div
        className={"upload-zone" + (dragOver ? " over" : "") + (uploading ? " loading" : "")}
        onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
        onDragLeave={() => setDragOver(false)}
        onDrop={(e) => { e.preventDefault(); setDragOver(false); uploadFiles(e.dataTransfer.files); }}
        onClick={() => !uploading && fileInputRef.current?.click()}
      >
        <input
          ref={fileInputRef}
          type="file" multiple accept="video/*,.mp4,.mov,.avi,.mkv,.webm"
          style={{ display: "none" }}
          onChange={(e) => uploadFiles(e.target.files)}
        />
        {uploading ? (
          <><span className="loader"></span><div className="upload-title">Uploading and starting pipeline...</div></>
        ) : (
          <>
            <div className="upload-icon">↓</div>
            <div className="upload-title">Drop raw footage here</div>
            <div className="upload-sub">Single clip or multiple clips stitched together in filename order · click to browse</div>
          </>
        )}
      </div>

      {jobs.length > 0 && (
        <>
          <div className="section-label">Pipeline jobs</div>
          <div className="job-list">
            {jobs.map((j) => (
              <div key={j.id} className={"job-row " + j.status}>
                <span className="mono" style={{ color: "var(--text-3)", fontSize: 11 }}>#{j.id}</span>
                <span style={{ flex: 1 }}>{j.label}</span>
                {j.status === "processing" && <><span className="loader"></span><span style={{ color: "var(--amber)" }}>Processing...</span></>}
                {j.status === "done" && <span style={{ color: "var(--accent)" }}>Done — check Review tab →</span>}
                {j.status === "error" && <span style={{ color: "var(--red)" }} title={j.error || ""}>Failed ×</span>}
              </div>
            ))}
          </div>
        </>
      )}

      {jobs.length === 0 && !uploading && (
        <div className="empty" style={{ marginTop: 24 }}>
          <h3>Nothing queued</h3>
          <p>Drop a raw video above to edit without linking it to an idea, or use the pipeline button inside an idea.</p>
        </div>
      )}
    </>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Review tab
// ─────────────────────────────────────────────────────────────────────────────
function Review({ refresh }) {
  const [reviews, setReviews] = useState([]);
  const [loading, setLoading] = useState(true);

  const load = useCallback(async () => {
    setLoading(true);
    try {
      const data = await api.get("/api/review");
      setReviews(Array.isArray(data) ? data : []);
    } finally {
      setLoading(false);
    }
  }, []);

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

  if (loading) return <div className="empty"><span className="loader"></span> Loading...</div>;
  if (reviews.length === 0) {
    return (
      <div className="empty">
        <h3>Nothing to review</h3>
        <p>Run the pipeline on an idea or drop footage in the Edit tab.</p>
      </div>
    );
  }

  return (
    <div className="review-list">
      {reviews.map((r) => (
        <ReviewCard key={r.name} review={r} refresh={() => { load(); refresh(); }} />
      ))}
    </div>
  );
}

const ALL_PLATFORMS = ["instagram", "facebook", "tiktok", "youtube"];

function ReviewCard({ review, refresh }) {
  const [caption, setCaption] = useState(review.caption || "");
  const [title, setTitle] = useState(review.title || "");
  const [platforms, setPlatforms] = useState(review.platforms || []);
  const [publishing, setPublishing] = useState(false);
  const [publishMsg, setPublishMsg] = useState(null); // { ok: bool, text: string }
  const [saved, setSaved] = useState(false);

  const togglePlatform = (p) =>
    setPlatforms((curr) => curr.includes(p) ? curr.filter((x) => x !== p) : [...curr, p]);

  const save = async () => {
    await api.patch(`/api/review/${review.name}`, { caption, title, platforms });
    setSaved(true);
    setTimeout(() => setSaved(false), 1200);
  };

  const publish = async () => {
    await save();
    if (!confirm(`Publish to ${platforms.join(", ")}?`)) return;
    setPublishing(true);
    setPublishMsg(null);
    try {
      const resp = await api.post("/api/review/publish", { folder: review.name });
      if (resp.detail) {
        setPublishMsg({ ok: false, text: resp.detail });
        return;
      }
      // Poll for background task result (max 3 min)
      for (let i = 0; i < 60; i++) {
        await new Promise((r) => setTimeout(r, 3000));
        const status = await api.get(`/api/review/publish-status/${review.name}`);
        if (status.state === "done") {
          setPublishMsg({ ok: true, text: `Posted to ${platforms.join(", ")} ✓` });
          refresh();
          return;
        }
        if (status.state === "error") {
          setPublishMsg({ ok: false, text: status.error || "Publish failed" });
          return;
        }
        if (status.state === "partial") {
          const failed = (status.results || []).filter((r) => !r.success)
            .map((r) => `${r.platform}: ${r.error}`).join("; ");
          setPublishMsg({ ok: false, text: `Partial failure — ${failed}` });
          refresh();
          return;
        }
      }
      setPublishMsg({ ok: false, text: "Timed out waiting for publish — check server logs" });
    } catch (e) {
      setPublishMsg({ ok: false, text: e.message || "Request failed — is the server running?" });
    } finally {
      setPublishing(false);
    }
  };

  return (
    <div className="review-card">
      <video controls src={review.video_url} poster={review.thumb_url || ""} />
      <div>
        <div className="meta">{review.name} · {review.duration_sec?.toFixed(1)}s</div>
        <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Title" />
        <h4 style={{ marginTop: 10 }}>Caption</h4>
        <textarea value={caption} onChange={(e) => setCaption(e.target.value)} />
        <div className="platforms">
          {ALL_PLATFORMS.map((p) => (
            <span
              key={p}
              className={"platform-toggle " + (platforms.includes(p) ? "on" : "")}
              onClick={() => togglePlatform(p)}
            >
              {p}{p === "youtube" && review.duration_sec > 60 ? " (long)" : ""}
            </span>
          ))}
        </div>
      </div>
      <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
        <button className="btn" onClick={save}>{saved ? "✓ Saved" : "Save"}</button>
        <button className="btn primary" disabled={publishing || platforms.length === 0} onClick={publish}>
          {publishing ? <><span className="loader"></span> Publishing...</> : "Publish →"}
        </button>
        {publishMsg && (
          <div style={{
            fontSize: 12,
            padding: "6px 10px",
            borderRadius: 6,
            background: publishMsg.ok ? "rgba(0,229,196,0.12)" : "rgba(255,60,60,0.12)",
            color: publishMsg.ok ? "var(--accent)" : "var(--red)",
            lineHeight: 1.4,
          }}>
            {publishMsg.text}
          </div>
        )}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Schedule tab
// ─────────────────────────────────────────────────────────────────────────────
function Schedule() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    api.get("/api/schedule")
      .then((data) => {
        if (Array.isArray(data)) setPosts(data);
        else setError(data?.detail || "Failed to load");
        setLoading(false);
      })
      .catch((e) => { setError(e.message); setLoading(false); });
  }, []);

  if (loading) return <div className="empty"><span className="loader"></span> Loading Blotato queue...</div>;
  if (error) return <div className="empty"><h3>Couldn't reach Blotato</h3><p>{error}</p></div>;
  if (posts.length === 0) {
    return (
      <div className="empty">
        <h3>Queue empty</h3>
        <p>Publish something in the Review tab to see it queued here.</p>
      </div>
    );
  }

  return (
    <div className="script-list">
      {posts.map((p, i) => (
        <div key={p.id || i} className="script-row" style={{ padding: 14 }}>
          <div style={{ display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 12, alignItems: "center" }}>
            <span className="mono" style={{ color: "var(--text-3)", fontSize: 11 }}>
              {p.scheduledTime ? new Date(p.scheduledTime).toLocaleString() : "—"}
            </span>
            <span style={{ fontSize: 13 }}>{(p.content?.text || p.caption || "").slice(0, 100)}</span>
            <span className="chip muted">{p.platform || p.target?.targetType || "?"}</span>
          </div>
        </div>
      ))}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Carousels tab
// ─────────────────────────────────────────────────────────────────────────────
const CAROUSEL_TYPES = [
  { value: "list",            label: "List (e.g. 5 tools)" },
  { value: "step_by_step",    label: "Step by step" },
  { value: "before_after",    label: "Before / after" },
  { value: "myth_truth",      label: "Myth / truth" },
  { value: "cheat_sheet",     label: "Cheat sheet" },
  { value: "single_insight",  label: "Single insight" },
];

const CAROUSEL_STATUS_COLORS = {
  draft:                      "#9ca3af",
  ingesting:                  "#3b82f6",
  digesting:                  "#3b82f6",
  researching:                "#3b82f6",
  generating_text:            "#3b82f6",
  awaiting_render_approval:   "#f59e0b",
  rendering:                  "#3b82f6",
  rendered:                   "#10b981",
  uploading:                  "#3b82f6",
  uploaded:                   "#10b981",
  scheduled:                  "#a855f7",
  posted:                     "#059669",
  error:                      "#ef4444",
};

// Google Drive download URLs don't render in <img>. Derive a thumbnail URL
// that does. The download URL stays on the row for Blotato + click-through.
function driveThumbUrl(url) {
  if (!url) return url;
  const m = url.match(/[?&]id=([^&]+)/);
  return m ? `https://drive.google.com/thumbnail?id=${m[1]}&sz=w800` : url;
}

function Carousels() {
  const [list, setList] = useState([]);
  const [showCreate, setShowCreate] = useState(false);
  const [mode, setMode] = useState("topic"); // "topic" | "import"
  const [topic, setTopic] = useState("");
  const [angle, setAngle] = useState("");
  const [carouselType, setCarouselType] = useState("list");
  const [slideCount, setSlideCount] = useState(8);
  const [sourceUrl, setSourceUrl] = useState("");
  const [creating, setCreating] = useState(false);
  const [expanded, setExpanded] = useState(null);

  const load = useCallback(async () => {
    try {
      const data = await api.get("/api/carousels");
      setList(Array.isArray(data) ? data : []);
    } catch {}
  }, []);

  useEffect(() => {
    load();
    const t = setInterval(load, 8000);
    return () => clearInterval(t);
  }, [load]);

  const create = async () => {
    if (mode === "topic" && !topic.trim()) return;
    if (mode === "import" && !sourceUrl.trim()) return;
    setCreating(true);
    try {
      const payload = mode === "import"
        ? { topic: topic.trim() || `Imported from IG`, source_url: sourceUrl.trim(), carousel_type: carouselType }
        : { topic: topic.trim(), angle: angle.trim(), carousel_type: carouselType, slide_count: slideCount };
      const idea = await api.post("/api/carousels", payload);
      if (idea?.id) {
        setTopic("");
        setAngle("");
        setSourceUrl("");
        setShowCreate(false);
        setExpanded(idea.id);
        setList((prev) => [idea, ...prev]);
      }
    } catch (e) {
      alert("Failed to create carousel — check the server logs.");
    } finally {
      setCreating(false);
    }
  };

  const remove = async (id) => {
    if (!confirm("Delete this carousel?")) return;
    await api.delete(`/api/carousels/${id}`);
    setList((prev) => prev.filter((c) => c.id !== id));
    if (expanded === id) setExpanded(null);
  };

  const update = (updated) =>
    setList((prev) => prev.map((c) => (c.id === updated.id ? updated : c)));

  return (
    <>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
        <div className="subtitle" style={{ fontSize: 14 }}>
          {list.length} carousel{list.length === 1 ? "" : "s"}
        </div>
        <button className="btn primary" onClick={() => setShowCreate(true)}>
          + New carousel
        </button>
      </div>

      {showCreate && (
        <div className="idea-form" style={{ marginBottom: 24 }}>
          <div style={{ display: "flex", gap: 4, marginBottom: 12 }}>
            <button
              className={mode === "topic" ? "btn primary" : "btn"}
              onClick={() => setMode("topic")}
              disabled={creating}
              style={{ flex: 1 }}
            >
              From topic
            </button>
            <button
              className={mode === "import" ? "btn primary" : "btn"}
              onClick={() => setMode("import")}
              disabled={creating}
              style={{ flex: 1 }}
            >
              Import from Instagram
            </button>
          </div>

          {mode === "topic" && (
            <>
              <input
                className="idea-topic-input"
                type="text"
                placeholder="Carousel topic (e.g. 5 AI tools every business needs)"
                value={topic}
                onChange={(e) => setTopic(e.target.value)}
                disabled={creating}
                autoFocus
              />
              <textarea
                className="idea-desc-input"
                placeholder={"Your angle, 2–3 sentences. Example: The five I keep coming back to in 2026, in the order I'd start. No hype, just the ones that pull weight."}
                value={angle}
                onChange={(e) => setAngle(e.target.value)}
                disabled={creating}
                rows={3}
              />
              <div style={{ display: "flex", gap: 12, marginBottom: 12 }}>
                <select
                  value={carouselType}
                  onChange={(e) => setCarouselType(e.target.value)}
                  disabled={creating}
                  style={{ padding: "8px 12px", borderRadius: 8, border: "1px solid #d4d4d4", flex: 1, color: "#0a0a0a", background: "#ffffff" }}
                >
                  {CAROUSEL_TYPES.map((t) => (
                    <option key={t.value} value={t.value} style={{ color: "#0a0a0a", background: "#ffffff" }}>{t.label}</option>
                  ))}
                </select>
                <select
                  value={slideCount}
                  onChange={(e) => setSlideCount(Number(e.target.value))}
                  disabled={creating}
                  style={{ padding: "8px 12px", borderRadius: 8, border: "1px solid #d4d4d4", color: "#0a0a0a", background: "#ffffff" }}
                >
                  {[5, 6, 7, 8, 9, 10].map((n) => (
                    <option key={n} value={n} style={{ color: "#0a0a0a", background: "#ffffff" }}>{n} slides</option>
                  ))}
                </select>
              </div>
            </>
          )}

          {mode === "import" && (
            <>
              <input
                className="idea-topic-input"
                type="text"
                placeholder="https://www.instagram.com/p/..."
                value={sourceUrl}
                onChange={(e) => setSourceUrl(e.target.value)}
                disabled={creating}
                autoFocus
              />
              <div className="subtitle" style={{ fontSize: 13, marginBottom: 12 }}>
                Paste a public IG post link. We'll scrape the slides, OCR the text, and rebuild it in our brand template. Slide count is inferred from the source post.
              </div>
              <select
                value={carouselType}
                onChange={(e) => setCarouselType(e.target.value)}
                disabled={creating}
                style={{ padding: "8px 12px", borderRadius: 8, border: "1px solid #d4d4d4", marginBottom: 12, width: "100%", color: "#0a0a0a", background: "#ffffff" }}
              >
                {CAROUSEL_TYPES.map((t) => (
                  <option key={t.value} value={t.value} style={{ color: "#0a0a0a", background: "#ffffff" }}>{t.label}</option>
                ))}
              </select>
            </>
          )}

          <div style={{ display: "flex", gap: 8 }}>
            <button
              className="btn primary idea-gen-btn"
              disabled={creating || (mode === "topic" ? !topic.trim() : !sourceUrl.trim())}
              onClick={create}
            >
              {creating ? <><span className="loader"></span> Submitting…</> : (mode === "import" ? "Import + plan slides →" : "Start pipeline →")}
            </button>
            <button className="btn" onClick={() => setShowCreate(false)} disabled={creating}>
              Cancel
            </button>
          </div>
        </div>
      )}

      {list.length === 0 && !showCreate && (
        <div className="empty" style={{ marginTop: 32 }}>
          <h3>No carousels yet</h3>
          <p>Hit “New carousel” above to start one. Topic + angle in, eight finished slides + caption out.</p>
        </div>
      )}

      <div className="idea-list">
        {list.map((c) => (
          <CarouselRow
            key={c.id}
            carousel={c}
            expanded={expanded === c.id}
            onToggle={() => setExpanded(expanded === c.id ? null : c.id)}
            onUpdate={update}
            onDelete={() => remove(c.id)}
          />
        ))}
      </div>
    </>
  );
}

function CarouselRow({ carousel, expanded, onUpdate, onToggle, onDelete }) {
  const status = carousel.status || "draft";
  const color = CAROUSEL_STATUS_COLORS[status] || "#9ca3af";
  const slides = carousel.slide_drive_urls || [];
  const slideCount = carousel.slide_count || 8;

  return (
    <div className="idea-row" style={{ borderLeft: `4px solid ${color}` }}>
      <div
        style={{ cursor: "pointer", padding: 12, display: "flex", justifyContent: "space-between", alignItems: "center" }}
        onClick={onToggle}
      >
        <div>
          <div style={{ fontWeight: 600 }}>{carousel.topic}</div>
          <div className="subtitle" style={{ fontSize: 12, marginTop: 4 }}>
            {titleCase(carousel.carousel_type)} · {slideCount} slides · created {fmtDate(carousel.created_at)}
          </div>
        </div>
        <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
          <span style={{
            fontSize: 11, padding: "3px 10px", borderRadius: 12,
            background: color, color: "white", fontWeight: 600, textTransform: "uppercase",
          }}>
            {titleCase(status)}
          </span>
          <span style={{ color: "#999" }}>{expanded ? "▾" : "▸"}</span>
        </div>
      </div>

      {expanded && (
        <div style={{ padding: 16, borderTop: "1px solid #eee" }}>
          {carousel.error_message && (
            <div style={{ background: "#fef2f2", padding: 12, borderRadius: 8, marginBottom: 16, color: "#991b1b", fontSize: 13 }}>
              <strong>Error:</strong> {carousel.error_message}
            </div>
          )}

          {slides.length > 0 && (
            <>
              <div className="subtitle" style={{ fontSize: 13, marginBottom: 8 }}>Rendered slides</div>
              <div style={{ display: "flex", overflowX: "auto", gap: 8, marginBottom: 16, paddingBottom: 8 }}>
                {slides.map((url, i) => (
                  <a key={i} href={url} target="_blank" rel="noreferrer">
                    <img
                      src={driveThumbUrl(url)}
                      alt={`slide ${i + 1}`}
                      style={{ width: 180, height: 180, objectFit: "cover", borderRadius: 8, border: "1px solid #e5e5e5", background: "#fff" }}
                    />
                  </a>
                ))}
              </div>
            </>
          )}

          <CarouselRenderApproval carousel={carousel} onUpdate={onUpdate} />

          <CarouselEditor carousel={carousel} onUpdate={onUpdate} />

          <div style={{ marginTop: 16, display: "flex", gap: 8, flexWrap: "wrap" }}>
            <CarouselSchedule carousel={carousel} onUpdate={onUpdate} />
            <button
              className="btn"
              onClick={async () => {
                const r = await api.post(`/api/carousels/${carousel.id}/render`);
                if (r?.id) onUpdate({ ...carousel, status: "researching" });
              }}
            >
              Re-render
            </button>
            <button className="btn" style={{ color: "#991b1b" }} onClick={onDelete}>
              Delete
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

function CarouselRenderApproval({ carousel, onUpdate }) {
  const [approving, setApproving] = useState(false);
  if (carousel.status !== "awaiting_render_approval") return null;
  const sj = carousel.slide_json || {};
  const introHasContent = sj.intro && ((sj.intro.headline_lines || []).length > 0 || (sj.intro.body_lines || []).length > 0);
  const allSlides = [
    sj.cover && { kind: "Cover", title: (sj.cover.headline_lines || []).join(" "), design_intent: sj.cover.design_intent, decorative_icon: sj.cover.decorative_icon },
    introHasContent && { kind: "Intro", title: (sj.intro.headline_lines || []).join(" "), design_intent: sj.intro.design_intent, decorative_icon: sj.intro.decorative_icon },
    ...(sj.body_slides || []).map((b, i) => ({ kind: `Slide ${b.title || i + 1}`, title: b.title || `(unnamed slide ${i + 1})`, design_intent: b.design_intent, decorative_icon: b.decorative_icon })),
    sj.cta && { kind: "CTA", title: `${sj.cta.label_above || "FOLLOW FOR MORE"} → ${sj.cta.handle || "@owencostello.ai"}`, design_intent: sj.cta.design_intent, decorative_icon: sj.cta.decorative_icon },
  ].filter(Boolean);

  const approve = async () => {
    setApproving(true);
    try {
      const res = await api.post(`/api/carousels/${carousel.id}/render-approve`);
      if (res?.id) onUpdate({ ...carousel, status: "rendering" });
    } catch (e) {
      alert("Failed to start render — check the server logs.");
    } finally {
      setApproving(false);
    }
  };

  const regenText = async () => {
    if (!confirm("Re-generate all slide text and caption? Higgsfield credits aren't touched.")) return;
    setApproving(true);
    try {
      const res = await api.post(`/api/carousels/${carousel.id}/render`);
      if (res?.id) onUpdate({ ...carousel, status: "researching" });
    } finally {
      setApproving(false);
    }
  };

  return (
    <div style={{ background: "#fffbeb", border: "1px solid #fde68a", borderRadius: 10, padding: 16, marginBottom: 16 }}>
      <div style={{ fontWeight: 600, marginBottom: 4, color: "#0a0a0a" }}>Review the slide plan before rendering</div>
      <div className="subtitle" style={{ fontSize: 13, marginBottom: 12 }}>
        Each slide below is what we'll send to Higgsfield. No image credits used yet.
      </div>
      <ol style={{ paddingLeft: 20, margin: 0, color: "#0a0a0a" }}>
        {allSlides.map((s, i) => (
          <li key={i} style={{ marginBottom: 10, fontSize: 14, lineHeight: 1.45 }}>
            <strong>{s.kind} — {s.title}</strong>
            {s.design_intent && <div style={{ color: "#404040", marginTop: 2 }}>{s.design_intent}</div>}
            {s.decorative_icon && <div style={{ color: "#737373", marginTop: 2, fontSize: 13 }}>Icon: {s.decorative_icon}</div>}
          </li>
        ))}
      </ol>
      <div style={{ marginTop: 16, display: "flex", gap: 8 }}>
        <button className="btn primary" disabled={approving} onClick={approve}>
          {approving ? "Starting…" : "Approve and render →"}
        </button>
        <button className="btn" disabled={approving} onClick={regenText}>
          Re-generate text
        </button>
      </div>
    </div>
  );
}

function CarouselEditor({ carousel, onUpdate }) {
  const [caption, setCaption] = useState(carousel.caption || "");
  const [saving, setSaving] = useState(false);

  useEffect(() => { setCaption(carousel.caption || ""); }, [carousel.caption]);

  const save = async () => {
    setSaving(true);
    try {
      const updated = await api.patch(`/api/carousels/${carousel.id}`, { caption });
      if (updated?.id) onUpdate(updated);
    } finally {
      setSaving(false);
    }
  };

  return (
    <div>
      <div className="subtitle" style={{ fontSize: 13, marginBottom: 4 }}>Caption</div>
      <textarea
        value={caption}
        onChange={(e) => setCaption(e.target.value)}
        rows={14}
        style={{
          width: "100%", padding: 12, borderRadius: 8,
          border: "1px solid #d4d4d4", fontSize: 14, lineHeight: 1.55,
          fontFamily: "inherit",
          color: "#0a0a0a", background: "#ffffff",
          resize: "vertical",
        }}
      />
      <button className="btn" style={{ marginTop: 8 }} disabled={saving} onClick={save}>
        {saving ? "Saving…" : "Save caption"}
      </button>
    </div>
  );
}

function CarouselSchedule({ carousel, onUpdate }) {
  const [alsoLinkedin, setAlsoLinkedin] = useState(false);
  const [posting, setPosting] = useState(false);

  const ready = (carousel.slide_drive_urls || []).length >= 2 && (carousel.caption || "").trim();
  const isScheduled = carousel.status === "scheduled" || carousel.status === "posted";

  const send = async () => {
    setPosting(true);
    try {
      const res = await api.post(`/api/carousels/${carousel.id}/schedule`, {
        also_linkedin: alsoLinkedin,
      });
      if (res?.id) onUpdate({ ...carousel, status: res.status, scheduled_at: res.scheduled_at });
    } catch (e) {
      alert("Schedule failed — check the server logs.");
    } finally {
      setPosting(false);
    }
  };

  if (!ready) {
    return <span className="subtitle" style={{ fontSize: 13, alignSelf: "center" }}>
      Pipeline still running — schedule unlocks once slides + caption are ready.
    </span>;
  }

  if (isScheduled) {
    return <span className="subtitle" style={{ fontSize: 13, alignSelf: "center", color: "#a855f7" }}>
      ✓ Scheduled for {fmtDate(carousel.scheduled_at)} {fmtTime(carousel.scheduled_at)}
    </span>;
  }

  return (
    <div style={{ display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
      <button className="btn primary" disabled={posting} onClick={send}>
        {posting ? "Scheduling…" : "Schedule (next 12:30 London)"}
      </button>
      <label style={{ fontSize: 13, display: "flex", alignItems: "center", gap: 6, color: "#0a0a0a" }}>
        <input
          type="checkbox"
          checked={alsoLinkedin}
          onChange={(e) => setAlsoLinkedin(e.target.checked)}
          disabled={posting}
        />
        Also post to LinkedIn
        <span className="subtitle" style={{ fontSize: 12, color: "#737373" }}>
          (next Wed 09:00)
        </span>
      </label>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Calendar tab — month grid of scheduled_posts
// ─────────────────────────────────────────────────────────────────────────────
const TYPE_COLORS = {
  short_video:               { bg: "#3b82f6", label: "Video"        },
  carousel:                  { bg: "#10b981", label: "Carousel"     },
  tweet_thread:              { bg: "#06b6d4", label: "Thread"       },
  image_text:                { bg: "#f59e0b", label: "Image+Text"   },
  long_video:                { bg: "#a855f7", label: "Long Video"   },
  long_video_announcement:   { bg: "#ec4899", label: "LV Announce"  },
  linkedin_carousel:         { bg: "#6366f1", label: "LinkedIn Car" },
};

const STATUS_COLORS_CAL = {
  queued:     "#3b82f6",
  publishing: "#a16207",
  posted:     "#059669",
  failed:     "#dc2626",
  cancelled:  "#737373",
};

const ALL_CHANNELS  = ["instagram", "facebook", "tiktok", "youtube", "twitter", "threads", "linkedin"];
const ALL_TYPES     = Object.keys(TYPE_COLORS);

function fmtTime(iso) {
  if (!iso) return "";
  return new Date(iso).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
}

function Calendar() {
  const today = new Date();
  const [month, setMonth] = useState({ y: today.getFullYear(), m: today.getMonth() + 1 });
  const [posts, setPosts]   = useState([]);
  const [filterTypes, setFilterTypes] = useState(new Set(ALL_TYPES));
  const [filterChannels, setFilterChannels] = useState(new Set(ALL_CHANNELS));
  const [openPost, setOpenPost] = useState(null);
  const [loading, setLoading] = useState(false);

  const load = useCallback(async () => {
    setLoading(true);
    try {
      const data = await api.get(`/api/scheduled-posts/_calendar/month?year=${month.y}&month=${month.m}`);
      setPosts(data.posts || []);
    } catch {}
    setLoading(false);
  }, [month]);

  useEffect(() => { load(); }, [load]);
  useEffect(() => {
    const t = setInterval(load, 30000);
    return () => clearInterval(t);
  }, [load]);

  const visiblePosts = posts.filter((p) => {
    if (!filterTypes.has(p.content_type)) return false;
    if (!(p.channels || []).some((c) => filterChannels.has(c))) return false;
    return true;
  });

  // Build day buckets
  const buckets = {};
  for (const p of visiblePosts) {
    const d = new Date(p.scheduled_at);
    const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
    if (!buckets[key]) buckets[key] = [];
    buckets[key].push(p);
  }

  // Calendar grid: start on Monday, render 6 weeks always
  const firstDay = new Date(month.y, month.m - 1, 1);
  const startOfGrid = new Date(firstDay);
  const dow = (firstDay.getDay() + 6) % 7; // Mon=0
  startOfGrid.setDate(firstDay.getDate() - dow);

  const cells = [];
  for (let i = 0; i < 42; i++) {
    const d = new Date(startOfGrid);
    d.setDate(startOfGrid.getDate() + i);
    cells.push(d);
  }

  const monthLabel = new Date(month.y, month.m - 1, 1).toLocaleDateString("en-GB", { month: "long", year: "numeric" });

  const toggleType = (t) => {
    const next = new Set(filterTypes);
    next.has(t) ? next.delete(t) : next.add(t);
    setFilterTypes(next);
  };
  const toggleChannel = (c) => {
    const next = new Set(filterChannels);
    next.has(c) ? next.delete(c) : next.add(c);
    setFilterChannels(next);
  };

  return (
    <>
      {/* Filters */}
      <div style={{ marginBottom: 16, padding: 12, background: "#f9fafb", borderRadius: 8 }}>
        <div className="subtitle" style={{ fontSize: 13, marginBottom: 6, color: "#404040" }}>Format</div>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 12 }}>
          {ALL_TYPES.map((t) => {
            const on = filterTypes.has(t);
            const c = TYPE_COLORS[t];
            return (
              <button key={t} onClick={() => toggleType(t)}
                      style={{
                        fontSize: 12, padding: "4px 10px", borderRadius: 12,
                        border: `1px solid ${c.bg}`,
                        background: on ? c.bg : "transparent",
                        color: on ? "white" : c.bg,
                        cursor: "pointer", fontWeight: 600,
                      }}>
                {c.label}
              </button>
            );
          })}
        </div>
        <div className="subtitle" style={{ fontSize: 13, marginBottom: 6, color: "#404040" }}>Channel</div>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
          {ALL_CHANNELS.map((ch) => {
            const on = filterChannels.has(ch);
            return (
              <button key={ch} onClick={() => toggleChannel(ch)}
                      style={{
                        fontSize: 12, padding: "4px 10px", borderRadius: 12,
                        border: "1px solid #d4d4d4",
                        background: on ? "#0a0a0a" : "transparent",
                        color: on ? "white" : "#0a0a0a",
                        cursor: "pointer", fontWeight: 500,
                      }}>
                {titleCase(ch)}
              </button>
            );
          })}
        </div>
      </div>

      {/* Month nav */}
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
        <button className="btn" onClick={() => {
          const d = new Date(month.y, month.m - 2, 1);
          setMonth({ y: d.getFullYear(), m: d.getMonth() + 1 });
        }}>← Prev</button>
        <h2 style={{ margin: 0 }}>{monthLabel}{loading && " · loading…"}</h2>
        <button className="btn" onClick={() => {
          const d = new Date(month.y, month.m, 1);
          setMonth({ y: d.getFullYear(), m: d.getMonth() + 1 });
        }}>Next →</button>
      </div>

      {/* Day-of-week header */}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 4, marginBottom: 4 }}>
        {["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map((d) => (
          <div key={d} style={{ textAlign: "center", fontSize: 12, fontWeight: 600, color: "#737373", padding: "4px 0" }}>{d}</div>
        ))}
      </div>

      {/* Grid */}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 4 }}>
        {cells.map((d, i) => {
          const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
          const dayPosts = buckets[key] || [];
          const inMonth = d.getMonth() === month.m - 1;
          const isToday = d.toDateString() === today.toDateString();
          return (
            <div key={i} style={{
              minHeight: 110, padding: 6,
              background: isToday ? "#fef3c7" : (inMonth ? "white" : "#f9fafb"),
              border: "1px solid #e5e7eb",
              borderRadius: 6,
              opacity: inMonth ? 1 : 0.55,
            }}>
              <div style={{ fontSize: 11, fontWeight: 600, color: inMonth ? "#0a0a0a" : "#737373", marginBottom: 4 }}>
                {d.getDate()}
              </div>
              <div style={{ display: "flex", flexDirection: "column", gap: 3 }}>
                {dayPosts.map((p) => {
                  const c = TYPE_COLORS[p.content_type] || { bg: "#737373", label: p.content_type };
                  const isDone = p.status === "posted";
                  const isCancel = p.status === "cancelled";
                  const isFail = p.status === "failed";
                  return (
                    <button key={p.id} onClick={() => setOpenPost(p)} title={p._source_topic}
                            style={{
                              fontSize: 11, padding: "2px 5px", borderRadius: 4,
                              background: isCancel ? "#e5e7eb" : (isFail ? "#fee2e2" : c.bg),
                              color: isCancel ? "#737373" : (isFail ? "#991b1b" : "white"),
                              border: isDone ? "2px solid #059669" : "none",
                              textAlign: "left", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                              opacity: isCancel ? 0.6 : 1,
                              cursor: "pointer", fontWeight: 500,
                              textDecoration: isCancel ? "line-through" : "none",
                            }}>
                      {fmtTime(p.scheduled_at)} {p._source_topic}
                    </button>
                  );
                })}
              </div>
            </div>
          );
        })}
      </div>

      {visiblePosts.length === 0 && !loading && (
        <div className="empty" style={{ marginTop: 24, color: "#737373" }}>
          <p>Nothing scheduled in {monthLabel} matching your filters.</p>
        </div>
      )}

      {openPost && <CalendarPostModal post={openPost} onClose={() => setOpenPost(null)} onChange={() => { setOpenPost(null); load(); }} />}
    </>
  );
}

function CalendarPostModal({ post, onClose, onChange }) {
  const [working, setWorking] = useState(false);
  const [when, setWhen] = useState(() => {
    const d = new Date(post.scheduled_at);
    const pad = (n) => String(n).padStart(2, "0");
    return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
  });

  const reschedule = async () => {
    if (!when) return;
    setWorking(true);
    try {
      await api.patch(`/api/scheduled-posts/${post.id}`, { scheduled_at: new Date(when).toISOString() });
      onChange();
    } finally { setWorking(false); }
  };
  const cancel = async () => {
    if (!confirm("Cancel this scheduled post (and its companions)?")) return;
    setWorking(true);
    try {
      await api.patch(`/api/scheduled-posts/${post.id}`, { status: "cancelled" });
      onChange();
    } finally { setWorking(false); }
  };
  const remove = async () => {
    if (!confirm("Delete this scheduled post permanently?")) return;
    setWorking(true);
    try {
      await api.delete(`/api/scheduled-posts/${post.id}`);
      onChange();
    } finally { setWorking(false); }
  };

  const c = TYPE_COLORS[post.content_type] || { bg: "#737373", label: post.content_type };
  const editable = post.status !== "posted" && post.status !== "publishing";

  return (
    <div style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)",
      display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000,
    }} onClick={onClose}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: "white", borderRadius: 8, padding: 24, maxWidth: 560, width: "92%",
        maxHeight: "85vh", overflow: "auto", color: "#0a0a0a",
      }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "start", marginBottom: 16 }}>
          <div>
            <span style={{ fontSize: 11, padding: "3px 10px", borderRadius: 12, background: c.bg, color: "white", fontWeight: 600, textTransform: "uppercase" }}>{c.label}</span>
            <span style={{ marginLeft: 8, fontSize: 11, padding: "3px 10px", borderRadius: 12, background: STATUS_COLORS_CAL[post.status] || "#737373", color: "white", fontWeight: 600, textTransform: "uppercase" }}>{post.status}</span>
            <h3 style={{ margin: "8px 0 0 0", fontSize: 18 }}>{post._source_topic}</h3>
          </div>
          <button className="btn" onClick={onClose}>Close</button>
        </div>

        <div className="subtitle" style={{ fontSize: 13, marginBottom: 6, color: "#404040" }}>Channels</div>
        <div style={{ display: "flex", gap: 4, flexWrap: "wrap", marginBottom: 16 }}>
          {(post.channels || []).map((ch) => (
            <span key={ch} style={{ fontSize: 11, padding: "3px 8px", borderRadius: 4, background: "#e5e7eb", color: "#0a0a0a" }}>{titleCase(ch)}</span>
          ))}
        </div>

        {post._source_caption_preview && (
          <>
            <div className="subtitle" style={{ fontSize: 13, marginBottom: 6, color: "#404040" }}>Caption preview</div>
            <div style={{ fontSize: 13, lineHeight: 1.5, padding: 10, background: "#f9fafb", borderRadius: 6, marginBottom: 16, whiteSpace: "pre-wrap", color: "#0a0a0a" }}>
              {post._source_caption_preview}
              {(post._source_caption_preview || "").length >= 200 && "…"}
            </div>
          </>
        )}

        {post.error_message && (
          <div style={{ background: "#fef2f2", padding: 10, borderRadius: 6, marginBottom: 16, color: "#991b1b", fontSize: 13 }}>
            <strong>Error:</strong> {post.error_message}
          </div>
        )}

        {editable && (
          <>
            <div className="subtitle" style={{ fontSize: 13, marginBottom: 6, color: "#404040" }}>Reschedule</div>
            <div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
              <input type="datetime-local" value={when} onChange={(e) => setWhen(e.target.value)}
                     style={{ padding: 6, borderRadius: 6, border: "1px solid #d4d4d4", color: "#0a0a0a", background: "#ffffff" }} />
              <button className="btn primary" disabled={working} onClick={reschedule}>
                {working ? "Saving…" : "Save new time"}
              </button>
            </div>
            <div style={{ display: "flex", gap: 8 }}>
              <button className="btn" onClick={cancel} disabled={working}>Cancel post</button>
              <button className="btn" style={{ color: "#991b1b" }} onClick={remove} disabled={working}>Delete row</button>
            </div>
          </>
        )}

        {!editable && (
          <div className="subtitle" style={{ fontSize: 13, color: "#737373" }}>
            This post is {post.status} — it can no longer be rescheduled or cancelled from here.
            {post.status === "posted" && " To take it down, do that on the platform itself."}
          </div>
        )}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Journey tab — image+text reflective posts for X / Threads / LinkedIn
// ─────────────────────────────────────────────────────────────────────────────
function Journey() {
  const [list, setList] = useState([]);
  const [topic, setTopic] = useState("");
  const [creating, setCreating] = useState(false);
  const [expanded, setExpanded] = useState(null);

  const load = useCallback(async () => {
    try {
      const data = await api.get("/api/image-text-posts");
      setList(Array.isArray(data) ? data : []);
    } catch {}
  }, []);
  useEffect(() => {
    load();
    const t = setInterval(load, 6000);
    return () => clearInterval(t);
  }, [load]);

  const create = async () => {
    if (!topic.trim()) return;
    setCreating(true);
    try {
      const post = await api.post("/api/image-text-posts", { topic: topic.trim() });
      if (post?.id) {
        setTopic("");
        setExpanded(post.id);
        setList((prev) => [post, ...prev]);
      }
    } catch (e) {
      alert("Failed to create — check server logs");
    } finally { setCreating(false); }
  };

  const remove = async (id) => {
    if (!confirm("Delete this post?")) return;
    await api.delete(`/api/image-text-posts/${id}`);
    setList((prev) => prev.filter((p) => p.id !== id));
    if (expanded === id) setExpanded(null);
  };

  const update = (updated) =>
    setList((prev) => prev.map((p) => (p.id === updated.id ? updated : p)));

  return (
    <>
      <div className="idea-form">
        <input
          className="idea-topic-input"
          type="text"
          placeholder="Topic for this journey post (e.g. 'first 6 months building Costello — what I'd tell past me')"
          value={topic}
          onChange={(e) => setTopic(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && !creating && topic.trim() && create()}
          disabled={creating}
        />
        <button className="btn primary idea-gen-btn" disabled={creating || !topic.trim()} onClick={create}>
          {creating ? <><span className="loader"></span> Drafting…</> : "Draft post →"}
        </button>
      </div>

      {list.length === 0 && !creating && (
        <div className="empty" style={{ marginTop: 32 }}>
          <h3>No journey posts yet</h3>
          <p>Drop a topic above. AI drafts the text in your voice; you upload the image.</p>
        </div>
      )}

      <div className="idea-list">
        {list.map((p) => (
          <JourneyRow
            key={p.id} post={p}
            expanded={expanded === p.id}
            onToggle={() => setExpanded(expanded === p.id ? null : p.id)}
            onUpdate={update}
            onDelete={() => remove(p.id)}
          />
        ))}
      </div>
    </>
  );
}

const JOURNEY_STATUS_COLORS = {
  drafting:    "#3b82f6",
  drafted:     "#f59e0b",
  image_ready: "#10b981",
  scheduled:   "#a855f7",
  posted:      "#059669",
  error:       "#ef4444",
};

function JourneyRow({ post, expanded, onToggle, onUpdate, onDelete }) {
  const status = post.status || "drafting";
  const color = JOURNEY_STATUS_COLORS[status] || "#737373";

  return (
    <div className="idea-row" style={{ borderLeft: `4px solid ${color}` }}>
      <div onClick={onToggle}
           style={{ cursor: "pointer", padding: 12, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 600 }}>{post.topic}</div>
          <div className="subtitle" style={{ fontSize: 12, marginTop: 4 }}>
            {(post.draft_text || "").slice(0, 100)}{(post.draft_text || "").length > 100 ? "…" : ""}
          </div>
        </div>
        <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
          <span style={{
            fontSize: 11, padding: "3px 10px", borderRadius: 12,
            background: color, color: "white", fontWeight: 600, textTransform: "uppercase",
          }}>{titleCase(status)}</span>
          <span style={{ color: "#999" }}>{expanded ? "▾" : "▸"}</span>
        </div>
      </div>

      {expanded && (
        <div style={{ padding: 16, borderTop: "1px solid #eee" }}>
          {post.error_message && (
            <div style={{ background: "#fef2f2", padding: 12, borderRadius: 8, marginBottom: 16, color: "#991b1b", fontSize: 13 }}>
              <strong>Error:</strong> {post.error_message}
            </div>
          )}
          <JourneyEditor post={post} onUpdate={onUpdate} />
          <div style={{ marginTop: 16, display: "flex", gap: 8, flexWrap: "wrap" }}>
            <JourneySchedule post={post} onUpdate={onUpdate} />
            <button className="btn" style={{ color: "#991b1b" }} onClick={onDelete}>Delete</button>
          </div>
        </div>
      )}
    </div>
  );
}

function JourneyEditor({ post, onUpdate }) {
  const [text, setText] = useState(post.draft_text || "");
  const [saving, setSaving] = useState(false);
  const [uploading, setUploading] = useState(false);
  useEffect(() => { setText(post.draft_text || ""); }, [post.draft_text]);

  const save = async () => {
    setSaving(true);
    try {
      const updated = await api.patch(`/api/image-text-posts/${post.id}`, { draft_text: text });
      if (updated?.id) onUpdate(updated);
    } finally { setSaving(false); }
  };

  const upload = async (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    setUploading(true);
    try {
      const fd = new FormData();
      fd.append("file", file);
      const res = await fetch(`/api/image-text-posts/${post.id}/upload-image`, { method: "POST", body: fd });
      const updated = await res.json();
      if (updated?.id) onUpdate(updated);
    } catch (e) { alert("Upload failed — check server logs"); }
    finally { setUploading(false); }
  };

  const charCount = text.length;
  const overLimit = charCount > 280;

  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 4 }}>
        <div className="subtitle" style={{ fontSize: 13 }}>Post text</div>
        <div style={{ fontSize: 12, color: overLimit ? "#dc2626" : "#737373" }}>
          {charCount} / 280 chars
        </div>
      </div>
      <textarea
        value={text}
        onChange={(e) => setText(e.target.value)}
        rows={5}
        style={{
          width: "100%", padding: 12, borderRadius: 8,
          border: `1px solid ${overLimit ? "#dc2626" : "#d4d4d4"}`,
          fontSize: 14, lineHeight: 1.55, fontFamily: "inherit",
          color: "#0a0a0a", background: "#ffffff", resize: "vertical",
        }}
      />
      <div style={{ display: "flex", gap: 8, marginTop: 8, marginBottom: 16 }}>
        <button className="btn" disabled={saving || overLimit} onClick={save}>
          {saving ? "Saving…" : "Save text"}
        </button>
      </div>

      <div className="subtitle" style={{ fontSize: 13, marginBottom: 4 }}>Image</div>
      {post.image_drive_url ? (
        <div style={{ marginBottom: 8 }}>
          <a href={post.image_drive_url} target="_blank" rel="noreferrer">
            <img src={driveThumbUrl(post.image_drive_url)} alt="post"
                 style={{ maxWidth: 220, borderRadius: 8, border: "1px solid #e5e5e5", background: "#fff" }} />
          </a>
        </div>
      ) : (
        <div style={{ fontSize: 13, color: "#737373", marginBottom: 8 }}>No image uploaded yet.</div>
      )}
      <input type="file" accept="image/*" onChange={upload} disabled={uploading} style={{ fontSize: 13 }} />
      {uploading && <span style={{ fontSize: 12, color: "#737373", marginLeft: 8 }}>uploading…</span>}
    </div>
  );
}

function JourneySchedule({ post, onUpdate }) {
  const [busy, setBusy] = useState(false);
  const ready =
    (post.draft_text || "").trim() &&
    (post.draft_text || "").length <= 280 &&
    post.image_drive_url &&
    post.status !== "scheduled" && post.status !== "posted";

  const fire = async () => {
    setBusy(true);
    try {
      const res = await api.post(`/api/image-text-posts/${post.id}/schedule`);
      if (res?.id) onUpdate({ ...post, status: "scheduled", scheduled_at: res.scheduled_at });
    } catch (e) { alert("Schedule failed — check server logs"); }
    finally { setBusy(false); }
  };

  if (post.status === "scheduled") {
    return <span className="subtitle" style={{ fontSize: 13, alignSelf: "center" }}>
      Scheduled for {fmtDate(post.scheduled_at)} {fmtTime(post.scheduled_at)} (X / Threads / LinkedIn)
    </span>;
  }
  if (post.status === "posted") {
    return <span className="subtitle" style={{ fontSize: 13, alignSelf: "center", color: "#059669" }}>
      ✓ Posted
    </span>;
  }
  if (!ready) {
    return <span className="subtitle" style={{ fontSize: 13, alignSelf: "center" }}>
      Need text (≤280 chars) + image before scheduling.
    </span>;
  }
  return (
    <button className="btn primary" disabled={busy} onClick={fire}>
      {busy ? "Scheduling…" : "Schedule (next 08:00 London)"}
    </button>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Long video tab — weekly YouTube + auto-announcement
// ─────────────────────────────────────────────────────────────────────────────
const LV_STATUS_COLORS = {
  draft:                 "#9ca3af",
  uploaded:              "#f59e0b",
  announcement_drafted:  "#10b981",
  scheduled:             "#a855f7",
  posted:                "#059669",
  error:                 "#ef4444",
};

function LongVideo() {
  const [list, setList] = useState([]);
  const [showCreate, setShowCreate] = useState(false);
  const [title, setTitle] = useState("");
  const [description, setDescription] = useState("");
  const [topic, setTopic] = useState("");
  const [creating, setCreating] = useState(false);
  const [expanded, setExpanded] = useState(null);

  const load = useCallback(async () => {
    try {
      const data = await api.get("/api/long-videos");
      setList(Array.isArray(data) ? data : []);
    } catch {}
  }, []);
  useEffect(() => {
    load();
    const t = setInterval(load, 6000);
    return () => clearInterval(t);
  }, [load]);

  const create = async () => {
    if (!title.trim()) return;
    setCreating(true);
    try {
      const v = await api.post("/api/long-videos", {
        title: title.trim(), description: description.trim(), announcement_topic: topic.trim(),
      });
      if (v?.id) {
        setTitle(""); setDescription(""); setTopic("");
        setShowCreate(false);
        setExpanded(v.id);
        setList((prev) => [v, ...prev]);
      }
    } finally { setCreating(false); }
  };

  const update = (updated) =>
    setList((prev) => prev.map((v) => (v.id === updated.id ? updated : v)));

  const remove = async (id) => {
    if (!confirm("Delete this long video record?")) return;
    await api.delete(`/api/long-videos/${id}`);
    setList((prev) => prev.filter((v) => v.id !== id));
    if (expanded === id) setExpanded(null);
  };

  return (
    <>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
        <div className="subtitle" style={{ fontSize: 14 }}>{list.length} long-form{list.length === 1 ? "" : "s"}</div>
        <button className="btn primary" onClick={() => setShowCreate(true)}>+ New long-form</button>
      </div>

      {showCreate && (
        <div className="idea-form" style={{ marginBottom: 24 }}>
          <input
            className="idea-topic-input"
            type="text"
            placeholder="Video title (also goes to YouTube)"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            disabled={creating}
            autoFocus
          />
          <textarea
            className="idea-desc-input"
            placeholder="Description (YouTube video description)"
            value={description}
            onChange={(e) => setDescription(e.target.value)}
            disabled={creating}
            rows={3}
          />
          <textarea
            className="idea-desc-input"
            placeholder="Announcement angle for X/Threads/LinkedIn — what's the hook readers should care about?"
            value={topic}
            onChange={(e) => setTopic(e.target.value)}
            disabled={creating}
            rows={2}
          />
          <div style={{ display: "flex", gap: 8 }}>
            <button className="btn primary idea-gen-btn" disabled={creating || !title.trim()} onClick={create}>
              {creating ? "Creating…" : "Create →"}
            </button>
            <button className="btn" onClick={() => setShowCreate(false)} disabled={creating}>Cancel</button>
          </div>
        </div>
      )}

      {list.length === 0 && !showCreate && (
        <div className="empty" style={{ marginTop: 32 }}>
          <h3>No long-form videos yet</h3>
          <p>Hit "+ New long-form" once you've filmed and edited one. You upload the MP4 + thumbnail; AI drafts the X/Threads/LinkedIn announcement.</p>
        </div>
      )}

      <div className="idea-list">
        {list.map((v) => (
          <LongVideoRow key={v.id} video={v}
                        expanded={expanded === v.id}
                        onToggle={() => setExpanded(expanded === v.id ? null : v.id)}
                        onUpdate={update}
                        onDelete={() => remove(v.id)} />
        ))}
      </div>
    </>
  );
}

function LongVideoRow({ video, expanded, onToggle, onUpdate, onDelete }) {
  const status = video.status || "draft";
  const color = LV_STATUS_COLORS[status] || "#737373";

  return (
    <div className="idea-row" style={{ borderLeft: `4px solid ${color}` }}>
      <div onClick={onToggle}
           style={{ cursor: "pointer", padding: 12, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 600 }}>{video.title}</div>
          <div className="subtitle" style={{ fontSize: 12, marginTop: 4 }}>
            created {fmtDate(video.created_at)}
            {video.scheduled_at && ` · scheduled ${fmtDate(video.scheduled_at)} ${fmtTime(video.scheduled_at)}`}
          </div>
        </div>
        <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
          <span style={{
            fontSize: 11, padding: "3px 10px", borderRadius: 12,
            background: color, color: "white", fontWeight: 600, textTransform: "uppercase",
          }}>{titleCase(status)}</span>
          <span style={{ color: "#999" }}>{expanded ? "▾" : "▸"}</span>
        </div>
      </div>

      {expanded && (
        <div style={{ padding: 16, borderTop: "1px solid #eee" }}>
          {video.error_message && (
            <div style={{ background: "#fef2f2", padding: 12, borderRadius: 8, marginBottom: 16, color: "#991b1b", fontSize: 13 }}>
              <strong>Error:</strong> {video.error_message}
            </div>
          )}

          <LongVideoUploads video={video} onUpdate={onUpdate} />
          <LongVideoAnnouncement video={video} onUpdate={onUpdate} />
          <LongVideoSchedule video={video} onUpdate={onUpdate} />

          <div style={{ marginTop: 16, display: "flex", gap: 8 }}>
            <button className="btn" style={{ color: "#991b1b" }} onClick={onDelete}>Delete</button>
          </div>
        </div>
      )}
    </div>
  );
}

function LongVideoUploads({ video, onUpdate }) {
  const [busy, setBusy] = useState("");
  const upload = async (kind, file) => {
    setBusy(kind);
    try {
      const fd = new FormData();
      fd.append("file", file);
      const res = await fetch(`/api/long-videos/${video.id}/upload-${kind}`, { method: "POST", body: fd });
      const updated = await res.json();
      if (updated?.id) onUpdate(updated);
    } catch (e) { alert(`${kind} upload failed`); }
    finally { setBusy(""); }
  };

  return (
    <div style={{ marginBottom: 16 }}>
      <div className="subtitle" style={{ fontSize: 13, marginBottom: 8 }}>Files</div>
      <div style={{ display: "flex", gap: 16, alignItems: "flex-start", flexWrap: "wrap" }}>
        <div>
          <div style={{ fontSize: 12, marginBottom: 4 }}>Video MP4</div>
          {video.video_drive_url
            ? <a href={video.video_drive_url} target="_blank" rel="noreferrer" style={{ fontSize: 13 }}>✓ uploaded</a>
            : <input type="file" accept="video/*" disabled={busy === "video"}
                     onChange={(e) => e.target.files?.[0] && upload("video", e.target.files[0])} />}
          {busy === "video" && <span style={{ fontSize: 12, color: "#737373", marginLeft: 8 }}>uploading…</span>}
        </div>
        <div>
          <div style={{ fontSize: 12, marginBottom: 4 }}>Thumbnail JPG/PNG</div>
          {video.thumbnail_drive_url
            ? <a href={video.thumbnail_drive_url} target="_blank" rel="noreferrer">
                <img src={driveThumbUrl(video.thumbnail_drive_url)} alt="thumb"
                     style={{ width: 120, height: 68, objectFit: "cover", borderRadius: 4, border: "1px solid #e5e5e5", background: "#fff" }} />
              </a>
            : <input type="file" accept="image/*" disabled={busy === "thumbnail"}
                     onChange={(e) => e.target.files?.[0] && upload("thumbnail", e.target.files[0])} />}
          {busy === "thumbnail" && <span style={{ fontSize: 12, color: "#737373", marginLeft: 8 }}>uploading…</span>}
        </div>
      </div>
    </div>
  );
}

function LongVideoAnnouncement({ video, onUpdate }) {
  const [text, setText] = useState(video.announcement_text || "");
  const [drafting, setDrafting] = useState(false);
  const [saving, setSaving] = useState(false);
  useEffect(() => { setText(video.announcement_text || ""); }, [video.announcement_text]);

  const draft = async () => {
    setDrafting(true);
    try {
      await api.post(`/api/long-videos/${video.id}/draft-announcement`);
      // The draft fires in a background task — poll the row briefly for the new text.
      // The parent component's polling cycle will pick it up; trigger a manual fetch too.
      setTimeout(async () => {
        const fresh = await api.get(`/api/long-videos/${video.id}`);
        if (fresh?.id) onUpdate(fresh);
      }, 4000);
    } finally { setDrafting(false); }
  };

  const save = async () => {
    setSaving(true);
    try {
      const updated = await api.patch(`/api/long-videos/${video.id}`, { announcement_text: text });
      if (updated?.id) onUpdate(updated);
    } finally { setSaving(false); }
  };

  const charCount = text.length;
  const overLimit = charCount > 280;

  return (
    <div style={{ marginBottom: 16 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 4 }}>
        <div className="subtitle" style={{ fontSize: 13 }}>Announcement (X / Threads / LinkedIn)</div>
        <div style={{ fontSize: 12, color: overLimit ? "#dc2626" : "#737373" }}>{charCount} / 280</div>
      </div>
      <textarea
        value={text}
        onChange={(e) => setText(e.target.value)}
        rows={4}
        placeholder="Click 'AI draft' to generate, or type yourself"
        style={{
          width: "100%", padding: 12, borderRadius: 8,
          border: `1px solid ${overLimit ? "#dc2626" : "#d4d4d4"}`,
          fontSize: 14, lineHeight: 1.55, fontFamily: "inherit",
          color: "#0a0a0a", background: "#ffffff", resize: "vertical",
        }}
      />
      <div style={{ display: "flex", gap: 8, marginTop: 8 }}>
        <button className="btn" disabled={drafting} onClick={draft}>
          {drafting ? "Drafting…" : "AI draft"}
        </button>
        <button className="btn" disabled={saving || overLimit} onClick={save}>
          {saving ? "Saving…" : "Save"}
        </button>
      </div>
    </div>
  );
}

function LongVideoSchedule({ video, onUpdate }) {
  const [busy, setBusy] = useState(false);
  const ready =
    video.video_drive_url &&
    (video.announcement_text || "").trim() &&
    (video.announcement_text || "").length <= 280 &&
    video.status !== "scheduled" && video.status !== "posted";

  const fire = async () => {
    setBusy(true);
    try {
      const res = await api.post(`/api/long-videos/${video.id}/schedule`);
      if (res?.id) onUpdate({ ...video, status: "scheduled", scheduled_at: res.scheduled_at });
    } catch (e) { alert("Schedule failed — check server logs"); }
    finally { setBusy(false); }
  };

  if (video.status === "scheduled") {
    return <div className="subtitle" style={{ fontSize: 13, color: "#a855f7", marginBottom: 8 }}>
      ✓ Scheduled for {fmtDate(video.scheduled_at)} {fmtTime(video.scheduled_at)} (YouTube + announcement to X / Threads / LinkedIn at the same time)
    </div>;
  }
  if (video.status === "posted") {
    return <div className="subtitle" style={{ fontSize: 13, color: "#059669", marginBottom: 8 }}>✓ Posted</div>;
  }
  if (!ready) {
    return <div className="subtitle" style={{ fontSize: 13, marginBottom: 8 }}>
      Need video upload + announcement text (≤280 chars) before scheduling.
    </div>;
  }
  return (
    <button className="btn primary" disabled={busy} onClick={fire}>
      {busy ? "Scheduling…" : "Schedule (next 14:00 London)"}
    </button>
  );
}

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