import * as React from "react";
import Markdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import { api, ChatWebSocket } from "../api/client";
import type { Message, ProviderConfig, ToolCall } from "../types";

const { useCallback, useEffect, useRef, useState } = React;

interface ChatProps {
  projectPath: string;
  onCloseProject: () => void;
}

export function Chat({ projectPath, onCloseProject }: ChatProps) {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");
  const [loading, setLoading] = useState(false);
  const [model, setModel] = useState("llama3.1");
  const [enableTools, setEnableTools] = useState(true);
  const [availableModels, setAvailableModels] = useState<string[]>([]);
  const [claudeModels, setClaudeModels] = useState<string[]>([]);
  const [streamingContent, setStreamingContent] = useState("");
  const [showApiKeyDialog, setShowApiKeyDialog] = useState(false);
  const [apiKeyInput, setApiKeyInput] = useState("");
  const [hasAnthropicKey, setHasAnthropicKey] = useState(false);

  const wsRef = useRef<ChatWebSocket | null>(null);
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const scrollContainerRef = useRef<HTMLDivElement>(null);
  const shouldAutoScrollRef = useRef(true);
  const lastScrollTopRef = useRef(0);
  const userScrolledUpRef = useRef(false);
  const pendingMessageRef = useRef<string>("");

  const estimateTokens = (text: string): number => Math.ceil(text.length / 4);

  const getContextWindowSize = (modelName: string): number => {
    if (modelName.startsWith("claude-")) return 200000;
    if (modelName.includes("llama3")) return 8192;
    if (modelName.includes("qwen2.5")) return 32768;
    if (modelName.includes("deepseek")) return 16384;
    return 8192;
  };

  const calculateContextUsage = (): {
    used: number;
    total: number;
    percentage: number;
  } => {
    let totalTokens = 0;

    totalTokens += 200;

    for (const msg of messages) {
      totalTokens += estimateTokens(msg.content);
      if (msg.tool_calls) {
        totalTokens += estimateTokens(JSON.stringify(msg.tool_calls));
      }
    }

    if (streamingContent) {
      totalTokens += estimateTokens(streamingContent);
    }

    const contextWindow = getContextWindowSize(model);
    const percentage = Math.round((totalTokens / contextWindow) * 100);

    return {
      used: totalTokens,
      total: contextWindow,
      percentage,
    };
  };

  const contextUsage = calculateContextUsage();

  const getContextEmoji = (percentage: number): string => {
    if (percentage >= 90) return "🔴";
    if (percentage >= 75) return "🟡";
    return "🟢";
  };

  useEffect(() => {
    api
      .getOllamaModels()
      .then(async (models) => {
        if (models.length > 0) {
          const sortedModels = models.sort((a, b) =>
            a.toLowerCase().localeCompare(b.toLowerCase()),
          );
          setAvailableModels(sortedModels);

          try {
            const savedModel = await api.getModelPreference();
            if (savedModel) {
              setModel(savedModel);
            } else if (sortedModels.length > 0) {
              setModel(sortedModels[0]);
            }
          } catch (e) {
            console.error(e);
          }
        }
      })
      .catch((err) => console.error(err));

    api
      .getAnthropicApiKeyExists()
      .then((exists) => {
        setHasAnthropicKey(exists);
      })
      .catch((err) => {
        console.error(err);
        setHasAnthropicKey(false);
      });

    api
      .getAnthropicModels()
      .then((models) => {
        if (models.length > 0) {
          const sortedModels = models.sort((a, b) =>
            a.toLowerCase().localeCompare(b.toLowerCase()),
          );
          setClaudeModels(sortedModels);
        } else {
          setClaudeModels([]);
        }
      })
      .catch((err) => {
        console.error(err);
        setClaudeModels([]);
      });
  }, []);

  useEffect(() => {
    const ws = new ChatWebSocket();
    wsRef.current = ws;

    ws.connect({
      onToken: (content) => {
        setStreamingContent((prev: string) => prev + content);
      },
      onUpdate: (history) => {
        setMessages(history);
        setStreamingContent("");
        const last = history[history.length - 1];
        if (last?.role === "assistant" && !last.tool_calls) {
          setLoading(false);
        }
      },
      onError: (message) => {
        console.error("WebSocket error:", message);
        setLoading(false);
      },
    });

    return () => {
      ws.close();
      wsRef.current = null;
    };
  }, []);

  const scrollToBottom = useCallback(() => {
    const element = scrollContainerRef.current;
    if (element) {
      element.scrollTop = element.scrollHeight;
      lastScrollTopRef.current = element.scrollHeight;
    }
  }, []);

  const handleScroll = () => {
    const element = scrollContainerRef.current;
    if (!element) return;

    const currentScrollTop = element.scrollTop;
    const isAtBottom =
      element.scrollHeight - element.scrollTop - element.clientHeight < 5;

    if (currentScrollTop < lastScrollTopRef.current) {
      userScrolledUpRef.current = true;
      shouldAutoScrollRef.current = false;
    }

    if (isAtBottom) {
      userScrolledUpRef.current = false;
      shouldAutoScrollRef.current = true;
    }

    lastScrollTopRef.current = currentScrollTop;
  };

  const autoScrollKey = messages.length + streamingContent.length;

  useEffect(() => {
    if (
      autoScrollKey >= 0 &&
      shouldAutoScrollRef.current &&
      !userScrolledUpRef.current
    ) {
      scrollToBottom();
    }
  }, [autoScrollKey, scrollToBottom]);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  const cancelGeneration = async () => {
    try {
      wsRef.current?.cancel();
      await api.cancelChat();

      if (streamingContent) {
        setMessages((prev: Message[]) => [
          ...prev,
          { role: "assistant", content: streamingContent },
        ]);
        setStreamingContent("");
      }

      setLoading(false);
    } catch (e) {
      console.error("Failed to cancel chat:", e);
    }
  };

  const sendMessage = async (messageOverride?: string) => {
    const messageToSend = messageOverride ?? input;
    if (!messageToSend.trim() || loading) return;

    if (model.startsWith("claude-")) {
      const hasKey = await api.getAnthropicApiKeyExists();
      if (!hasKey) {
        pendingMessageRef.current = messageToSend;
        setShowApiKeyDialog(true);
        return;
      }
    }

    const userMsg: Message = { role: "user", content: messageToSend };
    const newHistory = [...messages, userMsg];

    setMessages(newHistory);
    if (!messageOverride || messageOverride === input) {
      setInput("");
    }
    setLoading(true);
    setStreamingContent("");

    try {
      const config: ProviderConfig = {
        provider: model.startsWith("claude-") ? "anthropic" : "ollama",
        model,
        base_url: "http://localhost:11434",
        enable_tools: enableTools,
      };

      wsRef.current?.sendChat(newHistory, config);
    } catch (e) {
      console.error("Chat error:", e);
      const errorMessage = String(e);
      if (!errorMessage.includes("Chat cancelled by user")) {
        setMessages((prev: Message[]) => [
          ...prev,
          { role: "assistant", content: `**Error:** ${e}` },
        ]);
      }
      setLoading(false);
    }
  };

  const handleSaveApiKey = async () => {
    if (!apiKeyInput.trim()) return;

    try {
      await api.setAnthropicApiKey(apiKeyInput);
      setShowApiKeyDialog(false);
      setApiKeyInput("");

      const pendingMessage = pendingMessageRef.current;
      pendingMessageRef.current = "";

      if (pendingMessage.trim()) {
        sendMessage(pendingMessage);
      }
    } catch (e) {
      console.error("Failed to save API key:", e);
      alert(`Failed to save API key: ${e}`);
    }
  };

  const clearSession = async () => {
    const confirmed = window.confirm(
      "Are you sure? This will clear all messages and reset the conversation context.",
    );

    if (confirmed) {
      try {
        await api.cancelChat();
        wsRef.current?.cancel();
      } catch (e) {
        console.error("Failed to cancel chat:", e);
      }

      setMessages([]);
      setStreamingContent("");
      setLoading(false);
    }
  };

  return (
    <div
      className="chat-container"
      style={{
        display: "flex",
        flexDirection: "column",
        height: "100%",
        backgroundColor: "#171717",
        color: "#ececec",
      }}
    >
      <div
        style={{
          padding: "12px 24px",
          borderBottom: "1px solid #333",
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          background: "#171717",
          flexShrink: 0,
          fontSize: "0.9rem",
          color: "#ececec",
        }}
      >
        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: "12px",
            overflow: "hidden",
            flex: 1,
            marginRight: "20px",
          }}
        >
          <div
            title={projectPath}
            style={{
              whiteSpace: "nowrap",
              overflow: "hidden",
              textOverflow: "ellipsis",
              fontWeight: "500",
              color: "#aaa",
              direction: "rtl",
              textAlign: "left",
              fontFamily: "monospace",
              fontSize: "0.85em",
            }}
          >
            {projectPath}
          </div>
          <button
            type="button"
            onClick={onCloseProject}
            style={{
              background: "transparent",
              border: "none",
              cursor: "pointer",
              color: "#999",
              fontSize: "0.8em",
              padding: "4px 8px",
              borderRadius: "4px",
            }}
            onMouseOver={(e) => {
              e.currentTarget.style.background = "#333";
            }}
            onMouseOut={(e) => {
              e.currentTarget.style.background = "transparent";
            }}
            onFocus={(e) => {
              e.currentTarget.style.background = "#333";
            }}
            onBlur={(e) => {
              e.currentTarget.style.background = "transparent";
            }}
          >
            ✕
          </button>
        </div>

        <div style={{ display: "flex", alignItems: "center", gap: "16px" }}>
          <div
            style={{
              fontSize: "0.9em",
              color: "#ccc",
              whiteSpace: "nowrap",
            }}
            title={`Context: ${contextUsage.used.toLocaleString()} / ${contextUsage.total.toLocaleString()} tokens (${contextUsage.percentage}%)`}
          >
            {getContextEmoji(contextUsage.percentage)} {contextUsage.percentage}
            %
          </div>

          <button
            type="button"
            onClick={clearSession}
            style={{
              padding: "6px 12px",
              borderRadius: "99px",
              border: "none",
              fontSize: "0.85em",
              backgroundColor: "#2f2f2f",
              color: "#888",
              cursor: "pointer",
              outline: "none",
              transition: "all 0.2s",
            }}
            onMouseOver={(e) => {
              e.currentTarget.style.backgroundColor = "#3f3f3f";
              e.currentTarget.style.color = "#ccc";
            }}
            onMouseOut={(e) => {
              e.currentTarget.style.backgroundColor = "#2f2f2f";
              e.currentTarget.style.color = "#888";
            }}
            onFocus={(e) => {
              e.currentTarget.style.backgroundColor = "#3f3f3f";
              e.currentTarget.style.color = "#ccc";
            }}
            onBlur={(e) => {
              e.currentTarget.style.backgroundColor = "#2f2f2f";
              e.currentTarget.style.color = "#888";
            }}
          >
            🔄 New Session
          </button>
          {availableModels.length > 0 || claudeModels.length > 0 ? (
            <select
              value={model}
              onChange={(e) => {
                const newModel = e.target.value;
                setModel(newModel);
                api.setModelPreference(newModel).catch(console.error);
              }}
              style={{
                padding: "6px 32px 6px 16px",
                borderRadius: "99px",
                border: "none",
                fontSize: "0.9em",
                backgroundColor: "#2f2f2f",
                color: "#ececec",
                cursor: "pointer",
                outline: "none",
                appearance: "none",
                WebkitAppearance: "none",
                backgroundImage: `url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23ececec%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E")`,
                backgroundRepeat: "no-repeat",
                backgroundPosition: "right 12px center",
                backgroundSize: "10px",
              }}
            >
              {(claudeModels.length > 0 || !hasAnthropicKey) && (
                <optgroup label="Anthropic">
                  {claudeModels.length > 0 ? (
                    claudeModels.map((m: string) => (
                      <option key={m} value={m}>
                        {m}
                      </option>
                    ))
                  ) : (
                    <option value="" disabled>
                      Add Anthropic API key to load models
                    </option>
                  )}
                </optgroup>
              )}
              {availableModels.length > 0 && (
                <optgroup label="Ollama">
                  {availableModels.map((m: string) => (
                    <option key={m} value={m}>
                      {m}
                    </option>
                  ))}
                </optgroup>
              )}
            </select>
          ) : (
            <input
              value={model}
              onChange={(e) => {
                const newModel = e.target.value;
                setModel(newModel);
                api.setModelPreference(newModel).catch(console.error);
              }}
              placeholder="Model"
              style={{
                padding: "6px 12px",
                borderRadius: "99px",
                border: "none",
                fontSize: "0.9em",
                background: "#2f2f2f",
                color: "#ececec",
                outline: "none",
              }}
            />
          )}
          <label
            style={{
              display: "flex",
              alignItems: "center",
              gap: "6px",
              cursor: "pointer",
              fontSize: "0.9em",
              color: "#aaa",
            }}
            title="Allow the Agent to read/write files"
          >
            <input
              type="checkbox"
              checked={enableTools}
              onChange={(e) => setEnableTools(e.target.checked)}
              style={{ accentColor: "#000" }}
            />
            <span>Tools</span>
          </label>
        </div>
      </div>

      <div
        ref={scrollContainerRef}
        onScroll={handleScroll}
        style={{
          flex: 1,
          overflowY: "auto",
          padding: "20px 0",
          display: "flex",
          flexDirection: "column",
          gap: "24px",
        }}
      >
        <div
          style={{
            maxWidth: "768px",
            margin: "0 auto",
            width: "100%",
            padding: "0 24px",
            display: "flex",
            flexDirection: "column",
            gap: "24px",
          }}
        >
          {messages.map((msg: Message, idx: number) => (
            <div
              key={`msg-${idx}-${msg.role}-${msg.content.substring(0, 20)}`}
              style={{
                display: "flex",
                flexDirection: "column",
                alignItems: msg.role === "user" ? "flex-end" : "flex-start",
              }}
            >
              <div
                style={{
                  maxWidth: "100%",
                  padding: msg.role === "user" ? "10px 16px" : "0",
                  borderRadius: msg.role === "user" ? "20px" : "0",
                  background:
                    msg.role === "user"
                      ? "#2f2f2f"
                      : msg.role === "tool"
                        ? "#222"
                        : "transparent",
                  color: "#ececec",
                  border: msg.role === "tool" ? "1px solid #333" : "none",
                  fontFamily: msg.role === "tool" ? "monospace" : "inherit",
                  fontSize: msg.role === "tool" ? "0.85em" : "1em",
                  fontWeight: "500",
                  whiteSpace: msg.role === "tool" ? "pre-wrap" : "normal",
                  lineHeight: "1.6",
                }}
              >
                {msg.role === "user" ? (
                  msg.content
                ) : msg.role === "tool" ? (
                  <details style={{ cursor: "pointer" }}>
                    <summary
                      style={{
                        color: "#aaa",
                        fontSize: "0.9em",
                        marginBottom: "8px",
                        listStyle: "none",
                        display: "flex",
                        alignItems: "center",
                        gap: "6px",
                      }}
                    >
                      <span style={{ fontSize: "0.8em" }}>▶</span>
                      <span>
                        Tool Output
                        {msg.tool_call_id && ` (${msg.tool_call_id})`}
                      </span>
                    </summary>
                    <pre
                      style={{
                        maxHeight: "300px",
                        overflow: "auto",
                        margin: 0,
                        padding: "8px",
                        background: "#1a1a1a",
                        borderRadius: "4px",
                        fontSize: "0.85em",
                        whiteSpace: "pre-wrap",
                        wordBreak: "break-word",
                      }}
                    >
                      {msg.content}
                    </pre>
                  </details>
                ) : (
                  <div className="markdown-body">
                    <Markdown
                      components={{
                        // eslint-disable-next-line @typescript-eslint/no-explicit-any
                        // biome-ignore lint/suspicious/noExplicitAny: react-markdown requires any for component props
                        code: ({ className, children, ...props }: any) => {
                          const match = /language-(\w+)/.exec(className || "");
                          const isInline = !className;
                          return !isInline && match ? (
                            <SyntaxHighlighter
                              // biome-ignore lint/suspicious/noExplicitAny: oneDark style types are incompatible
                              style={oneDark as any}
                              language={match[1]}
                              PreTag="div"
                            >
                              {String(children).replace(/\n$/, "")}
                            </SyntaxHighlighter>
                          ) : (
                            <code className={className} {...props}>
                              {children}
                            </code>
                          );
                        },
                      }}
                    >
                      {msg.content}
                    </Markdown>
                  </div>
                )}

                {msg.tool_calls && (
                  <div
                    style={{
                      marginTop: "12px",
                      fontSize: "0.85em",
                      color: "#aaa",
                      display: "flex",
                      flexDirection: "column",
                      gap: "8px",
                    }}
                  >
                    {msg.tool_calls.map((tc: ToolCall, i: number) => {
                      let argsSummary = "";
                      try {
                        const args = JSON.parse(tc.function.arguments);
                        const firstKey = Object.keys(args)[0];
                        if (firstKey && args[firstKey]) {
                          argsSummary = String(args[firstKey]);
                          if (argsSummary.length > 50) {
                            argsSummary = `${argsSummary.substring(0, 47)}...`;
                          }
                        }
                      } catch (_e) {
                        // ignore
                      }

                      return (
                        <div
                          key={`tool-${i}-${tc.function.name}`}
                          style={{
                            display: "flex",
                            alignItems: "center",
                            gap: "8px",
                            fontFamily: "monospace",
                          }}
                        >
                          <span style={{ color: "#888" }}>▶</span>
                          <span
                            style={{
                              background: "#333",
                              padding: "2px 6px",
                              borderRadius: "4px",
                            }}
                          >
                            {tc.function.name}
                            {argsSummary && `(${argsSummary})`}
                          </span>
                        </div>
                      );
                    })}
                  </div>
                )}
              </div>
            </div>
          ))}
          {loading && streamingContent && (
            <div
              style={{
                display: "flex",
                flexDirection: "column",
                alignItems: "flex-start",
              }}
            >
              <div
                style={{
                  maxWidth: "85%",
                  padding: "16px 20px",
                  borderRadius: "12px",
                  background: "#262626",
                  color: "#fff",
                  border: "1px solid #404040",
                  fontFamily: "system-ui, -apple-system, sans-serif",
                  fontSize: "0.95rem",
                  fontWeight: 400,
                  whiteSpace: "pre-wrap",
                  lineHeight: 1.6,
                }}
              >
                <Markdown
                  components={{
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    // biome-ignore lint/suspicious/noExplicitAny: react-markdown requires any for component props
                    code: ({ className, children, ...props }: any) => {
                      const match = /language-(\w+)/.exec(className || "");
                      const isInline = !className;
                      return !isInline && match ? (
                        <SyntaxHighlighter
                          // biome-ignore lint/suspicious/noExplicitAny: oneDark style types are incompatible
                          style={oneDark as any}
                          language={match[1]}
                          PreTag="div"
                        >
                          {String(children).replace(/\n$/, "")}
                        </SyntaxHighlighter>
                      ) : (
                        <code className={className} {...props}>
                          {children}
                        </code>
                      );
                    },
                  }}
                >
                  {streamingContent}
                </Markdown>
              </div>
            </div>
          )}
          {loading && !streamingContent && (
            <div
              style={{
                alignSelf: "flex-start",
                color: "#888",
                fontSize: "0.9em",
                marginTop: "10px",
              }}
            >
              <span className="pulse">Thinking...</span>
            </div>
          )}
          <div ref={messagesEndRef} />
        </div>
      </div>

      <div
        style={{
          padding: "24px",
          background: "#171717",
          display: "flex",
          justifyContent: "center",
        }}
      >
        <div
          style={{
            maxWidth: "768px",
            width: "100%",
            display: "flex",
            gap: "8px",
            alignItems: "center",
          }}
        >
          <input
            ref={inputRef}
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter") {
                sendMessage();
              }
            }}
            placeholder="Send a message..."
            style={{
              flex: 1,
              padding: "14px 20px",
              borderRadius: "24px",
              border: "1px solid #333",
              outline: "none",
              fontSize: "1rem",
              fontWeight: "500",
              background: "#2f2f2f",
              color: "#ececec",
              boxShadow: "0 2px 6px rgba(0,0,0,0.02)",
            }}
          />
          <button
            type="button"
            onClick={loading ? cancelGeneration : () => sendMessage()}
            disabled={!loading && !input.trim()}
            style={{
              background: "#ececec",
              color: "black",
              border: "none",
              borderRadius: "50%",
              width: "32px",
              height: "32px",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              cursor: "pointer",
              opacity: !loading && !input.trim() ? 0.5 : 1,
              flexShrink: 0,
            }}
          >
            {loading ? "■" : "↑"}
          </button>
        </div>
      </div>

      {showApiKeyDialog && (
        <div
          style={{
            position: "fixed",
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            backgroundColor: "rgba(0, 0, 0, 0.7)",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            zIndex: 1000,
          }}
        >
          <div
            style={{
              backgroundColor: "#2f2f2f",
              padding: "32px",
              borderRadius: "12px",
              maxWidth: "500px",
              width: "90%",
              border: "1px solid #444",
            }}
          >
            <h2 style={{ marginTop: 0, color: "#ececec" }}>
              Enter Anthropic API Key
            </h2>
            <p
              style={{ color: "#aaa", fontSize: "0.9em", marginBottom: "20px" }}
            >
              To use Claude models, please enter your Anthropic API key. Your
              key will be stored server-side and reused across sessions.
            </p>
            <input
              type="password"
              value={apiKeyInput}
              onChange={(e) => setApiKeyInput(e.target.value)}
              onKeyDown={(e) => e.key === "Enter" && handleSaveApiKey()}
              placeholder="sk-ant-..."
              style={{
                width: "100%",
                padding: "12px",
                borderRadius: "8px",
                border: "1px solid #555",
                backgroundColor: "#1a1a1a",
                color: "#ececec",
                fontSize: "1em",
                marginBottom: "20px",
                outline: "none",
              }}
            />
            <div
              style={{
                display: "flex",
                gap: "12px",
                justifyContent: "flex-end",
              }}
            >
              <button
                type="button"
                onClick={() => {
                  setShowApiKeyDialog(false);
                  setApiKeyInput("");
                  pendingMessageRef.current = "";
                }}
                style={{
                  padding: "10px 20px",
                  borderRadius: "8px",
                  border: "1px solid #555",
                  backgroundColor: "transparent",
                  color: "#aaa",
                  cursor: "pointer",
                  fontSize: "0.9em",
                }}
              >
                Cancel
              </button>
              <button
                type="button"
                onClick={handleSaveApiKey}
                disabled={!apiKeyInput.trim()}
                style={{
                  padding: "10px 20px",
                  borderRadius: "8px",
                  border: "none",
                  backgroundColor: apiKeyInput.trim() ? "#ececec" : "#555",
                  color: apiKeyInput.trim() ? "#000" : "#888",
                  cursor: apiKeyInput.trim() ? "pointer" : "not-allowed",
                  fontSize: "0.9em",
                }}
              >
                Save Key
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
