import React, { useState, useEffect, useRef } from "react";
import { Header } from "./components/Header";
import { Sidebar } from "./components/Sidebar";
import { DocumentPane } from "./components/DocumentPane";
import { ChatPane } from "./components/ChatPane";
import { ActionBar } from "./components/ActionBar";
import { SettingsPage } from "./components/SettingsPage";
import { NewSessionModal } from "./components/NewSessionModal";
import type { Project, Session, Message, Phase, TokenUsage } from "./types";
import "./styles/globals.css";

const api = window.api;

/**
 * Derive a short session name (≤30 chars) from research.md content.
 * Looks for the first non-empty body line under ## Overview.
 * Falls back to the first non-heading line in the document.
 * Returns null if nothing usable is found.
 */
function extractSessionName(content: string): string | null {
  const truncate = (s: string) =>
    s.length > 30 ? s.slice(0, 28) + "\u2026" : s;

  // Primary: first sentence of ## Overview body
  const overviewMatch = content.match(/##\s+Overview\s*\n([\s\S]*?)(?=\n##|\n---)/);
  if (overviewMatch) {
    const firstLine = overviewMatch[1]
      .split("\n")
      .map((l) => l.trim())
      .find((l) => l.length > 0 && !l.startsWith("#"));
    if (firstLine) {
      const firstSentence = firstLine.split(/[.?!]/)[0].trim();
      if (firstSentence.length > 0) return truncate(firstSentence);
    }
  }

  // Fallback: first non-heading line anywhere in the document
  const firstLine = content
    .split("\n")
    .map((l) => l.trim())
    .find((l) => l.length > 0 && !l.startsWith("#"));
  if (!firstLine) return null;
  return truncate(firstLine);
}

type Theme = "dark" | "light";

export function App() {
  const [projects, setProjects] = useState<Project[]>([]);
  const [sessions, setSessions] = useState<Session[]>([]);
  const [selectedProject, setSelectedProject] = useState<Project | null>(null);
  const [selectedSession, setSelectedSession] = useState<Session | null>(null);
  const [messages, setMessages] = useState<Message[]>([]);
  const [documentContent, setDocumentContent] = useState<string>("");
  const [originalContent, setOriginalContent] = useState<string>("");
  // Per-session loading/activity state so switching sessions doesn't inherit
  // another session's "Thinking" indicator.
  const [loadingBySession, setLoadingBySession] = useState<Record<string, boolean>>({});
  const [activityBySession, setActivityBySession] = useState<Record<string, string | null>>({});

  const isLoading = selectedSession ? (loadingBySession[selectedSession.id] ?? false) : false;
  const activityStatus = selectedSession ? (activityBySession[selectedSession.id] ?? null) : null;

  const [tokenUsage, setTokenUsage] = useState<TokenUsage>({
    inputTokens: 0,
    outputTokens: 0,
  });
  const [error, setError] = useState<string | null>(null);
  const [showSettings, setShowSettings] = useState(false);
  const [showNewSessionModal, setShowNewSessionModal] = useState(false);
  const [newSessionProjectId, setNewSessionProjectId] = useState<string | null>(null);
  const [activeModel, setActiveModel] = useState<string | null>(null);

  const [theme, setTheme] = useState<Theme>(
    () => (localStorage.getItem("cf-theme") as Theme) ?? "dark"
  );

  // Which phase's artifact is currently displayed in the DocumentPane.
  // Defaults to the session's current phase; can be overridden to view
  // a historical artifact (e.g. research.md while in implement phase).
  const [viewPhase, setViewPhase] = useState<Phase>(
    (selectedSession?.phase ?? "research") as Phase
  );

  // Reset viewPhase whenever the active session or its phase changes.
  useEffect(() => {
    setViewPhase((selectedSession?.phase ?? "research") as Phase);
  }, [selectedSession?.id, selectedSession?.phase]);

  const isViewingHistorical =
    !!selectedSession && viewPhase !== (selectedSession.phase as Phase);

  // Refs so the global claude:message handler always sees the latest values
  // without needing to re-subscribe when the selected session changes.
  const selectedSessionRef = useRef(selectedSession);
  const selectedProjectRef = useRef(selectedProject);
  const viewPhaseRef = useRef(viewPhase);
  useEffect(() => { selectedSessionRef.current = selectedSession; }, [selectedSession]);
  useEffect(() => { selectedProjectRef.current = selectedProject; }, [selectedProject]);
  useEffect(() => { viewPhaseRef.current = viewPhase; }, [viewPhase]);

  const [chatWidth, setChatWidth] = useState<number>(
    () => Number(localStorage.getItem("cf-chat-width")) || 380
  );
  const [chatCollapsed, setChatCollapsed] = useState<boolean>(
    () => localStorage.getItem("cf-chat-collapsed") === "true"
  );
  const [sidebarWidth, setSidebarWidth] = useState<number>(
    () => Number(localStorage.getItem("cf-sidebar-width")) || 260
  );
  const [sidebarCollapsed, setSidebarCollapsed] = useState<boolean>(
    () => localStorage.getItem("cf-sidebar-collapsed") === "true"
  );

  // Keep document.documentElement in sync and persist to localStorage
  useEffect(() => {
    document.documentElement.setAttribute("data-theme", theme);
    localStorage.setItem("cf-theme", theme);
  }, [theme]);

  useEffect(() => {
    localStorage.setItem("cf-chat-width", String(chatWidth));
  }, [chatWidth]);

  useEffect(() => {
    localStorage.setItem("cf-chat-collapsed", String(chatCollapsed));
  }, [chatCollapsed]);

  useEffect(() => {
    localStorage.setItem("cf-sidebar-width", String(sidebarWidth));
  }, [sidebarWidth]);

  useEffect(() => {
    localStorage.setItem("cf-sidebar-collapsed", String(sidebarCollapsed));
  }, [sidebarCollapsed]);

  const handleCancel = () => {
    if (!selectedSession) return;
    const id = selectedSession.id;
    api.interruptSession(id);
    setLoadingBySession((prev) => ({ ...prev, [id]: false }));
    setActivityBySession((prev) => ({ ...prev, [id]: null }));
  };

  const handleToggleTheme = () =>
    setTheme((t) => (t === "dark" ? "light" : "dark"));

  const handleResizeMouseDown = (e: React.MouseEvent) => {
    e.preventDefault();
    const startX = e.clientX;
    const startWidth = chatWidth;
    const onMove = (ev: MouseEvent) => {
      const delta = startX - ev.clientX; // drag left = wider chat
      const next = Math.max(180, Math.min(700, startWidth + delta));
      setChatWidth(next);
    };
    const onUp = () => {
      document.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseup", onUp);
    };
    document.addEventListener("mousemove", onMove);
    document.addEventListener("mouseup", onUp);
  };

  const handleSidebarResizeMouseDown = (e: React.MouseEvent) => {
    e.preventDefault();
    const startX = e.clientX;
    const startWidth = sidebarWidth;
    const onMove = (ev: MouseEvent) => {
      const delta = ev.clientX - startX; // drag right = wider sidebar
      const next = Math.max(180, Math.min(400, startWidth + delta));
      setSidebarWidth(next);
    };
    const onUp = () => {
      document.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseup", onUp);
    };
    document.addEventListener("mousemove", onMove);
    document.addEventListener("mouseup", onUp);
  };

  const hasChanges = documentContent !== originalContent;

  // Clear error after 5 seconds
  useEffect(() => {
    if (error) {
      const timer = setTimeout(() => setError(null), 5000);
      return () => clearTimeout(timer);
    }
  }, [error]);

  // Keyboard shortcuts
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      // Escape to interrupt
      if (e.key === "Escape" && isLoading && selectedSession) {
        const id = selectedSession.id;
        api.interruptSession(id);
        setLoadingBySession((prev) => ({ ...prev, [id]: false }));
        setActivityBySession((prev) => ({ ...prev, [id]: null }));
      }
      // Cmd/Ctrl + Enter to submit
      if (
        e.key === "Enter" &&
        (e.metaKey || e.ctrlKey) &&
        selectedSession &&
        selectedSession.phase !== "implement" &&
        !isLoading
      ) {
        e.preventDefault();
        handleSubmit();
      }
    };
    window.addEventListener("keydown", handleKeyDown);
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, [selectedSession, isLoading]);

  // Load projects, all sessions, and initial model setting on mount.
  // Sessions for every project are fetched in parallel so the sidebar tree
  // can display all projects and their sessions immediately.
  useEffect(() => {
    (async () => {
      const loadedProjects = await api.listProjects();
      setProjects(loadedProjects);
      const arrays = await Promise.all(
        loadedProjects.map((p) => api.listSessions(p.id))
      );
      setSessions(arrays.flat());
      // Seed the model badge from the DB so it shows before any query fires.
      // system:init will overwrite this with the SDK-resolved name once a query runs.
      const s = await api.getSettings(["model"]);
      if (s["model"]) setActiveModel(s["model"]);
    })();
  }, []);

  // Load messages when session changes (not affected by viewPhase).
  useEffect(() => {
    if (selectedSession) {
      api.listMessages(selectedSession.id).then(setMessages);
    } else {
      setMessages([]);
    }
  }, [selectedSession?.id]);

  // Load the viewed artifact whenever the session or the viewed phase changes.
  useEffect(() => {
    if (selectedSession && selectedProject) {
      const filename = viewPhase === "research" ? "research.md" : "plan.md";
      api.readSessionArtifact(selectedProject.id, selectedSession.id, filename).then((content) => {
        const text = content || "";
        setDocumentContent(text);
        setOriginalContent(text);
      });
    } else {
      setDocumentContent("");
      setOriginalContent("");
    }
  }, [selectedSession?.id, selectedProject, viewPhase]);

  // Subscribe to Claude messages once globally.
  // Loading/activity state is updated for ANY session (via per-session maps).
  // Content state (messages, artifacts) is only updated for the currently
  // selected session, using refs to avoid re-subscribing on every session switch.
  useEffect(() => {
    const unsubscribe = api.onClaudeMessage((sessionId, msg) => {
      // ── Live activity indicator ──────────────────────────────────────
      if (msg.type === "tool_progress") {
        const elapsed = Math.round(msg.elapsed_time_seconds);
        setActivityBySession((prev) => ({
          ...prev,
          [sessionId]:
            elapsed > 0
              ? `Using ${msg.tool_name} (${elapsed}s)`
              : `Using ${msg.tool_name}…`,
        }));
      }

      if (
        msg.type === "system" &&
        msg.subtype === "task_progress" &&
        (msg as { last_tool_name?: string }).last_tool_name
      ) {
        setActivityBySession((prev) => ({
          ...prev,
          [sessionId]: `Using ${(msg as { last_tool_name: string }).last_tool_name}…`,
        }));
      }

      // ── Model resolved by SDK ────────────────────────────────────────
      if (msg.type === "system" && msg.subtype === "init") {
        setActiveModel((msg as { model?: string }).model ?? null);
      }

      // ── Result (success or error) ────────────────────────────────────
      // Clear loading state for the session that completed, regardless of
      // which session is currently selected.
      if (msg.type === "result") {
        setLoadingBySession((prev) => ({ ...prev, [sessionId]: false }));
        setActivityBySession((prev) => ({ ...prev, [sessionId]: null }));

        // Content updates only matter for the currently visible session.
        const currentSession = selectedSessionRef.current;
        const currentProject = selectedProjectRef.current;
        if (sessionId === currentSession?.id && msg.subtype === "success") {
          if (msg.usage) {
            setTokenUsage({
              inputTokens: msg.usage.input_tokens,
              outputTokens: msg.usage.output_tokens,
              cacheHits: msg.usage.cache_read_input_tokens,
            });
          }
          // Only reload the artifact if the user is viewing the current phase's
          // document. If they're browsing a historical artifact, leave it alone;
          // the effect tied to viewPhase will reload when they navigate back.
          if (
            currentProject &&
            currentSession &&
            viewPhaseRef.current === currentSession.phase
          ) {
            const filename =
              currentSession.phase === "research" ? "research.md" : "plan.md";
            const sessionAtTrigger = currentSession;
            api
              .readSessionArtifact(currentProject.id, sessionAtTrigger.id, filename)
              .then((content) => {
                const text = content || "";
                setDocumentContent(text);
                setOriginalContent(text);

                if (
                  sessionAtTrigger.phase === "research" &&
                  /^Session \d+$/.test(sessionAtTrigger.name) &&
                  text.length > 0
                ) {
                  const derived = extractSessionName(text);
                  if (derived) {
                    handleRenameSession(sessionAtTrigger.id, derived);
                  }
                }
              });
          }
        }
      }

      // ── Assistant message ────────────────────────────────────────────
      // Only update the chat pane for the currently visible session.
      if (msg.type === "assistant" && sessionId === selectedSessionRef.current?.id) {
        const content = (
          msg.message.content as Array<{ type: string; text?: string }>
        )
          .filter((c) => c.type === "text" && c.text)
          .map((c) => c.text!)
          .join("\n");

        if (content) {
          setMessages((prev) => {
            const last = prev[prev.length - 1];
            if (last?.role === "assistant") {
              return [...prev.slice(0, -1), { ...last, content }];
            }
            return [
              ...prev,
              {
                id: crypto.randomUUID(),
                session_id: sessionId,
                role: "assistant",
                content,
                created_at: Date.now() / 1000,
              },
            ];
          });
        }
      }
    });

    return unsubscribe;
  }, []); // Subscribe once — uses refs for current session/project

  const handleSendMessage = async (message: string) => {
    if (!selectedSession) return;
    const id = selectedSession.id;
    setLoadingBySession((prev) => ({ ...prev, [id]: true }));
    setError(null);
    setMessages((prev) => [
      ...prev,
      {
        id: crypto.randomUUID(),
        session_id: id,
        role: "user",
        content: message,
        created_at: Date.now() / 1000,
      },
    ]);
    try {
      await api.sendMessage(id, message);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to send message");
      setLoadingBySession((prev) => ({ ...prev, [id]: false }));
    }
  };

  const handleReview = async () => {
    if (!selectedSession || !selectedProject || isViewingHistorical) return;
    const id = selectedSession.id;
    setError(null);
    try {
      const filename =
        selectedSession.phase === "research" ? "research.md" : "plan.md";
      await api.writeSessionArtifact(selectedProject.id, id, filename, documentContent);
      setOriginalContent(documentContent);
      setLoadingBySession((prev) => ({ ...prev, [id]: true }));
      await api.triggerReview(id);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Review failed");
      setLoadingBySession((prev) => ({ ...prev, [id]: false }));
    }
  };

  const handleSubmit = async () => {
    if (!selectedSession || !selectedProject || isViewingHistorical) return;
    const id = selectedSession.id;
    setError(null);
    try {
      const filename =
        selectedSession.phase === "research" ? "research.md" : "plan.md";
      await api.writeSessionArtifact(selectedProject.id, id, filename, documentContent);

      const advanced = await api.advancePhase(id);
      if (advanced) {
        setSelectedSession({
          ...selectedSession,
          phase: advanced.phase,
          git_branch: advanced.git_branch,
        });
        // Keep sessions array in sync so Sidebar reflects the new phase immediately.
        setSessions((prev) =>
          prev.map((s) =>
            s.id === id
              ? { ...s, phase: advanced.phase, git_branch: advanced.git_branch }
              : s
          )
        );
        setLoadingBySession((prev) => ({ ...prev, [id]: true }));
        const initialMsg =
          advanced.phase === "plan"
            ? "Create a detailed implementation plan based on the research."
            : "Begin implementing the plan.";
        await api.sendMessage(id, initialMsg);
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : "Submit failed");
      setLoadingBySession((prev) => ({ ...prev, [id]: false }));
    }
  };

  const handleCreateProject = async () => {
    const path = await api.selectDirectory();
    if (!path) return;
    const name = path.split("/").pop() || "New Project";
    const project = await api.createProject(name, path);
    setProjects((prev) => [project, ...prev]);
    setSelectedProject(project);
  };

  // Called by Sidebar when user clicks "+" — opens the phase-selection modal
  const handleCreateSession = (projectId: string) => {
    setNewSessionProjectId(projectId);
    setShowNewSessionModal(true);
  };

  // Called by NewSessionModal when user clicks "Create"
  const handleConfirmNewSession = async (phase: Phase) => {
    setShowNewSessionModal(false);
    const projectId = newSessionProjectId;
    setNewSessionProjectId(null);
    if (!projectId) return;

    const project = projects.find((p) => p.id === projectId);
    if (!project) return;

    const projectSessions = sessions.filter((s) => s.project_id === projectId);
    const name = `Session ${projectSessions.length + 1}`;
    const session = await api.createSession(projectId, name, phase);
    setSessions((prev) => [session, ...prev]);
    setSelectedProject(project);
    setSelectedSession(session);
    setMessages([]);
    setDocumentContent("");
    setOriginalContent("");
    // User starts the conversation — no auto-send for manually-chosen phases.
  };

  const handleDeleteProject = async (id: string) => {
    try {
      await api.deleteProject(id);
      setProjects((prev) => prev.filter((p) => p.id !== id));
      setSessions((prev) => prev.filter((s) => s.project_id !== id));
      if (selectedProject?.id === id) {
        setSelectedProject(null);
        setSelectedSession(null);
        setMessages([]);
        setDocumentContent("");
        setOriginalContent("");
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to delete project");
    }
  };

  const handleDeleteSession = async (id: string) => {
    try {
      await api.deleteSession(id);
      setSessions((prev) => prev.filter((s) => s.id !== id));
      if (selectedSession?.id === id) {
        setSelectedSession(null);
        setMessages([]);
        setDocumentContent("");
        setOriginalContent("");
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to delete session");
    }
  };

  const handleRenameSession = async (id: string, name: string) => {
    await api.renameSession(id, name);
    setSessions((prev) =>
      prev.map((s) => (s.id === id ? { ...s, name } : s))
    );
    // Use functional updater to avoid stale-closure issues
    setSelectedSession((prev) =>
      prev?.id === id ? { ...prev, name } : prev
    );
  };

  return (
    <div className="app">
      <Header
        selectedSession={selectedSession}
        gitBranch={selectedSession?.git_branch ?? null}
        onOpenSettings={() => setShowSettings(true)}
        viewPhase={viewPhase}
        onViewPhase={setViewPhase}
      />

      <div className="main-layout">
        <Sidebar
          projects={projects}
          sessions={sessions}
          selectedProject={selectedProject}
          selectedSession={selectedSession}
          onSelectProject={(p) => {
            setSelectedProject(p);
            setSelectedSession(null);
          }}
          onSelectSession={(session) => {
            if (session) {
              const project = projects.find((p) => p.id === session.project_id);
              if (project) setSelectedProject(project);
            }
            setSelectedSession(session);
          }}
          onCreateProject={handleCreateProject}
          onCreateSession={handleCreateSession}
          onDeleteProject={handleDeleteProject}
          onDeleteSession={handleDeleteSession}
          onRenameSession={handleRenameSession}
          loadingBySession={loadingBySession}
          width={sidebarWidth}
          collapsed={sidebarCollapsed}
          onCollapsedChange={setSidebarCollapsed}
        />

        {!sidebarCollapsed && (
          <div
            className="sidebar-resize-handle"
            onMouseDown={handleSidebarResizeMouseDown}
          />
        )}

        <div className="main-content">
          <DocumentPane
            content={documentContent}
            onChange={setDocumentContent}
            phase={viewPhase}
            disabled={
              !selectedSession ||
              isViewingHistorical ||
              selectedSession.phase === "implement"
            }
            showOnboarding={!selectedProject}
            theme={theme}
          />

          {!chatCollapsed && (
            <div
              className="chat-resize-handle"
              onMouseDown={handleResizeMouseDown}
            />
          )}

          <ChatPane
            messages={messages}
            onSend={handleSendMessage}
            isLoading={isLoading}
            disabled={!selectedSession}
            placeholder={
              selectedSession
                ? `Chat with Claude (${selectedSession.phase})...`
                : "Select a session to start"
            }
            collapsed={chatCollapsed}
            chatWidth={chatWidth}
            onToggleCollapse={() => setChatCollapsed((c) => !c)}
            activityStatus={activityStatus}
            onCancel={handleCancel}
          />
        </div>
      </div>

      {error && (
        <div className="error-bar">
          <span>⚠️ {error}</span>
          <button onClick={() => setError(null)}>×</button>
        </div>
      )}

      <ActionBar
        phase={selectedSession?.phase || "research"}
        hasChanges={hasChanges}
        isLoading={isLoading}
        tokenUsage={tokenUsage}
        permissionMode={selectedSession?.permission_mode || "acceptEdits"}
        onReview={handleReview}
        onSubmit={handleSubmit}
        onPermissionModeChange={(mode) => {
          if (selectedSession) {
            api.setPermissionMode(selectedSession.id, mode);
            setSelectedSession({ ...selectedSession, permission_mode: mode });
          }
        }}
        disabled={!selectedSession}
        modelName={activeModel}
      />

      {showSettings && (
        <SettingsPage
          onClose={() => setShowSettings(false)}
          theme={theme}
          onToggleTheme={handleToggleTheme}
        />
      )}

      {showNewSessionModal && (
        <NewSessionModal
          onConfirm={handleConfirmNewSession}
          onCancel={() => {
            setShowNewSessionModal(false);
            setNewSessionProjectId(null);
          }}
        />
      )}
    </div>
  );
}
