import { useEffect, useState } from "react";
import { API_BASE } from "./config";

// Project workspace: a project groups chats and RAG documents. The active
// project is persisted in settings, and chat scopes its document retrieval to
// it (see synapse/main.py chat_stream_endpoint). "" = unscoped / All.
const input = { padding: "0.9rem", background: "#222", color: "#eee", border: "1px solid #333", borderRadius: "10px", width: "100%", boxSizing: "border-box" };
const card = { padding: "0.8rem 1rem", background: "#1a1a1a", border: "1px solid #2a2a2a", borderRadius: "10px", display: "flex", justifyContent: "space-between", alignItems: "center", gap: "0.6rem" };
const btn = { padding: "0.55rem 1rem", background: "#007acc", color: "#fff", border: "none", borderRadius: "8px", cursor: "pointer" };
const ghost = { padding: "0.55rem 1rem", background: "#1a1a1a", color: "#ccc", border: "1px solid #333", borderRadius: "8px", cursor: "pointer" };
const danger = { padding: "0.5rem 0.9rem", background: "#2a1a1a", color: "#ff8a80", border: "1px solid #5a2a2a", borderRadius: "8px", cursor: "pointer", fontSize: "0.8rem" };

function Modal({ title, onClose, children }) {
  return (
    <div onClick={onClose}
      style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.6)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 100, padding: "2rem" }}>
      <div onClick={e => e.stopPropagation()}
        style={{ background: "#1a1a1a", border: "1px solid #333", borderRadius: "12px", maxWidth: "700px", width: "100%", maxHeight: "80vh", display: "flex", flexDirection: "column" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "1rem 1.25rem", borderBottom: "1px solid #2a2a2a" }}>
          <strong>{title}</strong>
          <button onClick={onClose} style={{ background: "none", border: "none", color: "#aaa", fontSize: "1.2rem", cursor: "pointer" }}>✕</button>
        </div>
        <div style={{ overflowY: "auto", padding: "1rem 1.25rem" }}>{children}</div>
      </div>
    </div>
  );
}

