import { useCallback, useEffect, useRef, useState } from "react";
import { Chatbot } from "./Chatbot";
import { Playbook } from "./Playbook";
import { Models } from "./Models";
import { Settings } from "./Settings";
import { Memory } from "./Memory";
import { Projects } from "./Projects";
import { Logs } from "./Logs";
import { MODULES } from "./modules/registry";

import { API_BASE } from "./config";

function timeAgo(epochSeconds) {
  const diff = Math.floor(Date.now() / 1000) - epochSeconds;
  if (diff < 60) return "just now";
  if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
  if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
  if (diff < 604800) return `${Math.floor(diff / 86400)}d ago`;
  return new Date(epochSeconds * 1000).toLocaleDateString();
}

function App() {
  const [currentPage, setCurrentPage] = useState("chatbot");
  const [status, setStatus] = useState("Checking Synapse...");
  const [version, setVersion] = useState("");
  const [ollamaStatus, setOllamaStatus] = useState("checking");
  const [ollamaBusy, setOllamaBusy] = useState(false);
  const [showStatusTooltip, setShowStatusTooltip] = useState(false);
  const [showModulesMenu, setShowModulesMenu] = useState(false);
  const [update, setUpdate] = useState(null);
  const [updateBusy, setUpdateBusy] = useState(false);
  const [updating, setUpdating] = useState(false);

  const [activeConversationId, setActiveConversationId] = useState(() => crypto.randomUUID());
  const [isModelPulling, setIsModelPulling] = useState(false);
  const [conversations, setConversations] = useState([]);
  const [search, setSearch] = useState("");
  const [hoveredConvId, setHoveredConvId] = useState(null);
  const searchDebounce = useRef(null);

  const loadStatus = () => {
    fetch(`${API_BASE}/status`)
      .then(r => r.json())
      .then(d => {
        setStatus(d.status ?? "Unknown");
        setVersion(d.version ?? "");
        setOllamaStatus(d.ollama ?? "unavailable");
      })
      .catch(() => {
        setStatus("Offline");
        setOllamaStatus("unavailable");
      });
  };

  // Update check — git fetch on the backend, so it is manual (and once at startup),
  // not polled with loadStatus.
  const checkUpdate = async () => {
    setUpdateBusy(true);
    try {
      const r = await fetch(`${API_BASE}/update/check`);
      setUpdate(await r.json());
    } catch {
      setUpdate({ error: "Backend unreachable" });
    }
    setUpdateBusy(false);
  };

  // Install the update: the backend spawns `ncp upgrade` detached and then gets
  // stopped by it, so this request is the last one this build answers. The
  // overlay effect below waits for the new build to come back.
  const applyUpdate = async () => {
    if (!confirm(`Install v${update?.remote_version} (${update?.behind} commit(s))?\n\n`
      + "NexusOS will pull, rebuild and restart. This takes a few minutes and "
      + "the page reloads itself when the new build is up.")) return;
    try {
      const r = await fetch(`${API_BASE}/update/apply`, { method: "POST" });
      const d = await r.json();
      if (!d.started) { alert(`Update could not start: ${d.error || "unknown error"}`); return; }
      setUpdating(true);
    } catch {
      alert("Update could not start: backend unreachable.");
    }
  };

  // Manual AI control — Ollama does not auto-start with the app.
  const toggleOllama = async () => {
    const action = ollamaStatus === "running" ? "stop" : "start";
    setOllamaBusy(true);
    try {
      await fetch(`${API_BASE}/ollama/${action}`, { method: "POST" });
    } catch (e) {
      console.error("Ollama toggle failed:", e);
    }
    setOllamaBusy(false);
    loadStatus();
  };

  const loadConversations = useCallback(async (q = "") => {
    try {
      const url = q.trim()
        ? `${API_BASE}/conversations?q=${encodeURIComponent(q.trim())}`
        : `${API_BASE}/conversations`;
      const response = await fetch(url);
      if (response.ok) {
        const data = await response.json();
        setConversations(data.conversations || []);
      }
    } catch (error) {
      console.error("Failed to load conversations:", error);
    }
  }, []);

  useEffect(() => {
    loadStatus();
    const interval = setInterval(loadStatus, 5000);
    return () => clearInterval(interval);
  }, []);

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

  // Reload only after the backend has actually gone away and come back - it
  // stays up for a few seconds after /update/apply returns, so a naive
  // "poll until online" would reload the OLD build immediately.
  useEffect(() => {
    if (!updating) return;
    let wentDown = false;
    const t = setInterval(async () => {
      try {
        const r = await fetch(`${API_BASE}/status`, { cache: "no-store" });
        if (!r.ok) throw new Error("not ok");
        if (wentDown) window.location.reload();
      } catch {
        wentDown = true;
      }
    }, 3000);
    return () => clearInterval(t);
  }, [updating]);

  useEffect(() => {
    let cancelled = false;
    fetch(`${API_BASE}/conversations`)
      .then(r => r.ok ? r.json() : null)
      .then(data => {
        if (cancelled || !data) return;
        setConversations(data.conversations || []);
      })
      .catch(err => console.error("Failed to load conversations:", err));
    return () => { cancelled = true; };
  }, []);

  const handleSearch = (e) => {
    const q = e.target.value;
    setSearch(q);
    clearTimeout(searchDebounce.current);
    searchDebounce.current = setTimeout(() => loadConversations(q), 300);
  };

  const selectConversation = (id) => {
    setActiveConversationId(id);
    setCurrentPage("chatbot");
  };

  const startNewChat = () => {
    setActiveConversationId(crypto.randomUUID());
    setCurrentPage("chatbot");
  };

  const renameConversation = async (e, conv) => {
    e.stopPropagation();
    const current = conv.title || conv.preview || "";
    const next = window.prompt("Rename conversation:", current);
    if (next == null) return;
    const title = next.trim();
    if (!title || title === current) return;
    try {
      const response = await fetch(`${API_BASE}/conversations/${conv.id}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ title }),
      });
      if (response.ok) loadConversations(search);
    } catch (error) {
      console.error("Failed to rename conversation:", error);
    }
  };

  // Download ShareGPT JSONL — one conversation if convId given, else all.
  const exportConversations = (e, convId) => {
    if (e) e.stopPropagation();
    const url = convId
      ? `${API_BASE}/conversations/export?conversation_id=${encodeURIComponent(convId)}`
      : `${API_BASE}/conversations/export`;
    window.open(url, "_blank");
  };

  const deleteConversation = async (e, conversationId) => {
    e.stopPropagation();
    if (!window.confirm("Delete this conversation?")) return;
    try {
      const response = await fetch(`${API_BASE}/conversations/${conversationId}`, { method: "DELETE" });
      if (response.ok) {
        if (activeConversationId === conversationId) {
          setActiveConversationId(crypto.randomUUID());
        }
        loadConversations(search);
      }
    } catch (error) {
      console.error("Failed to delete conversation:", error);
    }
  };

  // Bulk delete of everything currently listed (respects the search filter).
  const clearConversations = async () => {
    const n = conversations.length;
    if (!n) return;
    const what = search ? `${n} matching conversation${n > 1 ? "s" : ""}` : `all ${n} conversation${n > 1 ? "s" : ""}`;
    if (!window.confirm(`Delete ${what}? This cannot be undone.`)) return;
    const ids = conversations.map(c => c.id);
    await Promise.all(
      ids.map(id => fetch(`${API_BASE}/conversations/${id}`, { method: "DELETE" }).catch(err =>
        console.error("Failed to delete conversation:", err)))
    );
    if (ids.includes(activeConversationId)) setActiveConversationId(crypto.randomUUID());
    loadConversations(search);
  };

  const navItems = [
    { key: "chatbot", icon: "💬", label: "Chat" },
    { key: "playbook", icon: "📖", label: "Playbooks" },
    { key: "models", icon: "🤖", label: "Models", badge: isModelPulling },
    { key: "memory", icon: "🧠", label: "Memory" },
    { key: "projects", icon: "📁", label: "Projects" },
    { key: "modules", icon: "📦", label: "Modules", isModulesMenu: true },
    { key: "logs", icon: "📜", label: "Logs" },
    { key: "settings", icon: "⚙️", label: "Settings" },
  ];

  return (
    <div style={{ background: "#111", color: "#eee", height: "100vh", overflow: "hidden", fontFamily: "system-ui", display: "flex" }}>
      {updating && (
        <div style={{
          position: "fixed", inset: 0, zIndex: 5000, background: "rgba(0,0,0,0.88)",
          display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
          gap: "0.6rem", textAlign: "center", padding: "2rem",
        }}>
          <h2 style={{ color: "#007acc", margin: 0 }}>Updating NexusOS…</h2>
          <p style={{ color: "#aaa", margin: 0, maxWidth: "34rem" }}>
            Pulling, rebuilding and restarting. This page reloads itself when the
            new build is up — a few minutes is normal.
          </p>
          <p style={{ color: "#666", fontSize: "0.75rem", margin: 0 }}>
            Progress is logged to runtime/logs/update.log
          </p>
        </div>
      )}
      {/* Sidebar */}
      <aside style={{
        width: "400px",
        flexShrink: 0,
        background: "#0a0a0a",
        borderRight: "1px solid #333",
        display: "flex",
        flexDirection: "column",
        minHeight: 0,
      }}>
        {/* Top cell: brand + nav */}
        <div style={{
          padding: "0.75rem 0.6rem 0.65rem",
          borderBottom: "1px solid #1f1f1f",
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          gap: "0.4rem",
          flexShrink: 0,
        }}>
          <img src="/n small.png" alt="Logo" style={{ width: "36px", height: "36px", objectFit: "contain", borderRadius: "6px" }} />
          <h1 style={{ fontSize: "0.9rem", margin: 0, color: "#007acc", textAlign: "center" }}>NexusOS</h1>
          {version && (
            <span
              onClick={checkUpdate}
              title={updateBusy ? "Checking for updates..."
                : update?.error ? `Update check failed: ${update.error}`
                : update?.behind ? `${update.behind} commit(s) behind origin/main (${update.latest})`
                : "Up to date - click to check for updates"}
              style={{ fontSize: "0.65rem", color: "#666", marginTop: "-0.35rem", letterSpacing: "0.02em", cursor: "pointer" }}
            >v{version}</span>
          )}
          {update?.behind > 0 && !updating && (
            <span
              onClick={applyUpdate}
              title={`Update available: v${update.remote_version} (${update.behind} commit(s) behind).\nClick to install and restart.`}
              style={{
                fontSize: "0.6rem", color: "#ff9800", border: "1px solid #ff9800",
                borderRadius: "8px", padding: "0.05rem 0.4rem", cursor: "pointer",
              }}
            >update available</span>
          )}

          {/* Status Indicator */}
          <div style={{ position: "relative" }}>
            <div
              style={{
                width: "8px",
                height: "8px",
                borderRadius: "50%",
                background: status === "Offline" || ollamaStatus === "unavailable" ? "#f44336"
                  : ollamaStatus === "running" ? "#4caf50" : "#ff9800",
                cursor: (ollamaBusy || ollamaStatus === "unavailable" || status === "Offline") ? "not-allowed" : "pointer",
                transition: "all 0.3s"
              }}
              onMouseEnter={() => {
                loadStatus();
                setShowStatusTooltip(true);
              }}
              onMouseLeave={() => setShowStatusTooltip(false)}
              onClick={() => {
                if (ollamaBusy || ollamaStatus === "unavailable" || status === "Offline") return;
                toggleOllama();
              }}
              title={ollamaStatus === "unavailable" ? "Ollama binary not found" : "Click to start/stop the AI"}
            />
            {showStatusTooltip && (
              <div style={{
                position: "absolute",
                top: "16px",
                left: "50%",
                transform: "translateX(-50%)",
                background: "#1a1a1a",
                border: "1px solid #333",
                borderRadius: "6px",
                padding: "0.6rem 0.8rem",
                fontSize: "0.8rem",
                whiteSpace: "nowrap",
                zIndex: 1000,
                boxShadow: "0 4px 12px rgba(0,0,0,0.5)"
              }}>
                <div style={{ marginBottom: "0.3rem", color: status === "Offline" ? "#f44336" : "#4caf50" }}>
                  Synapse: <strong>{status}</strong>
                </div>
                <div style={{ color: ollamaStatus === "unavailable" ? "#f44336" : ollamaStatus === "running" ? "#4caf50" : "#aaa" }}>
                  {/* Naming the phase matters: nearly all of the wait is the model
                      being read off disk (GBs), not the server booting. */}
                  Ollama: <strong>{ollamaBusy ? (ollamaStatus === "running" ? "stopping…" : "loading model…") : ollamaStatus}</strong>
                </div>
              </div>
            )}
          </div>

          {/* Start/Stop AI moved onto the status dot above — click it to toggle. */}

          <nav style={{ display: "flex", flexDirection: "column", width: "100%", marginTop: "0.1rem" }}>
            {navItems.map(item => {
              const isActive = item.isModulesMenu
                ? MODULES.some(m => m.key === currentPage)
                : currentPage === item.key;
              const buttonStyle = {
                padding: "0.32rem 0.5rem",
                background: "transparent",
                color: isActive ? "#4aa3e0" : "#bbb",
                fontWeight: isActive ? 600 : 400,
                border: "none",
                borderLeft: `2px solid ${isActive ? "#007acc" : "transparent"}`,
                borderRadius: "5px",
                cursor: "pointer",
                fontSize: "0.8rem",
                textAlign: "left",
                transition: "background 0.12s, color 0.12s",
                display: "flex",
                alignItems: "center",
                gap: "0.45rem",
                width: "100%",
              };

              if (item.isModulesMenu) {
                return (
                  <div
                    key={item.key}
                    style={{ position: "relative" }}
                    onMouseEnter={() => setShowModulesMenu(true)}
                    onMouseLeave={() => setShowModulesMenu(false)}
                  >
                    <button
                      onClick={() => setShowModulesMenu(v => !v)}
                      style={buttonStyle}
                      onMouseEnter={(e) => { if (!isActive) e.currentTarget.style.background = "#161616"; }}
                      onMouseLeave={(e) => { if (!isActive) e.currentTarget.style.background = "transparent"; }}
                    >
                      <span style={{ fontSize: "0.85rem", width: "1rem", textAlign: "center", flexShrink: 0 }}>{item.icon}</span>
                      <span style={{ flexGrow: 1 }}>{item.label}</span>
                    </button>
                    {showModulesMenu && (
                      <div style={{
                        position: "absolute",
                        left: "100%",
                        top: 0,
                        marginLeft: "0.4rem",
                        background: "#1a1a1a",
                        border: "1px solid #333",
                        borderRadius: "6px",
                        padding: "0.35rem",
                        minWidth: "150px",
                        zIndex: 1000,
                        boxShadow: "0 4px 12px rgba(0,0,0,0.5)",
                      }}>
                        {MODULES.length === 0 ? (
                          <div style={{ padding: "0.35rem 0.5rem", color: "#666", fontSize: "0.78rem" }}>No modules installed.</div>
                        ) : MODULES.map(m => {
                          const moduleActive = currentPage === m.key;
                          return (
                            <div
                              key={m.key}
                              onClick={() => { setCurrentPage(m.key); setShowModulesMenu(false); }}
                              style={{
                                padding: "0.35rem 0.5rem",
                                borderRadius: "5px",
                                cursor: "pointer",
                                fontSize: "0.8rem",
                                whiteSpace: "nowrap",
                                display: "flex",
                                alignItems: "center",
                                gap: "0.45rem",
                                background: "transparent",
                                borderLeft: `2px solid ${moduleActive ? "#007acc" : "transparent"}`,
                                color: moduleActive ? "#4aa3e0" : "#bbb",
                                fontWeight: moduleActive ? 600 : 400,
                              }}
                            >
                              <span style={{ fontSize: "0.85rem", width: "1rem", textAlign: "center", flexShrink: 0 }}>{m.icon}</span>
                              <span>{m.label}</span>
                            </div>
                          );
                        })}
                      </div>
                    )}
                  </div>
                );
              }

              return (
                <button
                  key={item.key}
                  onClick={() => setCurrentPage(item.key)}
                  style={buttonStyle}
                  onMouseEnter={(e) => { if (!isActive) e.currentTarget.style.background = "#161616"; }}
                  onMouseLeave={(e) => { if (!isActive) e.currentTarget.style.background = "transparent"; }}
                >
                  <span style={{ fontSize: "0.85rem", width: "1rem", textAlign: "center", flexShrink: 0 }}>{item.icon}</span>
                  <span style={{ flexGrow: 1 }}>{item.label}</span>
                  {item.badge && (
                    <span style={{
                      width: "6px",
                      height: "6px",
                      borderRadius: "50%",
                      background: "#28a745",
                      flexShrink: 0,
                      animation: "pulse 1.2s ease-in-out infinite",
                    }} />
                  )}
                </button>
              );
            })}
          </nav>
        </div>

        {/* Bottom cell: conversation list */}
        <div style={{
          flexGrow: 1,
          minHeight: 0,
          display: "flex",
          flexDirection: "column",
          padding: "0.6rem 0.6rem 0.6rem",
        }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "0.5rem", flexShrink: 0 }}>
            <span style={{ fontSize: "0.75rem", color: "#888", textTransform: "uppercase", letterSpacing: "0.05em" }}>Chats</span>
            <div style={{ display: "flex", gap: "0.35rem" }}>
              <button
                onClick={() => exportConversations(null, null)}
                title="Export all conversations (ShareGPT JSONL)"
                disabled={conversations.length === 0}
                style={{
                  padding: "0.2rem 0.5rem",
                  background: "#161616",
                  color: conversations.length === 0 ? "#555" : "#bbb",
                  border: "1px solid #2a2a2a",
                  borderRadius: "6px",
                  cursor: conversations.length === 0 ? "default" : "pointer",
                  fontSize: "0.75rem",
                }}
              >
                ⤓ Export
              </button>
              <button
                onClick={clearConversations}
                title={search ? "Delete the conversations matching this search" : "Delete all conversations"}
                disabled={conversations.length === 0}
                style={{
                  padding: "0.2rem 0.5rem",
                  background: "#161616",
                  color: conversations.length === 0 ? "#555" : "#c66",
                  border: "1px solid #2a2a2a",
                  borderRadius: "6px",
                  cursor: conversations.length === 0 ? "default" : "pointer",
                  fontSize: "0.75rem",
                }}
              >
                🗑 Clear
              </button>
              <button
                onClick={startNewChat}
                title="New chat"
                style={{
                  padding: "0.2rem 0.55rem",
                  background: "#161616",
                  color: "#bbb",
                  border: "1px solid #2a2a2a",
                  borderRadius: "6px",
                  cursor: "pointer",
                  fontSize: "0.75rem",
                }}
              >
                + New
              </button>
            </div>
          </div>
          <input
            type="text"
            placeholder="Search..."
            value={search}
            onChange={handleSearch}
            style={{
              width: "100%",
              padding: "0.45rem 0.6rem",
              background: "#161616",
              color: "#eee",
              border: "1px solid #2a2a2a",
              borderRadius: "6px",
              marginBottom: "0.5rem",
              boxSizing: "border-box",
              fontSize: "0.8rem",
              flexShrink: 0,
            }}
          />
          <div style={{ flexGrow: 1, overflowY: "auto", minHeight: 0 }}>
            {conversations.length === 0 ? (
              <p style={{ color: "#555", fontSize: "0.8rem", textAlign: "center", marginTop: "0.5rem" }}>
                {search ? "No matches." : "No conversations yet."}
              </p>
            ) : (
              conversations.map((conv) => {
                const isActive = activeConversationId === conv.id;
                const isHover = hoveredConvId === conv.id;
                return (
                  <div
                    key={conv.id}
                    onClick={() => selectConversation(conv.id)}
                    onMouseEnter={() => setHoveredConvId(conv.id)}
                    onMouseLeave={() => setHoveredConvId(null)}
                    style={{
                      padding: "0.55rem 0.65rem",
                      marginBottom: "0.3rem",
                      borderRadius: "6px",
                      cursor: "pointer",
                      background: isHover ? "#161616" : "transparent",
                      border: "1px solid transparent",
                      borderLeft: `2px solid ${isActive ? "#007acc" : "transparent"}`,
                      display: "flex",
                      alignItems: "center",
                      gap: "0.4rem",
                    }}
                  >
                    <div style={{ flexGrow: 1, minWidth: 0 }}>
                      <div style={{
                        fontSize: "0.82rem",
                        color: isActive ? "#4aa3e0" : "#ccc",
                        fontWeight: isActive ? 600 : 400,
                        whiteSpace: "nowrap",
                        overflow: "hidden",
                        textOverflow: "ellipsis",
                      }}>
                        {conv.title || conv.preview || "Conversation"}
                      </div>
                      <div style={{ fontSize: "0.7rem", color: "#666", marginTop: "0.1rem" }}>
                        {timeAgo(conv.timestamp)}
                      </div>
                    </div>
                    {isHover && (
                      <>
                        <button
                          onClick={(e) => exportConversations(e, conv.id)}
                          title="Export this conversation"
                          style={{
                            padding: "0.15rem 0.4rem",
                            background: "transparent",
                            color: "#888",
                            border: "1px solid #333",
                            borderRadius: "4px",
                            cursor: "pointer",
                            fontSize: "0.7rem",
                            flexShrink: 0,
                          }}
                        >
                          📤
                        </button>
                        <button
                          onClick={(e) => renameConversation(e, conv)}
                          title="Rename"
                          style={{
                            padding: "0.15rem 0.4rem",
                            background: "transparent",
                            color: "#888",
                            border: "1px solid #333",
                            borderRadius: "4px",
                            cursor: "pointer",
                            fontSize: "0.7rem",
                            flexShrink: 0,
                          }}
                        >
                          ✎
                        </button>
                        <button
                          onClick={(e) => deleteConversation(e, conv.id)}
                          title="Delete"
                          style={{
                            padding: "0.15rem 0.4rem",
                            background: "transparent",
                            color: "#888",
                            border: "1px solid #333",
                            borderRadius: "4px",
                            cursor: "pointer",
                            fontSize: "0.7rem",
                            flexShrink: 0,
                          }}
                        >
                          ✕
                        </button>
                      </>
                    )}
                  </div>
                );
              })
            )}
          </div>
        </div>
      </aside>

      {/* Main Content */}
      <main style={{ flexGrow: 1, padding: "1.5rem", display: "flex", flexDirection: "column", overflowY: "auto", minHeight: 0, minWidth: 0 }}>
        {/* Models stays mounted so active downloads survive page navigation */}
        <div style={{
          display: currentPage === "models" ? "flex" : "none",
          flexDirection: "column",
          flexGrow: 1,
          minHeight: 0,
        }}>
          <Models onPullStateChange={setIsModelPulling} />
        </div>
        {/* Chatbot stays mounted so an in-flight reply survives page navigation */}
        <div style={{
          display: currentPage === "chatbot" ? "flex" : "none",
          flexDirection: "column",
          flexGrow: 1,
          minHeight: 0,
        }}>
          <Chatbot
            visible={currentPage === "chatbot"}
            conversationId={activeConversationId}
            setConversationId={setActiveConversationId}
            onConversationChanged={() => loadConversations(search)}
          />
        </div>
        {currentPage === "playbook" && <Playbook />}
        {currentPage === "memory" && <Memory />}
        {currentPage === "projects" && <Projects onOpenChat={selectConversation} onNewChat={startNewChat} />}
        {MODULES.map(m => currentPage === m.key && <m.Component key={m.key} />)}
        {currentPage === "logs" && <Logs />}
        {currentPage === "settings" && <Settings />}
      </main>
    </div>
  );
}

export default App;
