import {
  useState,
  useRef,
  useEffect,
  useCallback,
  useLayoutEffect,
} from "react";
import {
  Send,
  Square,
  Plus,
  Lightbulb,
  LightbulbOff,
  ChevronDown,
  Settings,
  X,
} from "lucide-react";

import { useLLM } from "../hooks/useLLM";
import { MessageBubble } from "./MessageBubble";
import { MODEL_CONFIG } from "../model-config";
import type { ThinkingMode } from "../hooks/LLMContext";

const TEXTAREA_MIN_HEIGHT = "7.5rem";

const THINKING_OPTIONS: { value: ThinkingMode; label: string }[] = [
  { value: "enabled", label: "Reasoning On" },
  { value: "disabled", label: "Reasoning Off" },
];

export function ChatApp() {
  const {
    messages,
    isGenerating,
    tps,
    send,
    stop,
    status,
    clearChat,
    thinkingMode,
    setThinkingMode,
    systemPrompt,
    setSystemPrompt,
  } = useLLM();

  const [showSettings, setShowSettings] = useState(false);

  const [input, setInput] = useState("");
  const scrollRef = useRef<HTMLElement>(null);
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const [showThinkingMenu, setShowThinkingMenu] = useState(false);
  const thinkingMenuRef = useRef<HTMLDivElement>(null);
  const [thinkingSeconds, setThinkingSeconds] = useState(0);
  const thinkingStartRef = useRef<number | null>(null);
  const thinkingSecondsMapRef = useRef<Map<number, number>>(new Map());
  const prevIsGeneratingRef = useRef(false);
  const messagesRef = useRef(messages);
  const thinkingSecondsRef = useRef(thinkingSeconds);

  messagesRef.current = messages;
  thinkingSecondsRef.current = thinkingSeconds;

  const isReady = status.state === "ready";
  const hasMessages = messages.length > 0;

  const hasCompletedRef = useRef(false);
  useEffect(() => {
    if (hasMessages && !isGenerating) hasCompletedRef.current = true;
    if (!hasMessages) hasCompletedRef.current = false;
  }, [hasMessages, isGenerating]);

  const showNewChat =
    isReady && hasMessages && !isGenerating && hasCompletedRef.current;

  const prevMsgCountRef = useRef(0);
  const lastUserRef = useRef<HTMLDivElement>(null);
  const bottomSpacerRef = useRef<HTMLDivElement>(null);
  const userHasScrolledRef = useRef(false);

  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (
        thinkingMenuRef.current &&
        !thinkingMenuRef.current.contains(event.target as Node)
      ) {
        setShowThinkingMenu(false);
      }
    };

    document.addEventListener("mousedown", handleClickOutside);
    return () => document.removeEventListener("mousedown", handleClickOutside);
  }, []);

  useEffect(() => {
    if (prevIsGeneratingRef.current && !isGenerating) {
      const lastMessage = messagesRef.current.at(-1);
      if (
        lastMessage?.role === "assistant" &&
        lastMessage.reasoning &&
        thinkingSecondsRef.current > 0
      ) {
        thinkingSecondsMapRef.current.set(
          lastMessage.id,
          thinkingSecondsRef.current,
        );
      }
    }
    prevIsGeneratingRef.current = isGenerating;
  }, [isGenerating]);

  useEffect(() => {
    if (!isGenerating) {
      thinkingStartRef.current = null;
      return;
    }

    thinkingStartRef.current = Date.now();
    setThinkingSeconds(0);

    const interval = setInterval(() => {
      if (thinkingStartRef.current) {
        setThinkingSeconds(
          Math.round((Date.now() - thinkingStartRef.current) / 1000),
        );
      }
    }, 500);

    return () => clearInterval(interval);
  }, [isGenerating]);

  const lastAssistant = messages.at(-1);
  useEffect(() => {
    if (
      isGenerating &&
      lastAssistant?.role === "assistant" &&
      lastAssistant.content
    ) {
      thinkingStartRef.current = null;
    }
  }, [isGenerating, lastAssistant?.role, lastAssistant?.content]);

  const getContainerPadTop = useCallback(() => {
    const container = scrollRef.current;
    if (!container) return 0;
    return parseFloat(getComputedStyle(container).paddingTop) || 0;
  }, []);

  const recalcSpacer = useCallback(() => {
    const container = scrollRef.current;
    const userElement = lastUserRef.current;
    const spacer = bottomSpacerRef.current;
    if (!container || !userElement || !spacer) return;

    // Compute the user element's offset from the top of the scroll content
    // (immune to browser scrollTop clamping).
    const userOffsetInContent =
      userElement.getBoundingClientRect().top -
      container.getBoundingClientRect().top +
      container.scrollTop;

    const padTop = getContainerPadTop();
    const padBottom =
      parseFloat(getComputedStyle(container).paddingBottom) || 0;
    const usableHeight = container.clientHeight - padTop - padBottom;

    // Distance from user element top to the spacer top (everything between them).
    const contentBelowUser =
      spacer.getBoundingClientRect().top -
      userElement.getBoundingClientRect().top;

    spacer.style.height = `${Math.max(0, usableHeight - contentBelowUser)}px`;

    // Only re-anchor if the user hasn't manually scrolled away.
    if (!userHasScrolledRef.current) {
      const desiredScrollTop = userOffsetInContent - padTop;
      if (Math.abs(container.scrollTop - desiredScrollTop) > 0.5) {
        container.scrollTo({ top: desiredScrollTop, behavior: "smooth" });
      }
    }
  }, [getContainerPadTop]);

  useLayoutEffect(() => {
    recalcSpacer();

    const isNewMessage = messages.length > prevMsgCountRef.current;
    prevMsgCountRef.current = messages.length;

    if (isNewMessage) {
      // Reset manual-scroll flag so the initial snap works for the new message.
      userHasScrolledRef.current = false;

      const container = scrollRef.current;
      const userElement = lastUserRef.current;
      if (!container || !userElement) return;

      const scrollTarget =
        container.scrollTop +
        (userElement.getBoundingClientRect().top -
          container.getBoundingClientRect().top) -
        getContainerPadTop();
      container.scrollTo({ top: scrollTarget, behavior: "smooth" });
    }
  }, [messages, isGenerating, recalcSpacer, getContainerPadTop]);

  useEffect(() => {
    window.addEventListener("resize", recalcSpacer);
    return () => window.removeEventListener("resize", recalcSpacer);
  }, [recalcSpacer]);

  // Detect user-initiated scrolls (wheel or touch) to unlock free scrolling
  // during generation.
  useEffect(() => {
    const container = scrollRef.current;
    if (!container) return;

    const markScrolled = () => {
      if (isGenerating) {
        userHasScrolledRef.current = true;
      }
    };

    container.addEventListener("wheel", markScrolled, { passive: true });
    container.addEventListener("touchmove", markScrolled, { passive: true });
    return () => {
      container.removeEventListener("wheel", markScrolled);
      container.removeEventListener("touchmove", markScrolled);
    };
  }, [isGenerating]);

  // Re-anchor the user message whenever the scroll container's own size changes
  useLayoutEffect(() => {
    const container = scrollRef.current;
    if (!container) return;

    let lastHeight = container.clientHeight;
    const observer = new ResizeObserver(() => {
      const h = container.clientHeight;
      if (h !== lastHeight) {
        lastHeight = h;
        recalcSpacer();
      }
    });
    observer.observe(container);
    return () => observer.disconnect();
  }, [recalcSpacer]);

  const handleSubmit = useCallback(
    (event?: React.FormEvent) => {
      event?.preventDefault();
      const text = input.trim();
      if (!text || !isReady || isGenerating) return;
      setInput("");
      if (textareaRef.current) {
        textareaRef.current.style.height = TEXTAREA_MIN_HEIGHT;
      }
      send(text);
    },
    [input, isReady, isGenerating, send],
  );

  const handleInputKeyDown = useCallback(
    (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
      if (event.key === "Enter" && !event.shiftKey) {
        event.preventDefault();
        handleSubmit();
      }
    },
    [handleSubmit],
  );

  const lastUserIndex = messages.findLastIndex(
    (message) => message.role === "user",
  );

  const renderInputArea = (showDisclaimer: boolean) => (
    <div className="w-full">
      <form onSubmit={handleSubmit} className="mx-auto max-w-3xl">
        <div className="relative">
          <textarea
            ref={textareaRef}
            className="w-full rounded-xl px-4 py-3 pb-11 text-[15px] placeholder-[#555] disabled:opacity-50 resize-none max-h-40 shadow-sm focus:outline-none"
          style={{ border: "1px solid rgba(0,255,255,0.15)", background: "rgba(10,10,28,0.95)", color: "#e0e0f0", minHeight: TEXTAREA_MIN_HEIGHT, height: TEXTAREA_MIN_HEIGHT }}
          onFocus={e => (e.currentTarget.style.borderColor = "rgba(0,255,255,0.5)")}
          onBlur={e => (e.currentTarget.style.borderColor = "rgba(0,255,255,0.15)")}
            placeholder={isReady ? "Type a message…" : "Loading model…"}
            value={input}
            onChange={(event) => {
              setInput(event.target.value);
              event.target.style.height = TEXTAREA_MIN_HEIGHT;
              event.target.style.height =
                Math.max(event.target.scrollHeight, 120) + "px";
            }}
            onKeyDown={handleInputKeyDown}
            disabled={!isReady}
            autoFocus
          />

          <div className="absolute bottom-2 left-2 right-2 flex items-center justify-between pb-3 px-2">
            <div ref={thinkingMenuRef} className="relative">
              <button
                type="button"
                onClick={() => setShowThinkingMenu((value) => !value)}
                className="flex items-center gap-1 rounded-lg text-xs text-[#888888] hover:text-[#f0f0f0] transition-colors cursor-pointer"
                title="Reasoning mode"
              >
                {thinkingMode === "enabled" ? (
                  <Lightbulb className="h-3.5 w-3.5 text-[#76b900]" />
                ) : (
                  <LightbulbOff className="h-3.5 w-3.5 text-[#888888]" />
                )}
                <span>
                  {
                    THINKING_OPTIONS.find(
                      (option) => option.value === thinkingMode,
                    )?.label
                  }
                </span>
                <ChevronDown className="h-3 w-3" />
              </button>

              {showThinkingMenu && (
                <div className="absolute bottom-full left-0 mb-1 min-w-[160px] rounded-lg border border-[#2a2a2a] bg-[#1a1a1a] py-1 shadow-xl z-50">
                  {THINKING_OPTIONS.map((option) => (
                    <button
                      key={option.value}
                      type="button"
                      onClick={() => {
                        setThinkingMode(option.value);
                        setShowThinkingMenu(false);
                      }}
                      className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs transition-colors cursor-pointer ${
                        thinkingMode === option.value
                          ? "bg-[#222222] text-[#f0f0f0]"
                          : "text-[#888888] hover:bg-[#222222] hover:text-[#f0f0f0]"
                      }`}
                    >
                      {option.value === "enabled" ? (
                        <Lightbulb className="h-3.5 w-3.5 text-[#76b900]" />
                      ) : (
                        <LightbulbOff className="h-3.5 w-3.5 text-[#888888]" />
                      )}
                      <span>{option.label}</span>
                    </button>
                  ))}
                </div>
              )}
            </div>

            {isGenerating ? (
              <button
                type="button"
                onClick={stop}
                className="flex items-center justify-center rounded-lg text-[#76b900] hover:text-[#8fd400] transition-colors cursor-pointer"
                title="Stop generating"
              >
                <Square className="h-4 w-4 fill-current" />
              </button>
            ) : (
              <button
                type="submit"
                disabled={!isReady || !input.trim()}
                className="flex items-center justify-center rounded-lg text-[#76b900] hover:text-[#8fd400] disabled:opacity-30 transition-colors cursor-pointer"
                title="Send message"
              >
                <Send className="h-4 w-4" />
              </button>
            )}
          </div>
        </div>
      </form>

      {showDisclaimer && (
        <p className="mx-auto max-w-3xl mt-1 text-center text-xs text-[#888888]">
          No chats are sent to a server. Everything runs locally in your
          browser. AI can make mistakes. Check important info.
        </p>
      )}
    </div>
  );

  return (
    <div className="flex h-full flex-col text-[#e0e0f0]" style={{ background: "#050515" }}>
      <header className="flex-none flex items-center justify-between px-6 py-3 h-14" style={{ borderBottom: "1px solid rgba(0,255,255,0.12)", background: "rgba(5,5,21,0.9)" }}>
        <div className="flex items-center gap-3">
          <img src="/vexorium_logo.svg" alt="Vexorium" style={{ width: "28px", height: "28px" }} />
          <span
            style={{
              fontFamily: "'Orbitron', sans-serif",
              fontSize: "13px",
              fontWeight: 700,
              letterSpacing: "0.15em",
              background: "linear-gradient(90deg, #00ffff, #ff00ff)",
              WebkitBackgroundClip: "text",
              WebkitTextFillColor: "transparent",
              backgroundClip: "text",
              textTransform: "uppercase",
            }}
          >
            Vexorium
          </span>
          <span style={{ color: "rgba(0,255,255,0.35)", fontSize: "12px", fontFamily: "'Exo 2', sans-serif" }}>AI Interviewer · WebGPU</span>
        </div>
        <div className="flex items-center gap-3">
          <button
            onClick={() => setShowSettings(!showSettings)}
            className="flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs transition-colors cursor-pointer"
            style={{ color: "rgba(0,255,255,0.6)" }}
            onMouseEnter={e => (e.currentTarget.style.color = "#00ffff")}
            onMouseLeave={e => (e.currentTarget.style.color = "rgba(0,255,255,0.6)")}
            title="Settings"
          >
            <Settings className="h-3.5 w-3.5" />
          </button>
          <button
            onClick={clearChat}
            className={`flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs transition-opacity duration-300 cursor-pointer ${
              showNewChat ? "opacity-100" : "opacity-0 pointer-events-none"
            }`}
            style={{ color: "rgba(0,255,255,0.6)" }}
            onMouseEnter={e => (e.currentTarget.style.color = "#00ffff")}
            onMouseLeave={e => (e.currentTarget.style.color = "rgba(0,255,255,0.6)")}
            title="New chat"
          >
            <Plus className="h-3.5 w-3.5" />
            New chat
          </button>
        </div>
      </header>

      {showSettings && (
        <div className="absolute inset-0 z-50 flex items-center justify-center" style={{ background: "rgba(5,5,21,0.75)", backdropFilter: "blur(12px)" }}>
          <div className="w-full max-w-xl mx-4 rounded-xl p-6 shadow-2xl" style={{ border: "1px solid rgba(0,255,255,0.2)", background: "rgba(10,10,28,0.97)" }}>
            <div className="flex items-center justify-between mb-4">
              <h2 className="text-lg font-semibold" style={{ fontFamily: "'Orbitron', sans-serif", color: "#e0e0f0", letterSpacing: "0.08em" }}>Settings</h2>
              <button
                onClick={() => setShowSettings(false)}
                className="transition-colors cursor-pointer"
                style={{ color: "rgba(224,224,240,0.5)" }}
                onMouseEnter={e => (e.currentTarget.style.color = "#00ffff")}
                onMouseLeave={e => (e.currentTarget.style.color = "rgba(224,224,240,0.5)")}
              >
                <X className="h-5 w-5" />
              </button>
            </div>
            <div className="space-y-4">
              <div>
                <label className="block text-sm font-medium mb-2" style={{ fontFamily: "'Exo 2', sans-serif", color: "rgba(0,255,255,0.8)" }}>
                  System Prompt
                </label>
                <textarea
                  className="w-full rounded-lg px-3 py-2 text-sm resize-none focus:outline-none"
                  style={{
                    border: "1px solid rgba(0,255,255,0.2)",
                    background: "rgba(5,5,21,0.8)",
                    color: "#e0e0f0",
                    fontFamily: "'Exo 2', sans-serif",
                  }}
                  onFocus={e => (e.currentTarget.style.borderColor = "rgba(0,255,255,0.6)")}
                  onBlur={e => (e.currentTarget.style.borderColor = "rgba(0,255,255,0.2)")}
                  rows={6}
                  value={systemPrompt}
                  onChange={(e) => setSystemPrompt(e.target.value)}
                  placeholder="Enter system prompt..."
                />
                <p className="mt-1 text-xs" style={{ color: "rgba(224,224,240,0.35)", fontFamily: "'Exo 2', sans-serif" }}>
                  Customize the interviewer's personality and style. Changes apply to new messages.
                </p>
              </div>
              <div className="flex gap-2">
                <button
                  onClick={() => setSystemPrompt(MODEL_CONFIG.defaultSystemPrompt)}
                  className="rounded-lg px-3 py-1.5 text-xs transition-colors cursor-pointer"
                  style={{ border: "1px solid rgba(0,255,255,0.2)", color: "rgba(224,224,240,0.6)", fontFamily: "'Exo 2', sans-serif" }}
                  onMouseEnter={e => { (e.currentTarget as HTMLElement).style.borderColor = "rgba(0,255,255,0.5)"; (e.currentTarget as HTMLElement).style.color = "#e0e0f0"; }}
                  onMouseLeave={e => { (e.currentTarget as HTMLElement).style.borderColor = "rgba(0,255,255,0.2)"; (e.currentTarget as HTMLElement).style.color = "rgba(224,224,240,0.6)"; }}
                >
                  Reset to default
                </button>
                <button
                  onClick={() => setShowSettings(false)}
                  className="rounded-lg px-3 py-1.5 text-xs font-semibold cursor-pointer transition-all vex-glow"
                  style={{
                    fontFamily: "'Orbitron', sans-serif",
                    background: "linear-gradient(135deg, rgba(0,255,255,0.15), rgba(255,0,255,0.15))",
                    border: "1px solid rgba(0,255,255,0.5)",
                    color: "#00ffff",
                    letterSpacing: "0.06em",
                  }}
                >
                  Done
                </button>
              </div>
            </div>
          </div>
        </div>
      )}

      {isReady && !hasMessages ? (
        <div className="flex flex-1 flex-col items-center justify-center px-4">
          <div className="mb-8 text-center">
            <p className="text-3xl font-medium text-[#f0f0f0]">
              What can I help you with?
            </p>
          </div>

          {renderInputArea(false)}

          <div className="mt-6 flex flex-wrap justify-center gap-3 max-w-3xl">
            {MODEL_CONFIG.examplePrompts.map(({ label, prompt }) => (
              <button
                key={label}
                onClick={() => send(prompt)}
                className="relative rounded-[14px] border-[2px] border-[rgba(220,210,180,0.3)] bg-[linear-gradient(160deg,rgba(28,28,28,0.97)_0%,rgba(10,10,10,0.99)_100%)] px-4 py-2.5 text-xs text-[#888888] hover:text-[#f0f0f0] hover:border-[rgba(235,225,195,0.6)] hover:-translate-y-[2px] hover:shadow-[0_4px_16px_rgba(0,0,0,0.4),0_0_10px_rgba(220,210,180,0.05)] transition-all duration-300 cursor-pointer overflow-hidden before:pointer-events-none before:absolute before:inset-0 before:rounded-[14px] before:bg-[radial-gradient(ellipse_at_50%_0%,rgba(235,225,190,0.05)_0%,transparent_55%)]"
              >
                {label}
              </button>
            ))}
          </div>
        </div>
      ) : (
        <>
          <main
            ref={scrollRef}
            className="min-h-0 flex-1 overflow-y-auto px-4 py-6 animate-fade-in"
          >
            <div className="mx-auto flex max-w-3xl flex-col gap-6">
              {messages.map((message, index) => {
                const isLastAssistant =
                  index === messages.length - 1 && message.role === "assistant";
                const isLastUser =
                  message.role === "user" && index === lastUserIndex;

                return (
                  <div
                    key={message.id}
                    ref={isLastUser ? lastUserRef : undefined}
                  >
                    <MessageBubble
                      msg={message}
                      index={index}
                      isStreaming={isGenerating && isLastAssistant}
                      thinkingSeconds={
                        isLastAssistant
                          ? thinkingSeconds
                          : thinkingSecondsMapRef.current.get(message.id)
                      }
                      isGenerating={isGenerating}
                    />
                  </div>
                );
              })}
              <div ref={bottomSpacerRef} />
            </div>
          </main>

          <footer className="flex-none px-4 py-3 animate-fade-in">
            <p
              className={`mx-auto max-w-3xl mb-3 text-center text-xs font-mono tabular-nums h-4 ${isGenerating && tps > 0 ? "text-[#888888]" : "text-transparent"}`}
            >
              {isGenerating && tps > 0
                ? `${tps.toFixed(1)} tokens/s`
                : "\u00A0"}
            </p>
            {renderInputArea(true)}
          </footer>
        </>
      )}
    </div>
  );
}