export function Projects({ onOpenChat, onNewChat }) {
  const [docs, setDocs] = useState([]);
  const [chats, setChats] = useState([]);
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const [busy, setBusy] = useState(false);
  const [message, setMessage] = useState("");
  const [viewing, setViewing] = useState(null);   // {title, chunks} preview
  const [uploading, setUploading] = useState(false); // upload modal open
  const [picking, setPicking] = useState(null);   // conversations available to add
  const [projects, setProjects] = useState([]);
  const [activeProject, setActiveProject] = useState(""); // "" = All
  const [instructions, setInstructions] = useState("");   // per-project system prompt
  const [savedInstructions, setSavedInstructions] = useState("");
  const [facts, setFacts] = useState([]);                 // memory scoped to this project
  const [newFact, setNewFact] = useState("");
  const [dragging, setDragging] = useState(false);        // file hovering the drop zone

  const loadProjects = async () => {
    try {
      const r = await fetch(`${API_BASE}/projects`);
      if (!r.ok) return;
      const d = await r.json();
      setProjects(d.projects || []);
      setActiveProject(d.active || "");
      const mine = (d.projects || []).find(p => p.id === (d.active || ""));
      setInstructions(mine?.instructions || "");
      setSavedInstructions(mine?.instructions || "");
    } catch { /* offline */ }
  };

  const load = async (pid) => {
    const scope = pid ?? activeProject;
    if (!scope) { setDocs([]); setChats([]); setFacts([]); return; }
    try {
      const r = await fetch(`${API_BASE}/documents`);
      if (r.ok) setDocs((await r.json()).documents || []);
      const c = await fetch(`${API_BASE}/conversations?project=${encodeURIComponent(scope)}`);
      if (c.ok) setChats((await c.json()).conversations || []);
      const m = await fetch(`${API_BASE}/memory?project=${encodeURIComponent(scope)}`);
      if (m.ok) setFacts((await m.json()).items || []);
    } catch { /* offline — leave lists as-is */ }
  };

  useEffect(() => { loadProjects().then(() => {}); }, []);
  useEffect(() => { load(activeProject); }, [activeProject]); // eslint-disable-line react-hooks/exhaustive-deps

  // Switch the active workspace (persisted in settings; scopes chat RAG and
  // any new conversation started from here).
  const setActive = async (pid) => {
    await fetch(`${API_BASE}/settings`, {
      method: "PUT", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ active_project: pid }),
    });
    setActiveProject(pid);
    loadProjects();
  };

  const newProject = async () => {
    const name = window.prompt("New project name:");
    if (!name || !name.trim()) return;
    const r = await fetch(`${API_BASE}/projects`, {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name: name.trim() }),
    });
    if (r.ok) { const p = await r.json(); await setActive(p.id); }
  };

  const removeProject = async () => {
    if (!activeProject) return;
    if (!window.confirm("Delete this project? Its chats and documents are kept but become unscoped.")) return;
    await fetch(`${API_BASE}/projects/${encodeURIComponent(activeProject)}`, { method: "DELETE" });
    await setActive("");
  };

  // --- instructions --------------------------------------------------------
  const saveInstructions = async () => {
    await fetch(`${API_BASE}/projects/${encodeURIComponent(activeProject)}`, {
      method: "PATCH", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ instructions }),
    });
    setSavedInstructions(instructions);
    loadProjects();
  };

  // --- memory facts scoped to this project ---------------------------------
  const addFact = async () => {
    const text = newFact.trim();
    if (!text) return;
    await fetch(`${API_BASE}/memory`, {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ text, section: "Projects", project_id: activeProject }),
    });
    setNewFact("");
    load();
  };

  const removeFact = async (id) => {
    await fetch(`${API_BASE}/memory/${encodeURIComponent(id)}`, { method: "DELETE" });
    load();
  };

  // --- chats ---------------------------------------------------------------
  const moveChat = async (id, pid) => {
    await fetch(`${API_BASE}/conversations/${encodeURIComponent(id)}`, {
      method: "PATCH", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ project_id: pid }),
    });
    load();
  };

  const openPicker = async () => {
    const r = await fetch(`${API_BASE}/conversations`);
    if (r.ok) setPicking(((await r.json()).conversations || []).filter(c => c.project_id !== activeProject));
  };

  const addChat = async (id) => {
    await moveChat(id, activeProject);
    setPicking(p => (p || []).filter(c => c.id !== id));
  };

  // --- documents -----------------------------------------------------------
  const openDoc = async (doc) => {
    try {
      const r = await fetch(`${API_BASE}/documents/${encodeURIComponent(doc.doc_id)}`);
      if (r.ok) setViewing({ title: doc.title, chunks: (await r.json()).chunks || [] });
    } catch { /* ignore */ }
  };

  // Files (pdf/docx/txt/md) upload straight to the server, which extracts the
  // text. Base64 in JSON — no multipart dependency.
  const onFile = (e) => {
    const file = e.target.files?.[0];
    e.target.value = ""; // allow re-selecting the same file
    if (file) uploadFile(file);
  };

  // Resolves when the file is indexed, so a multi-file drop uploads in order
  // instead of racing (each one re-renders the list on completion).
  const uploadFile = (file) => new Promise((resolve) => {
    setBusy(true);
    setMessage(`Reading ${file.name}…`);
    const reader = new FileReader();
    reader.onload = async () => {
      const b64 = String(reader.result || "").split(",")[1];
      try {
        const r = await fetch(`${API_BASE}/documents/upload`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ filename: file.name, data: b64 }),
        });
        if (r.ok) {
          const d = await r.json();
          setMessage(`Indexed "${d.title}" (${d.chunks} chunk${d.chunks === 1 ? "" : "s"}).`);
          load();
        } else {
          setMessage((await r.json().catch(() => ({}))).detail || "Failed to index file.");
        }
      } catch (err) {
        setMessage(`Error: ${err.message}`);
      } finally {
        setBusy(false);
        resolve();
      }
    };
    reader.readAsDataURL(file);
  });

  const onDrop = async (e) => {
    e.preventDefault();
    setDragging(false);
    if (!activeProject) return;
    const files = [...(e.dataTransfer?.files || [])];
    if (!files.length) return;
    setUploading(true);
    for (const f of files) await uploadFile(f);
  };

  const addDoc = async () => {
    if (!title.trim() || !content.trim()) {
      setMessage("Title and content are required.");
      return;
    }
    setBusy(true);
    setMessage("Chunking and embedding…");
    try {
      const r = await fetch(`${API_BASE}/documents`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ title: title.trim(), content }),
      });
      if (r.ok) {
        const d = await r.json();
        setMessage(`Indexed "${d.title}" (${d.chunks} chunk${d.chunks === 1 ? "" : "s"}).`);
        setTitle("");
        setContent("");
        load();
      } else {
        setMessage((await r.json().catch(() => ({}))).detail || "Failed to index.");
      }
    } catch (err) {
      setMessage(`Error: ${err.message}`);
    } finally {
      setBusy(false);
    }
  };

  const removeDoc = async (docId) => {
    try {
      await fetch(`${API_BASE}/documents/${encodeURIComponent(docId)}`, { method: "DELETE" });
      load();
    } catch { /* ignore */ }
  };

  const label = (c) => c.title || c.preview || "Untitled chat";

  return (
    <div
      onDragOver={e => { e.preventDefault(); if (activeProject) setDragging(true); }}
      onDragLeave={e => { if (e.currentTarget === e.target) setDragging(false); }}
      onDrop={onDrop}
      style={{ padding: "1.5rem", color: "#eee", maxWidth: "820px", position: "relative",
               outline: dragging ? "2px dashed #007acc" : "none", borderRadius: "12px" }}
    >
      {!activeProject ? (
        <>
          <div style={{ display: "flex", alignItems: "center", gap: "0.6rem", marginBottom: "1rem" }}>
            <h2 style={{ margin: 0, flex: 1 }}>📁 Projects</h2>
            <button onClick={newProject} style={btn}>＋ New project</button>
          </div>
          {projects.length === 0 ? (
            <div style={{ color: "#777" }}>
              No projects. A project keeps its own chats, documents, memory and instructions.
            </div>
          ) : (
            <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
              {projects.map(p => (
                <div key={p.id} onClick={() => setActive(p.id)} style={{ ...card, cursor: "pointer" }} title="Open project">
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontWeight: 600 }}>{p.name}</div>
                    <div style={{ color: "#888", fontSize: "0.85rem" }}>
                      {p.chats} chat{p.chats === 1 ? "" : "s"} · {p.docs} document{p.docs === 1 ? "" : "s"}
                    </div>
                  </div>
                  <span style={{ color: "#555" }}>›</span>
                </div>
              ))}
            </div>
          )}
        </>
      ) : (
        <>
      <div style={{ display: "flex", alignItems: "center", gap: "0.6rem", marginBottom: "1.4rem" }}>
        <button onClick={() => setActive("")} title="Back to all projects" style={ghost}>‹ Projects</button>
        <h2 style={{ margin: 0, flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
          {projects.find(p => p.id === activeProject)?.name || "Project"}
        </h2>
        <button onClick={removeProject} title="Delete this project (chats and documents kept)" style={danger}>
          Delete project
        </button>
      </div>

      {dragging && (
        <div style={{ position: "sticky", top: 0, zIndex: 5, background: "#0d2233", border: "1px solid #007acc",
                      borderRadius: "10px", padding: "0.7rem 1rem", marginBottom: "0.8rem", color: "#8ab4ff" }}>
          Drop files to index them in this project.
        </div>
      )}

      <div style={{ marginBottom: "1.5rem" }}>
          <h3 style={{ margin: "0 0 0.5rem", fontSize: "1rem" }}>How the assistant should act here</h3>
          <textarea rows={4} value={instructions} onChange={e => setInstructions(e.target.value)}
            placeholder="e.g. Answer as a build engineer. Prefer bash over Python. Always cite the doc you used."
            style={input} />
          <div style={{ display: "flex", alignItems: "center", gap: "0.6rem", marginTop: "0.5rem" }}>
            <button onClick={saveInstructions} disabled={instructions === savedInstructions}
              style={{ ...btn, background: instructions === savedInstructions ? "#333" : "#007acc",
                       cursor: instructions === savedInstructions ? "default" : "pointer" }}>
              Save instructions
            </button>
            <span style={{ color: "#666", fontSize: "0.78rem" }}>
              Layered under the active playbook, for chats in this project only.
            </span>
          </div>
      </div>

      <div style={{ display: "flex", alignItems: "center", gap: "0.6rem", marginBottom: "0.7rem" }}>
        <h3 style={{ margin: 0, flex: 1, fontSize: "1rem" }}>Chats</h3>
        <button onClick={onNewChat} style={btn}>＋ New chat</button>
        <button onClick={openPicker} style={ghost}>Add existing…</button>
      </div>
      {chats.length === 0 ? (
        <div style={{ color: "#777", marginBottom: "1.5rem" }}>No chats in this project yet.</div>
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem", marginBottom: "1.5rem" }}>
          {chats.map(c => (
            <div key={c.id} style={card}>
              <div onClick={() => onOpenChat(c.id)} style={{ cursor: "pointer", flex: 1, minWidth: 0 }} title="Open in chat">
                <div style={{ fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{label(c)}</div>
                <div style={{ color: "#888", fontSize: "0.85rem" }}>{new Date(c.updated_at * 1000).toLocaleString()}</div>
              </div>
              <button onClick={() => moveChat(c.id, "")} title="Remove from project (chat kept)" style={danger}>Remove</button>
            </div>
          ))}
        </div>
      )}

      <div style={{ marginBottom: "1.5rem" }}>
          <h3 style={{ margin: "0 0 0.5rem", fontSize: "1rem" }}>Project memory</h3>
          <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
            {facts.map(f => (
              <div key={f.id} style={card}>
                <span style={{ flex: 1, minWidth: 0, color: "#ddd", fontSize: "0.9rem" }}>{f.text}</span>
                <button onClick={() => removeFact(f.id)} style={danger}>Delete</button>
              </div>
            ))}
            <div style={{ display: "flex", gap: "0.6rem" }}>
              <input type="text" value={newFact} onChange={e => setNewFact(e.target.value)}
                onKeyDown={e => e.key === "Enter" && addFact()}
                placeholder="Fact the assistant should remember in this project only" style={input} />
              <button onClick={addFact} style={btn}>Add</button>
            </div>
          </div>
          <span style={{ color: "#666", fontSize: "0.78rem" }}>
            Facts learned in this project's chats land here. Global facts stay on the Memory page.
          </span>
      </div>

      <div style={{ display: "flex", alignItems: "center", gap: "0.6rem", marginBottom: "0.7rem" }}>
        <h3 style={{ margin: 0, flex: 1, fontSize: "1rem" }}>Documents</h3>
        <button onClick={() => { setMessage(""); setUploading(true); }} style={btn}>＋ Add documents</button>
      </div>
      {docs.length === 0 ? (
        <div style={{ color: "#777" }}>No documents yet.</div>
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
          {docs.map(d => (
            <div key={d.doc_id} style={card}>
              <div onClick={() => openDoc(d)} style={{ cursor: "pointer", flex: 1 }} title="View chunks">
                <div style={{ fontWeight: 600 }}>{d.title}</div>
                <div style={{ color: "#888", fontSize: "0.85rem" }}>{d.chunks} chunk{d.chunks === 1 ? "" : "s"}</div>
              </div>
              <button onClick={() => removeDoc(d.doc_id)} style={danger}>Delete</button>
            </div>
          ))}
        </div>
      )}

        </>
      )}

      {uploading && (
        <Modal title="Add documents to this project" onClose={() => setUploading(false)}>
          <div style={{ display: "flex", flexDirection: "column", gap: "0.7rem" }}>
            <input type="text" placeholder="Title" value={title}
              onChange={e => setTitle(e.target.value)} style={input} />
            <textarea rows={8} placeholder="Paste text here, or drop / pick a .pdf/.docx/.txt/.md file"
              value={content} onChange={e => setContent(e.target.value)} style={input} />
            <div style={{ display: "flex", gap: "0.7rem", alignItems: "center" }}>
              <input type="file" accept=".pdf,.docx,.txt,.md,.markdown,text/*" onChange={onFile} disabled={busy}
                title="Upload a file — text extracted server-side"
                style={{ color: "#aaa", flex: 1 }} />
              <button onClick={addDoc} disabled={busy}
                style={{ ...btn, padding: "0.9rem 1.5rem", background: busy ? "#555" : "#007acc", cursor: busy ? "default" : "pointer" }}>
                {busy ? "Indexing…" : "Add document"}
              </button>
            </div>
            {message && <div style={{ color: "#8ab4ff" }}>{message}</div>}
          </div>
        </Modal>
      )}

      {picking && (
        <Modal title="Add an existing chat" onClose={() => setPicking(null)}>
          {picking.length === 0 ? (
            <div style={{ color: "#777" }}>No other chats.</div>
          ) : (
            <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
              {picking.map(c => (
                <div key={c.id} style={card}>
                  <div style={{ flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{label(c)}</div>
                  <button onClick={() => addChat(c.id)} style={ghost}>Add</button>
                </div>
              ))}
            </div>
          )}
        </Modal>
      )}

      {viewing && (
        <Modal title={viewing.title} onClose={() => setViewing(null)}>
          {viewing.chunks.map(c => (
            <div key={c.chunk_idx} style={{ marginBottom: "1rem" }}>
              <div style={{ color: "#666", fontSize: "0.75rem", marginBottom: "0.3rem" }}>chunk {c.chunk_idx + 1}</div>
              <div style={{ whiteSpace: "pre-wrap", color: "#ddd", fontSize: "0.9rem", background: "#141414", border: "1px solid #262626", borderRadius: "8px", padding: "0.7rem" }}>{c.text}</div>
            </div>
          ))}
        </Modal>
      )}
    </div>
  );
}
