/* onboarding.jsx - Flow 1: Knowledge upload + base model + generate test chatbot */

function FileGlyph({ kind }) {
  const map = { pdf: "doc", sheet: "grid", link: "link" };
  const bg = { pdf: "var(--bad-bg)", sheet: "var(--good-bg)", link: "var(--info-bg)" };
  const fg = { pdf: "var(--bad)", sheet: "var(--good)", link: "var(--info)" };
  return (
    <div style={{ width: 34, height: 34, borderRadius: "var(--r-sm)", background: bg[kind], color: fg[kind], display: "flex", alignItems: "center", justifyContent: "center", flex: "none" }}>
      {I(map[kind], { style: { width: 17, height: 17 } })}
    </div>
  );
}

function ModelCard({ m, on, onClick }) {
  return (
    <div className="tile selectable" data-on={on ? "1" : "0"} onClick={onClick}
         style={{ padding: 16, display: "flex", flexDirection: "column", gap: 10, position: "relative" }}>
      {m.recommended && <span className="badge badge-accent" style={{ position: "absolute", top: 12, right: 12 }}>Recommended</span>}
      <div className="row" style={{ gap: 4 }}>
        {[1,2,3,4,5].map(i => (
          <span key={i} style={{ width: 14, height: 5, borderRadius: 9, background: i <= m.quality ? "var(--primary)" : "var(--surface-3)" }} />
        ))}
      </div>
      <div>
        <div className="h3" style={{ marginBottom: 3 }}>{m.tier}</div>
        <div className="caption" style={{ lineHeight: 1.45 }}>{m.blurb}</div>
      </div>
      <div className="row" style={{ gap: 14, marginTop: "auto", paddingTop: 6 }}>
        <span className="caption">{I("bolt", { style: { width: 12, height: 12, verticalAlign: "-2px", marginRight: 3 } })}{m.latency}</span>
        <span className="caption">{m.cost}/msg</span>
      </div>
    </div>
  );
}


const ASSISTANT_PRESETS = {
  sales: {
    label: "Sales assistant",
    openingMessage: "Hi, I am your product assistant. I can help answer questions from the uploaded knowledge base, explain product details, compare options, address common concerns, and suggest the best next step.",
    systemPrompt: `You are a polished, commercially-minded product assistant for the business represented by the uploaded knowledge base.

Your job is to help prospects and customers understand the product, compare options, answer objections, and move toward a confident next step.

Rules:
- Use the uploaded knowledge base as the source of truth.
- Sound professional, warm, specific, and useful. Avoid generic chatbot language.
- When answering sales or evaluation questions, address the user's concern directly, then support the answer with concrete details from the documents.
- If the documents do not contain enough information, say that clearly and suggest what information would be needed.
- Do not invent prices, capabilities, policies, guarantees, timelines, or technical claims that are not supported by the uploaded knowledge base.
- Keep answers concise unless the user asks for detail.`,
  },
  support: {
    label: "Support specialist",
    openingMessage: "Hi, I am your support assistant. I can answer questions from the uploaded knowledge base, clarify policies, explain steps, and help troubleshoot common issues.",
    systemPrompt: `You are a calm, precise support assistant for the business represented by the uploaded knowledge base.

Use the uploaded documents as the source of truth. Help users understand policies, product details, steps, requirements, and troubleshooting guidance. Be concise and practical. If the answer is not supported by the documents, say so clearly and ask for the missing detail. Do not invent policies, prices, timelines, or guarantees.`,
  },
  advisor: {
    label: "Product advisor",
    openingMessage: "Hi, I am your product advisor. I can help explain what this product does, who it is for, how it compares, and which details matter for a buying decision.",
    systemPrompt: `You are a thoughtful product advisor for the business represented by the uploaded knowledge base.

Help users evaluate fit, understand tradeoffs, compare options, and identify the most relevant product details. Ground every claim in the uploaded documents. Answer objections directly and professionally. If the documents do not contain enough support, say that clearly instead of guessing.`,
  },
};

