"use client";

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type {
  AgentLogEntry,
  ChatMessage,
  ConnectionStatus,
  ResolvedChatContext,
  WorkPackage,
} from "@/lib/work-package-types";
import type { LlmConfig } from "@/lib/llm-config";
import type { ModelTrace } from "@/lib/chat-trace";
import { parseCommand } from "@/lib/command-parser";
import { applyBoardAction, type ChatResponse } from "@/lib/board-actions";
import { runMockAgent } from "@/lib/mock-agent";
import { hydrateWorkPackages } from "@/lib/work-package-specs";
import { buildAutomationInstruction } from "@/lib/automation-agent";
import { extractProductTitle } from "@/lib/product-title";
import { resolveChatRequestContext } from "@/lib/chat-request-context";
import {
  buildResolvedContext,
  buildThinkingSummary,
} from "@/lib/chat-request-context";
import { buildThinkingLogEntries } from "@/lib/agent-thinking-log";
import { AgentLogPanel } from "@/components/AgentLogPanel";
import { ChatPanel } from "@/components/ChatPanel";
import { WorkPackageBoard } from "@/components/WorkPackageBoard";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
  isLlmConfigReady,
  LLM_SETTINGS_STORAGE_KEY,
  normalizeLlmConfig,
} from "@/lib/llm-config";

import seedWorkPackages from "@/agentic_pm_demo_codex_plans/data/work-packages.seed.json";

type ViewMode = "chat" | "board";

function nowIso() {
  return new Date().toISOString();
}

function newId(prefix: string) {
  return `${prefix}-${crypto.randomUUID()}`;
}

function welcomeMessage(live: boolean) {
  return live
    ? "Live mode is connected. Type a product idea to auto-run the full board, or use a work package command like:\n`@SRS ask ...`  `@Design FMEA execute ...`"
    : "Mock mode is active until you add an API key in Model Settings below.\n\nType a product idea to auto-run the full board, or use a work package command like:\n`@SRS ask ...`  `@Design FMEA execute ...`";
}

