import { useState, useCallback, useEffect, useRef, useMemo, type MouseEvent } from "react";
import { useParams, useNavigate } from "react-router";
import { Masthead } from "../components/Masthead";
import { TaskList } from "../components/mesh/TaskList";
import { TaskDetail } from "../components/mesh/TaskDetail";
import { TaskOutput } from "../components/mesh/TaskOutput";
import { UnifiedMeshChatInput } from "../components/mesh/UnifiedMeshChatInput";
import { useTasks } from "../hooks/useTasks";
import { useTaskSubscription, type TaskUpdateEvent, type TaskOutputEvent } from "../hooks/useTaskSubscription";
import type { TaskWithSubtasks, MeshChatContext } from "../lib/api";
import { startTask as startTaskApi, stopTask as stopTaskApi, getTaskOutput } from "../lib/api";

// View modes for the task detail page
type ViewMode = "split" | "task" | "output";

// Minimum panel widths (in pixels)
const MIN_TASK_WIDTH = 300;
const MIN_OUTPUT_WIDTH = 200;

// TODO: Store task output in database for resuming from any device.
// Currently only persisted in localStorage which is device-specific.

// LocalStorage key prefix for task output
const STORAGE_KEY_PREFIX_OUTPUT = "makima-task-output-";

// Load persisted output from localStorage with deduplication
function loadPersistedOutput(taskId: string): TaskOutputEvent[] {
  try {
    const stored = localStorage.getItem(STORAGE_KEY_PREFIX_OUTPUT + taskId);
    if (!stored) return [];
    const entries = JSON.parse(stored) as TaskOutputEvent[];

    // Deduplicate consecutive identical entries (cleanup from previous bug)
    const deduplicated: TaskOutputEvent[] = [];
    for (const entry of entries) {
      const last = deduplicated[deduplicated.length - 1];
      if (
        !last ||
        last.messageType !== entry.messageType ||
        last.content !== entry.content ||
        last.toolName !== entry.toolName
      ) {
        deduplicated.push(entry);
      }
    }

    // Save cleaned up version if we removed duplicates
    if (deduplicated.length !== entries.length) {
      savePersistedOutput(taskId, deduplicated);
    }

    return deduplicated;
  } catch {
    return [];
  }
}

// Save output to localStorage
function savePersistedOutput(taskId: string, entries: TaskOutputEvent[]): void {
  try {
    localStorage.setItem(STORAGE_KEY_PREFIX_OUTPUT + taskId, JSON.stringify(entries));
  } catch {
    // Ignore storage errors
  }
}

// Clear output from localStorage
function clearPersistedOutput(taskId: string): void {
  try {
    localStorage.removeItem(STORAGE_KEY_PREFIX_OUTPUT + taskId);
  } catch {
    // Ignore storage errors
  }
}