function BotNameDialog({ open, busy, name, count, onNameChange, onCancel, onConfirm }) {
  if (!open) return null;
  const canCreate = name.trim().length > 0 && !busy;
  return (
    <div className="scrim" role="presentation" onMouseDown={(e) => { if (e.target === e.currentTarget && !busy) onCancel(); }}>
      <div className="modal" role="dialog" aria-modal="true" aria-labelledby="selected-bot-title" style={{ maxWidth: 500 }}>
        <div className="card-hd between" style={{ gap: 16 }}>
          <div className="row" style={{ gap: 12, minWidth: 0 }}>
            <div style={{ width: 38, height: 38, borderRadius: "var(--r-sm)", background: "var(--primary-soft)", color: "var(--ink)", display: "flex", alignItems: "center", justifyContent: "center", flex: "none" }}>{I("bot", { style: { width: 19, height: 19 } })}</div>
            <div style={{ minWidth: 0 }}>
              <div id="selected-bot-title" className="h3">New bot from selected PDFs</div>
              <div className="caption">Create a dedicated chatbot with {count} selected source{count === 1 ? "" : "s"}.</div>
            </div>
          </div>
          <button className="icon-btn" disabled={busy} onClick={onCancel} style={{ width: 32, height: 32 }}>{I("x", { style: { width: 15, height: 15 } })}</button>
        </div>
        <div className="card-pad stack" style={{ gap: 14 }}>
          <label>
            <span className="field-label">Bot name</span>
            <input className="input" autoFocus value={name} placeholder="Product advisor" onChange={(e) => onNameChange(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && canCreate) onConfirm(); if (e.key === "Escape" && !busy) onCancel(); }} />
          </label>
        </div>
        <div className="row" style={{ justifyContent: "flex-end", gap: 10, padding: "14px 18px", borderTop: "1px solid var(--border)", background: "var(--surface-2)" }}>
          <button className="btn btn-secondary" disabled={busy} onClick={onCancel}>Cancel</button>
          <button className="btn btn-primary" disabled={!canCreate} onClick={onConfirm}>{busy ? "Building..." : "Create bot"}</button>
        </div>
      </div>
    </div>
  );
}