export function AppShell() {
  const [view, setView] = useState<ViewMode>("chat");
  const [projectTitle, setProjectTitle] = useState("Agentic PM Demo");
  const [workPackages, setWorkPackages] = useState<WorkPackage[]>(() =>
    hydrateWorkPackages(seedWorkPackages as WorkPackage[]),
  );
  const [selectedWorkPackageId, setSelectedWorkPackageId] = useState<
    string | undefined
  >(() => (seedWorkPackages as WorkPackage[])[0]?.id);
  const [detailOpen, setDetailOpen] = useState(false);
  const [messages, setMessages] = useState<ChatMessage[]>(() => {
    return [
      {
        id: "welcome-message",
        role: "assistant",
        content: welcomeMessage(false),
        createdAt: nowIso(),
      },
    ];
  });
  const [agentLogs, setAgentLogs] = useState<AgentLogEntry[]>(() => [
    {
      id: newId("log"),
      title: "Workspace ready",
      detail:
        "Workspace ready. Add an API key below for live automation, or stay in mock mode.",
      level: "info",
      createdAt: nowIso(),
    },
  ]);
  const [draft, setDraft] = useState("");
  const [busy, setBusy] = useState(false);
  const [settingsLoaded, setSettingsLoaded] = useState(false);
  const [connectionStatus, setConnectionStatus] =
    useState<ConnectionStatus>("idle");
  const [connectionMessage, setConnectionMessage] = useState(
    "Add your model settings to enable live automation.",
  );
  const [activeWorkPackageId, setActiveWorkPackageId] = useState<
    string | undefined
  >();
  const [currentActivity, setCurrentActivity] = useState(
    "Ready for a product idea or a package command.",
  );
  const [llmConfig, setLlmConfig] = useState<LlmConfig>(() =>
    normalizeLlmConfig(),
  );
  const lastConnectionLogKeyRef = useRef("");

  const addLog = useCallback(
    (title: string, detail: string, level: AgentLogEntry["level"]) => {
      setAgentLogs((prev) => [
        ...prev,
        {
          id: newId("log"),
          title,
          detail,
          level,
          createdAt: nowIso(),
        },
      ]);
    },
    [],
  );

  const runConnectionCheck = useCallback(async (config: LlmConfig) => {
    if (!isLlmConfigReady(config)) {
      setConnectionStatus("idle");
      setConnectionMessage("Add API key, base URL, and model to enable live automation.");
      return;
    }

    setConnectionStatus("checking");
    setConnectionMessage("Testing model connection...");

    try {
      const res = await fetch("/api/test-connection", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ llmConfig: config }),
      });
      const data = (await res.json()) as {
        ok?: boolean;
        status?: ConnectionStatus;
        message?: string;
        model?: string;
      };

      if (data.ok) {
        setConnectionStatus("connected");
        setConnectionMessage(data.message || "Connection verified.");
        const logKey = `${config.baseUrl}|${config.model}|connected`;
        if (lastConnectionLogKeyRef.current !== logKey) {
          lastConnectionLogKeyRef.current = logKey;
          addLog(
            "Model connected",
            `${config.model} is ready for live automation.`,
            "success",
          );
        }
        return;
      }

      setConnectionStatus(data.status === "error" ? "error" : "idle");
      setConnectionMessage(data.message || "Connection test did not complete.");
      const logKey = `${config.baseUrl}|${config.model}|${data.message}`;
      if (data.status === "error" && lastConnectionLogKeyRef.current !== logKey) {
        lastConnectionLogKeyRef.current = logKey;
        addLog(
          "Model connection failed",
          data.message || "Connection test failed.",
          "warn",
        );
      }
    } catch (error) {
      setConnectionStatus("error");
      setConnectionMessage(String(error));
    }
  }, [addLog]);

  useEffect(() => {
    try {
      const raw = window.localStorage.getItem(LLM_SETTINGS_STORAGE_KEY);
      if (raw) {
        const parsed = JSON.parse(raw) as Partial<LlmConfig>;
        setLlmConfig(normalizeLlmConfig(parsed));
      }
    } catch {
      // ignore malformed local storage
    } finally {
      setSettingsLoaded(true);
    }
  }, []);

  useEffect(() => {
    if (!settingsLoaded) return;
    try {
      window.localStorage.setItem(
        LLM_SETTINGS_STORAGE_KEY,
        JSON.stringify(llmConfig),
      );
    } catch {
      // ignore persistence failures
    }
  }, [llmConfig, runConnectionCheck, settingsLoaded]);

  useEffect(() => {
    if (typeof document === "undefined") return;
    document.title =
      projectTitle === "Agentic PM Demo"
        ? projectTitle
        : `${projectTitle} · Agentic PM Demo`;
  }, [projectTitle]);

  useEffect(() => {
    setMessages((prev) => {
      if (!prev.length || prev[0]?.id !== "welcome-message") return prev;
      const next = [...prev];
      next[0] = {
        ...next[0],
        content: welcomeMessage(connectionStatus === "connected"),
      };
      return next;
    });
  }, [connectionStatus]);

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

    if (!isLlmConfigReady(llmConfig)) {
      setConnectionStatus("idle");
      setConnectionMessage("Add API key, base URL, and model to enable live automation.");
      return;
    }

    const timeout = window.setTimeout(async () => {
      await runConnectionCheck(llmConfig);
    }, 700);

    return () => {
      window.clearTimeout(timeout);
    };
  }, [llmConfig, runConnectionCheck, settingsLoaded]);

  const isMockMode = useMemo(
    () => connectionStatus !== "connected",
    [connectionStatus],
  );

  function logModelTrace(trace: ModelTrace | undefined) {
    if (!trace) return;

    if (trace.requestPreview) {
      addLog(
        "Model request",
        `${trace.model ?? "unknown"}${trace.endpoint ? ` @ ${trace.endpoint}` : ""} :: ${trace.requestPreview}`,
        trace.provider === "live" ? "info" : "warn",
      );
    }

    if (trace.responsePreview) {
      addLog(
        "Model response",
        trace.responsePreview,
        trace.provider === "live" ? "success" : "info",
      );
    }
  }

  function logThinkingSummary(lines: string[] | undefined, knownLines: string[] = []) {
    const known = new Set(knownLines);
    for (const entry of buildThinkingLogEntries({
      thinkingSummary: (lines ?? []).filter((line) => !known.has(line)),
    })) {
      addLog(entry.title, entry.detail, entry.level);
    }
  }

  async function requestChatResponse(args: {
    nextMessages: ChatMessage[];
    boardSnapshot: WorkPackage[];
    selectedId?: string;
    requestContext: ResolvedChatContext;
    parsedCommand: {
      referencedPackageName?: string;
      mode?: "ask" | "plan" | "change" | "execute";
      instruction: string;
    };
  }) {
    const res = await fetch("/api/chat", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        messages: args.nextMessages.map((message) => ({
          role: message.role,
          content: message.content,
        })),
        workPackages: args.boardSnapshot,
        selectedWorkPackageId: args.selectedId ?? null,
        requestContext: args.requestContext,
        parsedCommand: args.parsedCommand,
        llmConfig,
      }),
    });

    if (!res.ok) {
      throw new Error(`Request failed with status ${res.status}.`);
    }

    return (await res.json()) as ChatResponse;
  }

  async function automateBoardFromProductIdea(args: {
    productIdea: string;
    nextMessages: ChatMessage[];
    fallbackToMock?: boolean;
  }) {
    const hydrated = hydrateWorkPackages(workPackages, args.productIdea);
    let boardState = hydrated;
    const completedShortNames: string[] = [];
    const newTitle = extractProductTitle(args.productIdea);

    setProjectTitle(newTitle);
    setWorkPackages(hydrated);
    setDetailOpen(false);
    setActiveWorkPackageId(undefined);
    setCurrentActivity(`Preparing board for ${newTitle}.`);
    addLog(
      "Board hydrated",
      `Prepared ${hydrated.length} work packages from the product idea.`,
      "info",
    );

    for (const workPackage of hydrated) {
      setActiveWorkPackageId(workPackage.id);
      setCurrentActivity(`Running ${workPackage.shortName} for ${newTitle}.`);
      addLog(
        "Package running",
        `${workPackage.shortName} is running with ${
          isMockMode ? "mock mode" : llmConfig.model
        }.`,
        "info",
      );

      const response = args.fallbackToMock
        ? runMockAgent({
            messages: args.nextMessages,
            workPackages: boardState,
            selectedWorkPackageId: workPackage.id,
            parsedCommand: {
              referencedPackageName: workPackage.title,
              mode: "execute",
              instruction: buildAutomationInstruction(workPackage),
            },
          })
        : await requestChatResponse({
            nextMessages: args.nextMessages,
            boardSnapshot: boardState,
            selectedId: workPackage.id,
            requestContext: buildResolvedContext({
              parsedCommand: {
                referencedPackageName: workPackage.title,
                mode: "execute",
                instruction: buildAutomationInstruction(workPackage),
              },
              shouldAutomateBoard: false,
              selectedWorkPackage: workPackage,
              provider: "live",
            }),
            parsedCommand: {
              referencedPackageName: workPackage.title,
              mode: "execute",
              instruction: buildAutomationInstruction(workPackage),
            },
          });

      if (response.boardAction) {
        boardState = applyBoardAction(boardState, response.boardAction);
        setWorkPackages(boardState);
      }
      logModelTrace(response.trace);

      completedShortNames.push(workPackage.shortName);
      addLog(
        "Package completed",
        `${workPackage.shortName} completed.`,
        "success",
      );
    }

    setActiveWorkPackageId(undefined);
    setCurrentActivity(`Completed board automation for ${newTitle}.`);

    setMessages((prev) => [
      ...prev,
      {
        id: newId("m"),
        role: "assistant",
        content: isMockMode
          ? `Completed all ${completedShortNames.length} work packages in mock mode.\n\nGenerated outputs: ${completedShortNames.join(", ")}`
          : `Completed all ${completedShortNames.length} work packages with ${llmConfig.model}.\n\nGenerated outputs: ${completedShortNames.join(", ")}`,
        createdAt: nowIso(),
      },
    ]);
    addLog(
      "Board automation finished",
      `Generated outputs for ${completedShortNames.join(", ")}.`,
      "success",
    );
  }

  async function handleChatSend(rawText: string) {
    const text = rawText.trim();
    if (!text) return;

    const userMsg: ChatMessage = {
      id: newId("m"),
      role: "user",
      content: text,
      createdAt: nowIso(),
    };

    const nextMessages = [...messages, userMsg];
    const parsed = parseCommand(text, workPackages);
    const selectedWorkPackage = workPackages.find(
      (workPackage) => workPackage.id === selectedWorkPackageId,
    );
    const contextWorkPackage =
      workPackages.find(
        (workPackage) => workPackage.id === parsed.matchedWorkPackageId,
      ) ?? selectedWorkPackage;
    const resolvedContext = resolveChatRequestContext({
      parsed: parsed.parsed,
      detailOpen,
      selectedWorkPackage: contextWorkPackage,
    });
    const effectiveParsedCommand = resolvedContext.parsedCommand;
    const requestContext = buildResolvedContext({
      parsedCommand: effectiveParsedCommand,
      shouldAutomateBoard: resolvedContext.shouldAutomateBoard,
      selectedWorkPackage: contextWorkPackage,
      provider: isMockMode ? "mock" : "live",
    });
    const requestThinking = buildThinkingSummary({
      context: requestContext,
      model: isMockMode ? undefined : llmConfig.model,
    });

    setMessages(nextMessages);
    setDraft("");

    if (parsed.matchedWorkPackageId) {
      setSelectedWorkPackageId(parsed.matchedWorkPackageId);
      setDetailOpen(true);
      addLog(
        "Package selected",
        `Focused ${parsed.matchedWorkPackageTitle ?? parsed.parsed.referencedPackageName ?? "package"} from command context.`,
        "info",
      );
    } else if (resolvedContext.source === "board-automation") {
      setDetailOpen(false);
    }

    if (parsed.error) {
      const suggestionLine =
        parsed.suggestions && parsed.suggestions.length
          ? `\n\nDid you mean:\n${parsed.suggestions
              .map((s) => `- ${s.shortName} (${s.title})`)
              .join("\n")}`
          : "";

      setMessages((prev) => [
        ...prev,
        {
          id: newId("m"),
          role: "assistant",
          content: `${parsed.error}${suggestionLine}`,
          createdAt: nowIso(),
        },
      ]);
      addLog("Command parse warning", parsed.error, "warn");
      return;
    }

    // Mock-first: call local mock agent if the command is @... OR if the user is just brainstorming.
    // Once the /api/chat route is present, we’ll prefer it for consistent behavior.
    setBusy(true);
    setCurrentActivity(
      effectiveParsedCommand.mode
        ? `Running ${effectiveParsedCommand.mode} on ${effectiveParsedCommand.referencedPackageName ?? "selected package"}.`
        : "Processing product idea and preparing the board.",
    );
    addLog(
      "Agent started",
      effectiveParsedCommand.mode
        ? `Running ${effectiveParsedCommand.mode} on ${effectiveParsedCommand.referencedPackageName ?? "board context"}.`
        : "Processing free-form product planning request.",
      "info",
    );
    logThinkingSummary(requestThinking);
    if (resolvedContext.source === "selected-package-context") {
      addLog(
        "Detail question routed",
        `Treating the plain-text prompt as an ask request for ${contextWorkPackage?.title ?? "the selected work package"}.`,
        "info",
      );
    } else if (resolvedContext.source === "slash-command") {
      addLog(
        "Slash command routed",
        `Applied /${effectiveParsedCommand.mode ?? "ask"} to ${contextWorkPackage?.title ?? "the selected work package"}.`,
        "info",
      );
    }

    try {
      if (resolvedContext.shouldAutomateBoard) {
        await automateBoardFromProductIdea({
          productIdea: text,
          nextMessages,
        });
        return;
      }

      // Call server route first (it will fall back to mock mode automatically if no key).
      if (effectiveParsedCommand.mode === "execute") {
        setActiveWorkPackageId(
          parsed.matchedWorkPackageId ?? selectedWorkPackageId,
        );
      }
      const response = await requestChatResponse({
        nextMessages,
        boardSnapshot: workPackages,
        selectedId: parsed.matchedWorkPackageId ?? selectedWorkPackageId,
        requestContext,
        parsedCommand: effectiveParsedCommand,
      });
      logThinkingSummary(response.thinkingSummary, requestThinking);
      logModelTrace(response.trace);

      if (response?.boardAction) {
        setWorkPackages((prev) => applyBoardAction(prev, response?.boardAction));
        if (response.boardAction.type === "replace_all") {
          setDetailOpen(false);
          addLog(
            "Board refreshed",
            "Hydrated the work package board from the product idea.",
            "success",
          );
        } else if (response.boardAction.type !== "none") {
          addLog(
            "Board updated",
            `Applied ${response.boardAction.type} to ${effectiveParsedCommand.referencedPackageName ?? "work package"}.`,
            "success",
          );
        }
      }

      setMessages((prev) => [
        ...prev,
        {
          id: newId("m"),
          role: "assistant",
          content: response?.assistantMessage ?? "No response.",
          createdAt: nowIso(),
        },
      ]);
      addLog(
        "Agent finished",
        response?.assistantMessage ?? "No response.",
        "success",
      );
    } catch (err) {
      if (resolvedContext.shouldAutomateBoard) {
        addLog(
          "Automation fallback",
          "Live automation failed, continuing in mock mode.",
          "warn",
        );
        await automateBoardFromProductIdea({
          productIdea: text,
          nextMessages,
          fallbackToMock: true,
        });
        return;
      }

      const fallbackResponse = runMockAgent({
        messages: nextMessages,
        workPackages,
        selectedWorkPackageId: parsed.matchedWorkPackageId ?? selectedWorkPackageId,
        parsedCommand: effectiveParsedCommand,
      });

      if (fallbackResponse.boardAction) {
        setWorkPackages((prev) =>
          applyBoardAction(prev, fallbackResponse.boardAction),
        );
      }
      setMessages((prev) => [
        ...prev,
        {
          id: newId("m"),
          role: "assistant",
          content:
            fallbackResponse.assistantMessage ||
            `Network error. (Mock mode)\n\n${String(err)}`,
          createdAt: nowIso(),
        },
      ]);
      addLog(
        "Fallback path",
        "Live request failed locally; switched to the mock response path.",
        "warn",
      );
      addLog("Agent error", String(err), "error");
    } finally {
      setActiveWorkPackageId(undefined);
      setCurrentActivity("Ready for the next step.");
      setBusy(false);
    }
  }

  return (
    <div className="flex h-[100dvh] flex-col bg-muted/40">
      <div className="bg-background/72 backdrop-blur-sm">
        <div className="mx-auto w-full max-w-[1760px] px-4 py-3 md:px-5">
          <div className="flex items-center justify-between gap-2">
            <div className="min-w-0">
              <div className="truncate text-sm font-semibold">
                {projectTitle}
              </div>
              <div className="truncate text-xs text-muted-foreground">
                {busy ? currentActivity : "Chat + automated IoT work packages"}
              </div>
            </div>
            <div className="shrink-0 md:hidden">
              <Tabs value={view} onValueChange={(v) => setView(v as ViewMode)}>
                <TabsList>
                  <TabsTrigger value="chat">Chat</TabsTrigger>
                  <TabsTrigger value="board">Board</TabsTrigger>
                </TabsList>
              </Tabs>
            </div>
            <div className="hidden text-xs text-muted-foreground md:block">
              {isMockMode ? "Mock mode" : `Live · ${llmConfig.model}`}
            </div>
          </div>
        </div>
      </div>

      <div className="flex-1 overflow-hidden px-3 pb-3 md:px-4 md:pb-4">
        <div className="mx-auto grid h-full w-full max-w-[1760px] grid-cols-1 gap-3 md:grid-cols-[minmax(320px,1fr)_minmax(0,2fr)]">
          <div
            className={[
              "h-full min-h-0 overflow-hidden rounded-[22px] bg-background/82 shadow-sm backdrop-blur-sm",
              view === "board" ? "hidden md:block" : "block",
            ].join(" ")}
          >
            <ChatPanel
              messages={messages}
              draft={draft}
              setDraft={setDraft}
              busy={busy}
              productTitle={projectTitle}
              currentActivity={currentActivity}
              workPackages={workPackages}
              selectedWorkPackageId={selectedWorkPackageId}
              onSelectWorkPackage={(id) => {
                setSelectedWorkPackageId(id);
              }}
              llmConfig={llmConfig}
              setLlmConfig={setLlmConfig}
              isMockMode={isMockMode}
              connectionStatus={connectionStatus}
              connectionMessage={connectionMessage}
              canTestConnection={isLlmConfigReady(llmConfig)}
              onTestConnection={() => {
                addLog("Connection test requested", "Checking the configured model settings.", "info");
                void runConnectionCheck(llmConfig);
              }}
              onSend={handleChatSend}
            />
          </div>
          <div
            className={[
              "h-full min-h-0 overflow-hidden rounded-[22px] bg-background/82 shadow-sm backdrop-blur-sm",
              view === "chat" ? "hidden md:block" : "block",
            ].join(" ")}
          >
            <div className="flex h-full min-h-0 flex-col">
              <div className="min-h-0 flex-1 p-2">
                <WorkPackageBoard
                  workPackages={workPackages}
                  activeWorkPackageId={activeWorkPackageId}
                  selectedWorkPackageId={selectedWorkPackageId}
                  onSelect={(id) => {
                    setSelectedWorkPackageId(id);
                    setDetailOpen(true);
                    const selected = workPackages.find((wp) => wp.id === id);
                    addLog(
                      "Detail opened",
                      `Opened ${selected?.title ?? "work package"} detail view.`,
                      "info",
                    );
                  }}
                  onPrefill={(s) => {
                    setView("chat");
                    setDraft(s);
                    addLog("Command prepared", `Prefilled chat input with: ${s}`, "info");
                  }}
                  detailOpen={detailOpen}
                  onBackToBoard={() => {
                    setDetailOpen(false);
                    addLog("Board view restored", "Returned to work package card board.", "info");
                  }}
                />
              </div>
              <div className="h-[22dvh] min-h-[150px] px-2 pb-2">
                <AgentLogPanel logs={agentLogs} busy={busy} />
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