export default function MeshPage() {
  const { id } = useParams<{ id: string }>();
  const navigate = useNavigate();
  const { tasks, loading, error, conflict, clearConflict, fetchTask, fetchTasks, editTask, removeTask, saveTask } = useTasks();
  const [taskDetail, setTaskDetail] = useState<TaskWithSubtasks | null>(null);
  const [detailLoading, setDetailLoading] = useState(false);
  const [creating, setCreating] = useState(false);
  const [taskOutputEntries, setTaskOutputEntries] = useState<TaskOutputEvent[]>([]);
  const [isStreaming, setIsStreaming] = useState(false);
  // Track which subtask's output we're viewing (null = parent task)
  const [viewingSubtaskId, setViewingSubtaskId] = useState<string | null>(null);
  const [viewingSubtaskName, setViewingSubtaskName] = useState<string | null>(null);
  // View mode for the split panel layout
  const [viewMode, setViewMode] = useState<ViewMode>("split");
  // Width of the task panel as a percentage (0-100)
  const [taskPanelPercent, setTaskPanelPercent] = useState(66.67);
  // Track resizing state
  const [isResizing, setIsResizing] = useState(false);
  const containerRef = useRef<HTMLDivElement>(null);
  // Track which task we've loaded output for to avoid stale saves
  const loadedTaskIdRef = useRef<string | null>(null);

  // Handle task update events from WebSocket
  const handleTaskUpdate = useCallback(async (event: TaskUpdateEvent) => {
    // Refresh task list if we're viewing the list
    if (!id) {
      fetchTasks();
      return;
    }

    // Check if this update is for the current task or one of its subtasks
    const isCurrentTask = event.taskId === id;
    const isSubtask = taskDetail?.subtasks.some((st) => st.id === event.taskId);

    // Refresh task detail if the update is for current task or any subtask
    // This ensures subtask status changes (e.g., when orchestrator starts them) are reflected
    if (isCurrentTask || isSubtask) {
      const updated = await fetchTask(id);
      if (updated) {
        setTaskDetail(updated);
      }
    }

    // Update streaming state based on status for current task
    if (isCurrentTask) {
      setIsStreaming(event.status === "running");
    }
  }, [id, fetchTask, fetchTasks, taskDetail?.subtasks]);

  // The task ID whose output we're currently viewing
  const activeOutputTaskId = viewingSubtaskId || id;

  // Handle task output events from WebSocket
  const handleTaskOutput = useCallback((event: TaskOutputEvent) => {
    // Only process output for the task we're currently viewing
    if (event.taskId === activeOutputTaskId) {
      setTaskOutputEntries((prev) => {
        // Deduplicate by checking if last entry is identical
        // This prevents duplicates from React StrictMode or WebSocket reconnects
        const lastEntry = prev[prev.length - 1];
        if (
          lastEntry &&
          lastEntry.messageType === event.messageType &&
          lastEntry.content === event.content &&
          lastEntry.toolName === event.toolName
        ) {
          return prev; // Skip duplicate
        }
        const newEntries = [...prev, event];
        // Persist to localStorage
        savePersistedOutput(event.taskId, newEntries);
        return newEntries;
      });
    }
  }, [activeOutputTaskId]);

  // Handle user input sent to task - show immediately in output
  const handleUserInput = useCallback((message: string) => {
    if (!activeOutputTaskId) return;
    const userEntry: TaskOutputEvent = {
      taskId: activeOutputTaskId,
      messageType: "user_input",
      content: message,
      isPartial: false,
    };
    setTaskOutputEntries((prev) => {
      const newEntries = [...prev, userEntry];
      savePersistedOutput(activeOutputTaskId, newEntries);
      return newEntries;
    });
  }, [activeOutputTaskId]);

  // Subscribe to task updates and output
  // When viewing a subtask's output, subscribe to that instead of the parent
  // Always subscribe to all updates so we see subtask status changes
  const { connected } = useTaskSubscription({
    taskId: id || null,
    subscribeAll: true, // Always subscribe to all - needed to see subtask updates
    subscribeOutput: !!activeOutputTaskId, // Subscribe to output when viewing a task
    outputTaskId: activeOutputTaskId || undefined, // Which task's output to subscribe to
    onUpdate: handleTaskUpdate,
    onOutput: handleTaskOutput,
  });

  // Load persisted output when task or viewed subtask changes
  useEffect(() => {
    if (activeOutputTaskId) {
      // First load from localStorage (instant, for local cache)
      const persisted = loadPersistedOutput(activeOutputTaskId);
      setTaskOutputEntries(persisted);
      loadedTaskIdRef.current = activeOutputTaskId;

      // Then fetch from API to get any output we missed
      // (e.g., subtask was running before we started viewing it)
      getTaskOutput(activeOutputTaskId)
        .then((response) => {
          if (response.entries.length > 0) {
            setTaskOutputEntries((prev) => {
              // API returns all historical entries in chronological order
              const apiEntries = response.entries.map(entry => ({
                taskId: entry.taskId,
                messageType: entry.messageType,
                content: entry.content,
                toolName: entry.toolName,
                toolInput: entry.toolInput,
                isError: entry.isError,
                costUsd: entry.costUsd,
                durationMs: entry.durationMs,
                isPartial: false,
              }));

              // If localStorage is empty, just use API data
              if (prev.length === 0) {
                savePersistedOutput(activeOutputTaskId, apiEntries);
                return apiEntries;
              }

              // localStorage has user_input entries in correct positions - trust its order
              // Only append API entries that we don't already have locally
              const localKeys = new Set(prev.map(e => `${e.messageType}:${e.content}`));
              const newFromApi = apiEntries.filter(e => !localKeys.has(`${e.messageType}:${e.content}`));

              // Keep local order (has user_input in correct spots), append new API data
              const merged = [...prev, ...newFromApi];
              savePersistedOutput(activeOutputTaskId, merged);
              return merged;
            });
          }
        })
        .catch((err) => {
          console.error("Failed to fetch task output:", err);
        });
    } else {
      setTaskOutputEntries([]);
      loadedTaskIdRef.current = null;
    }
    setIsStreaming(false);
  }, [activeOutputTaskId]);

  // Reset subtask view when navigating to a different parent task
  useEffect(() => {
    setViewingSubtaskId(null);
    setViewingSubtaskName(null);
  }, [id]);

  // Toggle viewing a subtask's output (for running subtasks)
  const handleToggleSubtaskOutput = useCallback(
    (subtaskId: string, subtaskName: string) => {
      if (viewingSubtaskId === subtaskId) {
        // Already viewing this subtask, switch back to parent
        setViewingSubtaskId(null);
        setViewingSubtaskName(null);
      } else {
        // Switch to viewing this subtask's output
        setViewingSubtaskId(subtaskId);
        setViewingSubtaskName(subtaskName);
      }
    },
    [viewingSubtaskId]
  );

  // Load task detail when URL has an id
  useEffect(() => {
    if (id) {
      setDetailLoading(true);
      fetchTask(id).then((detail) => {
        setTaskDetail(detail);
        setDetailLoading(false);
      });
    } else {
      setTaskDetail(null);
    }
  }, [id, fetchTask]);

  const handleSelectTask = useCallback(
    (taskId: string) => {
      navigate(`/mesh/${taskId}`);
    },
    [navigate]
  );

  const handleBack = useCallback(() => {
    // If viewing a subtask, go back to parent
    if (taskDetail?.parentTaskId) {
      navigate(`/mesh/${taskDetail.parentTaskId}`);
    } else {
      navigate("/mesh");
    }
  }, [navigate, taskDetail]);

  const handleDelete = useCallback(
    async (taskId: string) => {
      if (confirm("Are you sure you want to delete this task?")) {
        const success = await removeTask(taskId);
        if (success && id === taskId) {
          // If deleting current task, go back
          if (taskDetail?.parentTaskId) {
            navigate(`/mesh/${taskDetail.parentTaskId}`);
          } else {
            navigate("/mesh");
          }
        }
      }
    },
    [removeTask, id, taskDetail, navigate]
  );

  const handleStart = useCallback(
    async (taskId: string) => {
      try {
        const updated = await startTaskApi(taskId);
        setTaskDetail((prev) => prev ? { ...prev, ...updated } : prev);
      } catch (e) {
        console.error("Failed to start task:", e);
        alert(e instanceof Error ? e.message : "Failed to start task");
      }
    },
    []
  );

  const handleStop = useCallback(
    async (taskId: string) => {
      try {
        const updated = await stopTaskApi(taskId);
        setTaskDetail((prev) => prev ? { ...prev, ...updated } : prev);
      } catch (e) {
        console.error("Failed to stop task:", e);
        alert(e instanceof Error ? e.message : "Failed to stop task");
      }
    },
    []
  );

  const handleRestart = useCallback(
    async (taskId: string) => {
      try {
        // First stop the task
        await stopTaskApi(taskId);
        // Then start it again
        const updated = await startTaskApi(taskId);
        setTaskDetail((prev) => prev ? { ...prev, ...updated } : prev);
      } catch (e) {
        console.error("Failed to restart task:", e);
        alert(e instanceof Error ? e.message : "Failed to restart task");
      }
    },
    []
  );

  const handleContinue = useCallback(
    async (taskId: string) => {
      try {
        // Start the task again from terminal state
        const updated = await startTaskApi(taskId);
        setTaskDetail((prev) => prev ? { ...prev, ...updated } : prev);
      } catch (e) {
        console.error("Failed to continue task:", e);
        alert(e instanceof Error ? e.message : "Failed to continue task");
      }
    },
    []
  );

  const handleSave = useCallback(
    async (taskId: string, name: string, description: string, plan: string, targetRepoPath?: string, completionAction?: string) => {
      if (!taskDetail) return;
      const result = await editTask(taskId, {
        name,
        description: description || undefined,
        plan,
        targetRepoPath: targetRepoPath || undefined,
        completionAction: completionAction as import("../lib/api").CompletionAction | undefined,
        version: taskDetail.version,
      });
      if (result) {
        setTaskDetail(result);
      }
    },
    [editTask, taskDetail]
  );

  const handleCreate = useCallback(async () => {
    if (creating) return;
    setCreating(true);
    try {
      const newTask = await saveTask({
        name: `Task ${new Date().toLocaleDateString()}`,
        plan: "# Plan\n\nDescribe what this task should accomplish...",
      });
      if (newTask) {
        navigate(`/mesh/${newTask.id}`);
      }
    } finally {
      setCreating(false);
    }
  }, [creating, saveTask, navigate]);

  const handleCreateSubtask = useCallback(async () => {
    if (!taskDetail || creating) return;
    setCreating(true);
    try {
      const newTask = await saveTask({
        name: `Subtask of ${taskDetail.name}`,
        plan: "# Plan\n\nDescribe what this subtask should accomplish...",
        parentTaskId: taskDetail.id,
      });
      if (newTask) {
        // Refresh current task to show new subtask
        const refreshed = await fetchTask(taskDetail.id);
        if (refreshed) {
          setTaskDetail(refreshed);
        }
      }
    } finally {
      setCreating(false);
    }
  }, [creating, saveTask, taskDetail, fetchTask]);

  // Callback when task is updated via CLI
  const handleTaskUpdatedFromCli = useCallback(async () => {
    if (id) {
      const updated = await fetchTask(id);
      if (updated) {
        setTaskDetail(updated);
      }
    }
    // Also refresh the task list
    fetchTasks();
  }, [id, fetchTask, fetchTasks]);

  // Calculate chat context based on current view
  const chatContext: MeshChatContext = useMemo(() => {
    if (!id) {
      return { type: "mesh" };
    }
    if (taskDetail?.parentTaskId) {
      return { type: "subtask", taskId: id, parentTaskId: taskDetail.parentTaskId };
    }
    return { type: "task", taskId: id };
  }, [id, taskDetail?.parentTaskId]);

  // Handle resizing of the split panel
  const handleResizeStart = useCallback((e: MouseEvent) => {
    e.preventDefault();
    setIsResizing(true);
  }, []);

  useEffect(() => {
    if (!isResizing) return;

    const handleMouseMove = (e: globalThis.MouseEvent) => {
      if (!containerRef.current) return;
      const containerRect = containerRef.current.getBoundingClientRect();
      const containerWidth = containerRect.width;
      const mouseX = e.clientX - containerRect.left;

      // Calculate percentage, respecting minimum widths
      const minTaskPercent = (MIN_TASK_WIDTH / containerWidth) * 100;
      const maxTaskPercent = ((containerWidth - MIN_OUTPUT_WIDTH) / containerWidth) * 100;
      const newPercent = Math.max(minTaskPercent, Math.min(maxTaskPercent, (mouseX / containerWidth) * 100));

      setTaskPanelPercent(newPercent);
    };

    const handleMouseUp = () => {
      setIsResizing(false);
    };

    document.addEventListener("mousemove", handleMouseMove);
    document.addEventListener("mouseup", handleMouseUp);

    return () => {
      document.removeEventListener("mousemove", handleMouseMove);
      document.removeEventListener("mouseup", handleMouseUp);
    };
  }, [isResizing]);

  // Cycle through view modes
  const cycleViewMode = useCallback(() => {
    setViewMode((current) => {
      if (current === "split") return "task";
      if (current === "task") return "output";
      return "split";
    });
  }, []);

  // Get label for current view mode
  const getViewModeLabel = (mode: ViewMode): string => {
    switch (mode) {
      case "split": return "Split";
      case "task": return "Task";
      case "output": return "Output";
    }
  };

  return (
    <div className="relative z-10 h-screen flex flex-col overflow-hidden">
      <Masthead showTicker={false} showNav />

      <main className="flex-1 p-4 md:p-6 min-h-0 overflow-hidden flex flex-col">
        {error && (
          <div className="mb-4 p-3 border border-red-400/50 bg-red-400/10 text-red-400 font-mono text-sm shrink-0">
            {error}
          </div>
        )}

        {conflict?.hasConflict && (
          <div className="mb-4 p-3 border border-yellow-400/50 bg-yellow-400/10 text-yellow-400 font-mono text-sm shrink-0">
            <p>Version conflict detected. Please reload and try again.</p>
            <button
              onClick={clearConflict}
              className="mt-2 px-3 py-1 border border-yellow-400/30 hover:border-yellow-400/50 text-xs uppercase"
            >
              Dismiss
            </button>
          </div>
        )}

        {/* Main content area - conditional based on route */}
        <div className="flex-1 flex flex-col min-h-0 overflow-hidden gap-4">
          {id && taskDetail ? (
            <>
              {/* Header with connection status and view toggle */}
              <div className="flex items-center justify-between shrink-0">
                <div className="flex items-center gap-2">
                  <span
                    className={`w-2 h-2 rounded-full ${
                      connected ? "bg-green-400" : "bg-yellow-400 animate-pulse"
                    }`}
                  />
                  <span className="font-mono text-[10px] text-[#75aafc] uppercase">
                    {connected ? "Connected" : "Connecting..."}
                  </span>
                </div>
                {/* View mode toggle */}
                <button
                  onClick={cycleViewMode}
                  className="px-3 py-1 font-mono text-[10px] text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors uppercase"
                >
                  View: {getViewModeLabel(viewMode)}
                </button>
              </div>

              {/* Split panel layout */}
              <div
                ref={containerRef}
                className={`flex-1 flex min-h-0 overflow-hidden ${isResizing ? "select-none" : ""}`}
              >
                {/* Task detail panel */}
                {(viewMode === "split" || viewMode === "task") && (
                  <div
                    className="min-h-0 overflow-hidden"
                    style={{
                      width: viewMode === "split" ? `${taskPanelPercent}%` : "100%",
                      flexShrink: 0,
                    }}
                  >
                    <TaskDetail
                      task={taskDetail}
                      loading={detailLoading}
                      onBack={handleBack}
                      onSave={handleSave}
                      onDelete={handleDelete}
                      onStart={handleStart}
                      onStop={handleStop}
                      onRestart={handleRestart}
                      onContinue={handleContinue}
                      onSelectSubtask={handleSelectTask}
                      onCreateSubtask={handleCreateSubtask}
                      onToggleSubtaskOutput={handleToggleSubtaskOutput}
                      viewingSubtaskId={viewingSubtaskId}
                    />
                  </div>
                )}

                {/* Resizable divider */}
                {viewMode === "split" && (
                  <div
                    className="w-1 shrink-0 cursor-col-resize bg-[rgba(117,170,252,0.15)] hover:bg-[rgba(117,170,252,0.35)] transition-colors group flex items-center justify-center"
                    onMouseDown={handleResizeStart}
                  >
                    <div className="w-0.5 h-8 bg-[rgba(117,170,252,0.3)] group-hover:bg-[rgba(117,170,252,0.5)] rounded-full" />
                  </div>
                )}

                {/* Output panel */}
                {(viewMode === "split" || viewMode === "output") && (
                  <div
                    className="panel min-h-0 overflow-hidden flex-1"
                  >
                    <TaskOutput
                      entries={taskOutputEntries}
                      isStreaming={isStreaming || taskDetail.status === "running"}
                      viewingSubtaskName={viewingSubtaskName}
                      onClearSubtaskView={viewingSubtaskId ? () => {
                        setViewingSubtaskId(null);
                        setViewingSubtaskName(null);
                      } : undefined}
                      onClear={() => {
                        setTaskOutputEntries([]);
                        if (activeOutputTaskId) {
                          clearPersistedOutput(activeOutputTaskId);
                        }
                      }}
                      taskId={activeOutputTaskId}
                      onUserInput={handleUserInput}
                    />
                  </div>
                )}
              </div>
            </>
          ) : id && detailLoading ? (
            <div className="panel flex-1 flex items-center justify-center">
              <div className="font-mono text-[#9bc3ff] text-sm">Loading...</div>
            </div>
          ) : (
            <div className="flex-1 min-h-0 overflow-hidden">
              <TaskList
                tasks={tasks}
                loading={loading || creating}
                onSelect={handleSelectTask}
                onDelete={handleDelete}
                onCreate={handleCreate}
              />
            </div>
          )}

          {/* Mesh Chat Input - always rendered to persist state across navigation */}
          <div className="shrink-0">
            <UnifiedMeshChatInput
              context={chatContext}
              onUpdate={id ? handleTaskUpdatedFromCli : fetchTasks}
            />
          </div>
        </div>
      </main>
    </div>
  );
}