function DeleteSourceDialog({ source, busy, onCancel, onConfirm }) {
  if (!source) return null;
  const chunks = Number(source.chunks) || 0;
  return (
    <div className="scrim" role="presentation" onMouseDown={(e) => { if (e.target === e.currentTarget && !busy) onCancel(); }}>
      <div className="modal" role="dialog" aria-modal="true" aria-labelledby="delete-source-title" style={{ maxWidth: 500 }}>
        <div className="card-hd between" style={{ gap: 16 }}>
          <div className="row" style={{ gap: 12, minWidth: 0 }}>
            <div style={{ width: 38, height: 38, borderRadius: "var(--r-sm)", background: "var(--bad-bg)", color: "var(--bad)", display: "flex", alignItems: "center", justifyContent: "center", flex: "none" }}>
              {I("alert", { style: { width: 19, height: 19 } })}
            </div>
            <div style={{ minWidth: 0 }}>
              <div id="delete-source-title" className="h3">Remove source</div>
              <div className="caption" style={{ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{source.name}</div>
            </div>
          </div>
          <button className="icon-btn" disabled={busy} onClick={onCancel} style={{ width: 32, height: 32 }}>{I("x", { style: { width: 15, height: 15 } })}</button>
        </div>
        <div className="card-pad stack" style={{ gap: 14 }}>
          <div style={{ fontSize: 13.5, lineHeight: 1.55, color: "var(--ink-2)" }}>
            This removes the source from this bot's knowledge base and clears its searchable chunks for this bot. Other bots using the same PDF are not affected.
          </div>
          <div className="stack" style={{ gap: 8, padding: 12, border: "1px solid var(--border)", borderRadius: "var(--r-sm)", background: "var(--surface-2)" }}>
            <div className="row" style={{ gap: 8, fontSize: 12.5, color: "var(--ink-2)" }}>
              {I("doc", { style: { width: 13, height: 13, color: "var(--bad)" } })}
              <span>{source.size || "Uploaded file"}</span>
            </div>
            <div className="row" style={{ gap: 8, fontSize: 12.5, color: "var(--ink-2)" }}>
              {I("db", { style: { width: 13, height: 13, color: "var(--bad)" } })}
              <span>{chunks ? `${chunks.toLocaleString()} indexed chunks` : "Knowledge index entries"}</span>
            </div>
          </div>
        </div>
        <div className="row" style={{ justifyContent: "flex-end", gap: 10, padding: "14px 18px", borderTop: "1px solid var(--border)", background: "var(--surface-2)" }}>
          <button className="btn btn-secondary" disabled={busy} onClick={onCancel}>Cancel</button>
          <button className="btn btn-primary" disabled={busy} onClick={onConfirm} style={{ background: "var(--bad)", color: "white", borderColor: "var(--bad)" }}>
            {busy ? "Removing..." : "Remove source"}
          </button>
        </div>
      </div>
    </div>
  );
}

function OnboardingView({ ctx }) {
  const { user: authUser, loading: authLoading, signIn: authSignIn } = useFirebaseAuth();
  const { MODELS } = window.DEMO;
  const initialAgent = ctx.activeAgent || window.ModelOSIntegrations.load().activeAgent;
  const knowledgeSourcesKey = initialAgent?.modelId ? `knowledgeSources:${initialAgent.modelId}` : "knowledgeSources:none";
  const [sources, setSources] = React.useState(() => window.ModelOSUserStorage.load(authUser?.uid, knowledgeSourcesKey, []));
  const [model, setModel] = React.useState(ctx.model.id);
  const [hot, setHot] = React.useState(false);
  const [building, setBuilding] = React.useState(null); // null | 0..n
  const [busy, setBusy] = React.useState(false);
  const [notice, setNotice] = React.useState(null);
  const [settings, setSettings] = React.useState(() => window.ModelOSIntegrations.load());
  const [deleting, setDeleting] = React.useState(null); // fileId being deleted
  const [deleteTarget, setDeleteTarget] = React.useState(null);
  const [selected, setSelected] = React.useState(new Set());
  const [buildingBot, setBuildingBot] = React.useState(false);
  const [assistantSetup, setAssistantSetup] = React.useState(() => {
    const loaded = window.ModelOSIntegrations.load();
    const defaults = window.ModelOSIntegrations.defaults;
    return {
      voicePreset: loaded.assistantVoice || "sales",
      systemPrompt: loaded.systemPrompt || defaults.systemPrompt,
      openingMessage: loaded.openingMessage || defaults.openingMessage,
    };
  });
  const [savingSetup, setSavingSetup] = React.useState(false);
  const [botNameOpen, setBotNameOpen] = React.useState(false);
  const [botName, setBotName] = React.useState("Product advisor");
  const fileRef = React.useRef(null);

  const isRealUser = authUser && !authUser.isDemo;
  const botSources = activeAgent?.modelId ? sources.filter(s => s.modelId === activeAgent.modelId) : sources;
  const indexed = botSources.filter(s => s.status === "indexed");
  const totalChunks = indexed.reduce((a, s) => a + s.chunks, 0);
  const hasApiKey = window.ModelOSIntegrations.hasLlmApiKey(settings);
  const activeAgent = ctx.activeAgent || settings.activeAgent;
  const activeModelId = activeAgent?.modelId || "";
  const activeSourcesKey = activeModelId ? `knowledgeSources:${activeModelId}` : "knowledgeSources:none";

  const updateAssistantSetup = (patch) => {
    const next = { ...assistantSetup, ...patch };
    setAssistantSetup(next);
    const saved = window.ModelOSIntegrations.save({
      assistantVoice: next.voicePreset,
      systemPrompt: next.systemPrompt,
      openingMessage: next.openingMessage,
    });
    setSettings(saved);
  };

  const applyAssistantPreset = (id) => {
    const preset = ASSISTANT_PRESETS[id];
    if (!preset) return;
    updateAssistantSetup({ voicePreset: id, systemPrompt: preset.systemPrompt, openingMessage: preset.openingMessage });
  };

  const saveAssistantSetup = async ({ quiet = false } = {}) => {
    const clean = {
      voicePreset: assistantSetup.voicePreset || "sales",
      systemPrompt: String(assistantSetup.systemPrompt || "").trim() || window.ModelOSIntegrations.defaults.systemPrompt,
      openingMessage: String(assistantSetup.openingMessage || "").trim() || window.ModelOSIntegrations.defaults.openingMessage,
      setupCustomized: true,
    };
    updateAssistantSetup(clean);
    if (!activeAgent?.modelId || !isRealUser) return clean;
    setSavingSetup(true);
    try {
      const updated = await window.ModelOSIntegrations.updateAgent(activeAgent.modelId, clean, authUser);
      const nextAgent = { ...activeAgent, ...updated, modelId: activeAgent.modelId };
      ctx.setActiveAgent(nextAgent);
      window.ModelOSUserStorage.save(authUser.uid, "activeAgent", nextAgent);
      if (!quiet) setNotice({ type: "success", text: "Assistant setup saved." });
    } catch (err) {
      if (!quiet) setNotice({ type: "error", text: `Failed to save assistant setup: ${err.message || "Unknown error"}` });
    } finally {
      setSavingSetup(false);
    }
    return clean;
  };
  const remoteStatusRank = (status) => status === "ready" ? 3 : status === "processing" ? 2 : status === "failed" ? 1 : 0;
  const remoteTime = (file) => Date.parse(file?.updatedAt || file?.createdAt || "") || 0;
  const remoteChunks = (file) => Number(file?.chunkCount || file?.chunks || 0) || 0;
  const remoteNameMatches = (file, item) => {
    const fileName = String(file?.filename || file?.name || "").trim();
    const itemName = String(item?.filename || item?.name || "").trim();
    return fileName && itemName && fileName === itemName;
  };
  const remoteStorageMatches = (file, item) => {
    const itemStorage = item?.storagePath || item?.raw?.uploadData?.storagePath || "";
    return itemStorage && file?.storagePath === itemStorage;
  };
  const pickBestRemoteFile = (item, files = []) => {
    const visibleFiles = files.filter(file => file.status !== "failed");
    const exact = visibleFiles.find(file => file.fileId && (file.fileId === item.fileId || file.fileId === item.id));
    if (exact) return exact;

    const storageMatch = visibleFiles.find(file => remoteStorageMatches(file, item));
    if (storageMatch) return storageMatch;

    const sameName = visibleFiles.filter(file => remoteNameMatches(file, item));
    if (!sameName.length) return null;
    return sameName.sort((a, b) =>
      remoteStatusRank(b.status) - remoteStatusRank(a.status) ||
      remoteChunks(b) - remoteChunks(a) ||
      remoteTime(b) - remoteTime(a)
    )[0];
  };
  const remoteToSource = (file, modelId) => {
    const mappedStatus = file.status === "ready" ? "indexed" : file.status === "processing" ? "indexing" : file.status;
    return {
      id: file.fileId,
      fileId: file.fileId,
      name: file.filename || "Knowledge source.pdf",
      filename: file.filename || "Knowledge source.pdf",
      kind: "pdf",
      size: file.sizeBytes ? `${Math.max(1, Math.round(file.sizeBytes / 1024))} KB` : "PDF",
      chunks: remoteChunks(file),
      status: mappedStatus,
      storagePath: file.storagePath,
      modelId: modelId || null,
      raw: { uploadData: { storagePath: file.storagePath } },
    };
  };

  // clear knowledge sources on sign-out (prevents persist effect from writing stale values back)
  React.useEffect(() => {
    if (authLoading) return;
    if (!isRealUser) setSources([]);
  }, [isRealUser, authLoading]);

  React.useEffect(() => {
    setSelected(new Set());
    setDeleteTarget(null);
    if (!isRealUser || !activeModelId) {
      setSources([]);
      return;
    }
    setSources(window.ModelOSUserStorage.load(authUser.uid, activeSourcesKey, []));
  }, [activeSourcesKey, activeModelId, authUser?.uid, isRealUser]);

  React.useEffect(() => {
    if (isRealUser && activeModelId) window.ModelOSUserStorage.save(authUser.uid, activeSourcesKey, sources);
  }, [sources, authUser, isRealUser, activeSourcesKey, activeModelId]);

  React.useEffect(() => {
    const agent = ctx.activeAgent || window.ModelOSIntegrations.load().activeAgent;
    if (!agent?.modelId) return;
    let cancelled = false;

    const syncKnowledgeStatus = async () => {
      try {
        const status = await window.ModelOSIntegrations.loadKnowledgeStatus(agent.modelId, authUser);
        if (cancelled) return;

        setSources(prev => {
          let changed = false;
          const visibleRemoteFiles = (status?.files || []).filter(file => file.status !== "failed");
          const next = prev
            .filter(item => item.status !== "failed")
            .map(item => {
              const remote = pickBestRemoteFile(item, visibleRemoteFiles);
              if (!remote) {
                if (item.id?.startsWith("pending-") || !item.fileId) return item;
                changed = true;
                return null;
              }

              const mappedStatus = remote.status === "ready" ? "indexed" : remote.status === "processing" ? "indexing" : remote.status;
              const chunks = remoteChunks(remote) || (mappedStatus === "indexed" ? status.chunkCount : item.chunks) || 0;
              if (item.status === mappedStatus && item.chunks === chunks && item.fileId === remote.fileId && item.modelId === agent.modelId) return item;
              changed = true;
              return {
                ...item,
                id: item.id?.startsWith("pending-") ? (remote.fileId || item.id) : item.id,
                fileId: remote.fileId || item.fileId,
                filename: remote.filename || item.filename || item.name,
                status: mappedStatus,
                chunks,
                storagePath: remote.storagePath || item.storagePath,
                modelId: agent.modelId,
                raw: item.raw || { uploadData: { storagePath: remote.storagePath } },
                error: null
              };
            })
            .filter(Boolean);

          for (const remote of visibleRemoteFiles) {
            const exists = next.some(item =>
              item.fileId === remote.fileId ||
              item.id === remote.fileId ||
              remoteStorageMatches(remote, item)
            );
            if (!exists) {
              next.push(remoteToSource(remote, agent.modelId));
              changed = true;
            }
          }

          if (!next.length && !visibleRemoteFiles.length && prev.length) changed = true;
          const removedFailed = next.length !== prev.length && prev.some(item => item.status === "failed");
          if (!changed && !removedFailed) return prev;
          return next;
        });
      } catch (error) {
        console.warn("Knowledge status sync failed:", error.message);
      }
    };

    syncKnowledgeStatus();
    const interval = setInterval(syncKnowledgeStatus, 8000);
    return () => {
      cancelled = true;
      clearInterval(interval);
    };
  }, [ctx.activeAgent?.modelId, authUser]);

  const addFiles = async (fileList) => {
    if (!isRealUser) {
      setNotice({ type: "error", text: "Please sign in with Google before uploading." });
      authSignIn();
      return;
    }

    const files = Array.from(fileList || []).filter(file => file.name.toLowerCase().endsWith(".pdf"));
    if (files.length === 0) {
      setNotice({ type: "error", text: "Upload a PDF file to build the RAG knowledge base." });
      return;
    }

    setBusy(true);
    setNotice({ type: "info", text: "Uploading PDF and building RAG index..." });

    for (const file of files) {
      const tempId = `pending-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
      const pending = {
        id: tempId,
        name: file.name,
        kind: "pdf",
        size: `${Math.max(1, Math.round(file.size / 1024))} KB`,
        chunks: 0,
        status: "indexing",
        modelId: activeAgent?.modelId || null
      };
      setSources(prev => [pending, ...prev]);

      try {
        const indexedFile = await window.ModelOSIntegrations.uploadAndIndexPdf(file, ({ agent }) => {
          if (agent?.modelId) {
            ctx.setActiveAgent(agent);
            window.ModelOSUserStorage.save(authUser.uid, "activeAgent", agent);
          }
        }, authUser);
        setSources(prev => prev.map(item => item.id === tempId
          ? { ...indexedFile, modelId: indexedFile.modelId || activeAgent?.modelId || indexedFile.agent?.modelId || null }
          : item));
        if (indexedFile.agent?.modelId) ctx.setActiveAgent(indexedFile.agent);
        setNotice({
          type: hasApiKey ? "success" : "info",
          text: hasApiKey
            ? `${file.name} indexed. Chatbot is ready to use this PDF.`
            : `${file.name} indexed. Add your API key to use RAG chat.`,
        });
      } catch (error) {
        setSources(prev => prev.filter(item => item.id !== tempId));
        setNotice({ type: "error", text: error.message || "Failed to index PDF." });
      }
    }

    setBusy(false);
  };
  const selectedIndexedItems = () => botSources.filter(s => selected.has(s.id) && s.status === "indexed" && s.fileId && s.raw?.uploadData?.storagePath);

  const confirmBuildSelectedBot = async () => {
    const items = selectedIndexedItems();
    const cleanName = botName.trim();
    if (!items.length || !cleanName) return;
    setBuildingBot(true);
    try {
      const agent = await window.ModelOSIntegrations.createAgent(cleanName, authUser);
      for (const s of items) {
        await window.ModelOSIntegrations.indexExistingFile(s.raw.uploadData.storagePath, s.name, agent.modelId, authUser);
      }
      ctx.setActiveAgent(agent);
      window.ModelOSUserStorage.save(authUser.uid, "activeAgent", agent);
      setSelected(new Set());
      setBotNameOpen(false);
      setNotice({ type: "success", text: `Bot "${agent.name}" created with ${items.length} PDF${items.length > 1 ? "s" : ""}.` });
    } catch (err) {
      setNotice({ type: "error", text: `Failed to build bot: ${err.message || "Unknown error"}` });
    } finally {
      setBuildingBot(false);
    }
  };
  const autoqa = window.useAutoQa({ modelId: activeAgent?.modelId, settings, authUser });

  const removeSourceLocally = (sourceId) => {
    setSources(prev => prev.filter(item => item.id !== sourceId));
    setSelected(prev => {
      const next = new Set(prev);
      next.delete(sourceId);
      return next;
    });
  };

  const confirmDeleteSource = async () => {
    const source = deleteTarget;
    if (!source) return;
    const hasBackendFile = source.fileId && !source.id?.startsWith("pending-");

    if (!hasBackendFile) {
      removeSourceLocally(source.id);
      setDeleteTarget(null);
      return;
    }

    try {
      setDeleting(source.id);
      const result = await window.ModelOSIntegrations.deleteFile(source.fileId, authUser);
      removeSourceLocally(source.id);
      setDeleteTarget(null);
      const cleanupWarning = Array.isArray(result?.cleanupErrors) && result.cleanupErrors.length
        ? " Some storage cleanup is still pending, but the source was removed from this bot."
        : "";
      setNotice({ type: cleanupWarning ? "info" : "success", text: `${source.name} removed from this bot.${cleanupWarning}` });
    } catch (err) {
      setNotice({ type: "error", text: `Failed to delete ${source.name}: ${err.message || "Unknown error"}` });
    } finally {
      setDeleting(null);
    }
  };

  const BUILD_STEPS = ["Parsing documents", "Chunking & cleaning", "Generating embeddings", "Building RAG index", "Spinning up test chatbot"];
  const startBuild = async () => {
    await saveAssistantSetup({ quiet: true });
    ctx.setModel(MODELS.find(m => m.id === model));
    setBuilding(0);
    let i = 0;
    const tick = () => {
      i++;
      if (i <= BUILD_STEPS.length) { setBuilding(i); setTimeout(tick, 720); }
      else setTimeout(() => ctx.setView("playground"), 500);
    };
    setTimeout(tick, 720);
  };

  if (building !== null) {
    return (
      <div className="page" style={{ maxWidth: 560 }}>
        <div className="card card-pad fu" style={{ marginTop: 40 }}>
          <div className="row" style={{ gap: 12, marginBottom: 22 }}>
            <div className="avatar" style={{ background: "var(--primary)", color: "var(--primary-ink)", width: 40, height: 40 }}>{I("spark", { style: { width: 20, height: 20 } })}</div>
            <div>
              <div className="h2">Building your knowledge base</div>
              <div className="caption">{totalChunks.toLocaleString()} chunks across {indexed.length} sources</div>
            </div>
          </div>
          <div className="stack" style={{ gap: 2 }}>
            {BUILD_STEPS.map((s, i) => {
              const state = i < building ? "done" : i === building ? "active" : "todo";
              return (
                <div key={s} className="row" style={{ gap: 12, padding: "11px 4px", opacity: state === "todo" ? .45 : 1, transition: "opacity .3s" }}>
                  <div style={{ width: 22, height: 22, flex: "none", display: "flex", alignItems: "center", justifyContent: "center" }}>
                    {state === "done" ? <span style={{ color: "var(--good)" }}>{I("checkCircle", { style: { width: 20, height: 20 } })}</span>
                      : state === "active" ? <span className="spin" style={{ color: "var(--primary)" }}>{I("refresh", { style: { width: 18, height: 18 } })}</span>
                      : <span className="dot" style={{ width: 8, height: 8, background: "var(--border-3)" }} />}
                  </div>
                  <span style={{ fontSize: 14, fontWeight: state === "active" ? 600 : 500 }}>{s}</span>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="page">
      <BotNameDialog open={botNameOpen} busy={buildingBot} name={botName} count={selected.size} onNameChange={setBotName} onCancel={() => !buildingBot && setBotNameOpen(false)} onConfirm={confirmBuildSelectedBot} />
      <window.AutoQaDialog state={autoqa.autoqa} hasKey={hasApiKey}
        providerLabel={settings.llmProvider}
        onClose={autoqa.close} onStart={autoqa.start} onStop={autoqa.stop}
        onDownload={autoqa.downloadJsonl} onOpenStudio={() => { autoqa.close(); ctx.setView("conversations"); }}
        onQuestionsPerChunkChange={autoqa.setQuestionsPerChunk} />
      <DeleteSourceDialog source={deleteTarget} busy={Boolean(deleting)} onCancel={() => !deleting && setDeleteTarget(null)} onConfirm={confirmDeleteSource} />
      <PageHead eyebrow="Step 1 of 3 - Setup"
        title="Bring in your business knowledge"
        sub="Create a RAG chatbot that answers from your business documents, captures customer questions, and helps generate higher-quality datasets for future fine-tuning." />

      <div style={{ display: "grid", gridTemplateColumns: "1.55fr 1fr", gap: 20, alignItems: "start" }} className="ob-grid">
        {/* left: sources */}
        <div className="stack fu fu-1" style={{ gap: 16 }}>
          <div className="dropzone" data-hot={hot ? "1" : "0"}
               onDragOver={(e) => { e.preventDefault(); if (isRealUser) setHot(true); }}
               onDragLeave={() => setHot(false)}
               onDrop={(e) => { e.preventDefault(); setHot(false); if (!isRealUser) { authSignIn(); return; } addFiles(e.dataTransfer.files); }}
               onClick={() => { if (!isRealUser) { authSignIn(); return; } fileRef.current?.click(); }}
               style={{ padding: "30px 24px", textAlign: "center", cursor: "pointer" }}>
            <input ref={fileRef} type="file" accept="application/pdf,.pdf" multiple hidden disabled={!isRealUser} onChange={(e) => { addFiles(e.target.files); e.target.value = ""; }} />
            <div className="stack" style={{ alignItems: "center", gap: 10 }}>
              <div style={{ width: 46, height: 46, borderRadius: 12, background: "var(--surface)", border: "1px solid var(--border-2)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-2)" }}>{I("upload", { style: { width: 22, height: 22 } })}</div>
              <div>
                <div style={{ fontSize: 14.5, fontWeight: 600 }}>{!isRealUser ? "Connect Google to upload PDF" : busy ? "Indexing PDF..." : "Drop PDF or click to upload"}</div>
                <div className="caption" style={{ marginTop: 3 }}>{!isRealUser ? "Google sign-in is required for uploads and indexing" : "PDF only - manuals, FAQs, pricing, policies - up to 50 MB each"}</div>
              </div>
            </div>
          </div>
          {notice && (
            <div className={cx("badge", notice.type === "error" ? "badge-bad" : notice.type === "success" ? "badge-good" : "badge-info")} style={{ alignSelf: "flex-start" }}>
              {I(notice.type === "error" ? "alert" : notice.type === "success" ? "checkCircle" : "refresh", { style: { width: 13, height: 13 } })}
              {notice.text}
            </div>
          )}
          <div className="card">
            <div className="card-hd between">
              <div className="row" style={{ gap: 8 }}>
                <span className="h3">Sources</span>
                <span className="badge badge-neutral">{botSources.length}</span>
              </div>
              <div className="row" style={{ gap: 10 }}>
                <span className="caption">{totalChunks.toLocaleString()} chunks indexed</span>
                <button className="btn btn-secondary btn-sm" disabled={selected.size === 0 || buildingBot} onClick={() => {
                  const items = selectedIndexedItems();
                  if (!items.length) return;
                  setBotName(activeAgent?.name ? `${activeAgent.name} copy` : "Product advisor");
                  setBotNameOpen(true);
                }}>
                  {I("plus", { style: { width: 13, height: 13 } })} {buildingBot ? "Building..." : `Build new bot from selected (${selected.size})`}
                </button>
              </div>
            </div>
            <div className="stack">
              {botSources.map((s, i) => {
                const canSelect = s.status === "indexed" && s.fileId && !s.id?.startsWith("pending-");
                return (
                <div key={s.id} className="row" style={{ gap: 12, padding: "12px 16px", borderBottom: i < botSources.length - 1 ? "1px solid var(--border)" : "none" }}>
                  {canSelect ? (
                    <input type="checkbox" checked={selected.has(s.id)} onChange={() => setSelected(prev => {
                      const next = new Set(prev);
                      next.has(s.id) ? next.delete(s.id) : next.add(s.id);
                      return next;
                    })} style={{ width: 16, height: 16, flex: "none", cursor: "pointer" }} />
                  ) : (
                    <div style={{ width: 16, flex: "none" }} />
                  )}
                  <FileGlyph kind={s.kind} />
                  <div className="grow" style={{ minWidth: 0 }}>
                    <div style={{ fontSize: 13.5, fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{s.name}</div>
                    <div className="caption">{s.size}{s.chunks ? ` - ${s.chunks} chunks` : ""}</div>
                  </div>
                  {s.status === "indexed"
                    ? <span className="badge badge-good">{I("check", { style: { width: 12, height: 12 } })} Indexed</span>
                    : s.status === "failed"
                      ? <span className="badge badge-bad">{I("alert", { style: { width: 12, height: 12 } })} Failed</span>
                      : <span className="badge badge-info"><span className="spin" style={{ display: "inline-flex" }}>{I("refresh", { style: { width: 12, height: 12 } })}</span> Indexing</span>}
                  {canSelect && activeAgent?.modelId && isRealUser && (
                    <button className="btn btn-secondary btn-sm" disabled={!!autoqa.autoqa} onClick={() => autoqa.openForFile(s)}
                            title="Generate question/answer training data from this document">
                      {I("spark", { style: { width: 12, height: 12 } })} Generate QA
                    </button>
                  )}
                  <button className="icon-btn" disabled={deleting === s.id} onClick={() => setDeleteTarget(s)} style={{ width: 28, height: 28, opacity: deleting === s.id ? 0.4 : 1 }}>{deleting === s.id ? <span className="spin" style={{ display: "inline-flex" }}>{I("refresh", { style: { width: 14, height: 14 } })}</span> : I("x", { style: { width: 14, height: 14 } })}</button>
                </div>
                );
              })}
            </div>
          </div>

          <div className="card card-pad">
            <window.ModelOSLlmApiKeyPanel settings={settings} setSettings={setSettings} activeAgent={activeAgent} authUser={authUser} />
          </div>
        </div>

        {/* right: model + cta */}
        <div className="stack fu fu-2" style={{ gap: 16, position: "sticky", top: 20 }}>

          <div className="card card-pad">
            <div className="stack" style={{ gap: 4, marginBottom: 14 }}>
              <span className="h3">Base model</span>
              <span className="caption">Powers your chatbot's reasoning. You can change this anytime.</span>
            </div>
            <div className="stack" style={{ gap: 10 }}>
              {MODELS.map(m => <ModelCard key={m.id} m={m} on={model === m.id} onClick={() => setModel(m.id)} />)}
            </div>

          </div>

          <button className="btn btn-primary btn-lg btn-block" onClick={startBuild} disabled={indexed.length === 0 || busy || !hasApiKey}>
            {I("spark")} Open test chatbot
          </button>
          <div className="caption" style={{ textAlign: "center" }}>
            {hasApiKey ? "Uploads create a bot and build its searchable knowledge index" : "Upload a PDF, then add your API key to enable RAG"}
          </div>
        </div>
      </div>

      <style>{`
        @media(max-width:900px){.ob-grid{grid-template-columns:minmax(0,1fr) !important}.ob-grid>*{min-width:0}.ob-grid .stack[style*="sticky"]{position:static !important}}
      `}</style>
    </div>
  );
}
window.OnboardingView = OnboardingView;
