import { useEffect, useMemo, useRef, useState, useCallback } from "react"
import {
  Loader2,
  Code,
  FileText,
  GitPullRequest,
  Brain,
  ChevronDown,
  Sparkles,
} from "lucide-react"
import { useVirtualizer } from "@tanstack/react-virtual"
import { useQueryClient } from "@tanstack/react-query"
import { useMessagesQuery } from "@/hooks/useAiChatQuery"
import { useStreamingStore } from "@/store/streaming"
import type { StreamPart } from "@/store/streaming"
import { ChatMessageBubble } from "./ChatMessageBubble"
import { useChatPage } from "./ChatPageContext"
import { getModelIcon } from "@/lib/icons/modelIcons"
import { IrRenderer } from "@/lib/ir/renderer"
import { Shimmer } from "@/components/ai-elements/shimmer"
import {
  Reasoning,
  ReasoningTrigger,
  ReasoningContent,
} from "@/components/ai-elements/reasoning"
import { ToolCallBlock } from "@/components/chat/ToolCallBlock"
import { useCodePreview } from "@/components/chat/CodePreviewContext"
import {
  Empty,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from "@/components/ui/empty"
import { t } from "@/i18n/T"

interface ChatMessageListProps {
  conversationId: string | null
  setIsStreaming: (value: boolean) => void
}

const OVERSCAN = 3
const ESTIMATED_SIZE = 200

const PROSE_CLASS =
  "prose prose-sm dark:prose-invert max-w-none [&_p]:leading-[1.55] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1 [&_li]:my-0 [&_h1]:mt-2 [&_h2]:mt-2 [&_h3]:mt-2 [&_pre]:my-1.5 [&_blockquote]:my-1"

const PROMPT_SUGGESTIONS = [
  { icon: Code, text: t("chat.message_list.explain_code") },
  { icon: FileText, text: t("chat.message_list.summarize_doc") },
  { icon: GitPullRequest, text: t("chat.message_list.review_pr") },
  { icon: Brain, text: t("chat.message_list.brainstorm") },
]

export function ChatMessageList({
  conversationId,
  setIsStreaming,
}: ChatMessageListProps) {
  const { data, isLoading } = useMessagesQuery(conversationId || "")
  const scrollRef = useRef<HTMLDivElement>(null)
  const messages = useMemo(() => data?.messages ?? [], [data?.messages])
  const stream = useStreamingStore((s) =>
    conversationId ? s.streams[conversationId] : undefined
  )
  const isStreaming = stream && !stream.isDone
  const queryClient = useQueryClient()
  const codePreview = useCodePreview()

  const [isAtBottom, setIsAtBottom] = useState(true)
  const [userScrolledUp, setUserScrolledUp] = useState(false)
  const [activeUserAnchor, setActiveUserAnchor] = useState(0)

  const checkAtBottom = useCallback(() => {
    const el = scrollRef.current
    if (!el) return
    const distance = el.scrollHeight - el.scrollTop - el.clientHeight
    const atBottom = distance < 200
    setIsAtBottom(atBottom)
    if (atBottom) setUserScrolledUp(false)
  }, [])

  const userAnchors = useMemo(() => {
    return messages
      .map((message, index) => ({ message, index }))
      .filter(({ message }) => message.role === "user")
      .map(({ index }) => index)
  }, [messages])
  const showUserTimeline = userAnchors.length > 0 && !codePreview?.activeCode

  const hasStreamingBubble = !!stream?.parts && stream.parts.length > 0
  const streamContentLength =
    stream?.parts.reduce((sum, part) => sum + part.content.length, 0) ?? 0

  // eslint-disable-next-line react-hooks/incompatible-library
  const virtualizer = useVirtualizer({
    count: messages.length,
    getScrollElement: () => scrollRef.current,
    estimateSize: () => ESTIMATED_SIZE,
    overscan: OVERSCAN,
  })

  const updateActiveUserAnchor = useCallback(() => {
    if (messages.length === 0 || userAnchors.length === 0) {
      setActiveUserAnchor(0)
      return
    }
    const visibleItems = virtualizer.getVirtualItems()
    if (visibleItems.length === 0) return
    const firstVisible = visibleItems[0]?.index ?? 0
    let closestAnchor = 0
    for (let i = 0; i < userAnchors.length; i++) {
      if (userAnchors[i] <= firstVisible) {
        closestAnchor = i
      } else {
        break
      }
    }
    setActiveUserAnchor(closestAnchor)
  }, [messages.length, userAnchors, virtualizer])

  const handleScroll = useCallback(() => {
    const el = scrollRef.current
    if (!el) return
    const distance = el.scrollHeight - el.scrollTop - el.clientHeight
    const atBottom = distance < 200
    setIsAtBottom(atBottom)
    if (!atBottom) setUserScrolledUp(true)
    updateActiveUserAnchor()
  }, [updateActiveUserAnchor])

  useEffect(() => {
    updateActiveUserAnchor()
  }, [updateActiveUserAnchor, streamContentLength])

  useEffect(() => {
    if (isAtBottom && scrollRef.current) {
      requestAnimationFrame(() => {
        if (scrollRef.current) {
          scrollRef.current.scrollTop = scrollRef.current.scrollHeight
        }
      })
    }
  }, [messages.length, streamContentLength, isAtBottom])

  useEffect(() => {
    if (conversationId && messages.length > 0) {
      checkAtBottom()
    }
  }, [conversationId, messages.length, checkAtBottom])

  useEffect(() => {
    if (scrollRef.current && messages.length > 0) {
      scrollRef.current.scrollTop = scrollRef.current.scrollHeight
    }
  }, [isLoading, messages.length])

  if (!conversationId || (messages.length === 0 && !hasStreamingBubble)) {
    return (
      <div
        ref={scrollRef}
        className="flex-1 overflow-y-auto px-4 py-8"
        style={{ backgroundColor: "var(--surface-ground)" }}
      >
        <div className="mx-auto flex max-w-3xl flex-col items-center gap-6">
          <Empty className="border border-[var(--border-subtle)] bg-[var(--surface-secondary)]/70 py-10">
            <EmptyHeader>
              <EmptyMedia variant="icon">
                <Sparkles />
              </EmptyMedia>
              <EmptyTitle>{t("chat.message_list.welcome_title")}</EmptyTitle>
              <EmptyDescription>
                {t("chat.message_list.welcome_desc")}
              </EmptyDescription>
            </EmptyHeader>
          </Empty>

          <div className="grid w-full gap-3 sm:grid-cols-2">
            {PROMPT_SUGGESTIONS.map((s, i) => (
              <button
                key={i}
                className="flex items-start gap-2.5 rounded-2xl border border-[var(--border-subtle)] bg-[var(--surface-ground)] p-4 text-left transition-all duration-200 hover:-translate-y-0.5 hover:border-[var(--border-default)] hover:bg-[var(--surface-elevated)] hover:shadow-sm"
                style={{
                  color: "var(--text-secondary)",
                }}
              >
                <s.icon
                  className="mt-0.5 size-4 shrink-0"
                  style={{ color: "var(--text-muted)" }}
                />
                <span className="text-[13px] leading-relaxed">{s.text}</span>
              </button>
            ))}
          </div>
        </div>
      </div>
    )
  }

  if (isLoading) {
    return (
      <div
        className="flex flex-1 items-center justify-center"
        style={{ backgroundColor: "var(--surface-ground)" }}
      >
        <Loader2
          className="size-5 animate-spin"
          style={{ color: "var(--accent)" }}
        />
      </div>
    )
  }

  return (
    <div
      className="relative flex min-h-0 flex-1 flex-col"
      style={{ backgroundColor: "var(--surface-ground)" }}
    >
      {isStreaming && userScrolledUp && (
        <div
          className="absolute bottom-3 left-1/2 z-10 -translate-x-1/2 cursor-pointer rounded-full border px-4 py-1.5 shadow-lg transition-all hover:scale-105"
          style={{
            backgroundColor: "var(--surface-elevated)",
            borderColor: "var(--border-subtle)",
          }}
          onClick={() => {
            if (scrollRef.current) {
              scrollRef.current.scrollTo({
                top: scrollRef.current.scrollHeight,
                behavior: "smooth",
              })
              setUserScrolledUp(false)
              setIsAtBottom(true)
            }
          }}
        >
          <div className="flex items-center gap-2">
            <Shimmer duration={1}>
              {t("chat.message_list.new_response")}
            </Shimmer>
            <ChevronDown
              className="size-3.5"
              style={{ color: "var(--text-muted)" }}
            />
          </div>
        </div>
      )}

      <div
        ref={scrollRef}
        className="app-scrollbar flex-1 overflow-y-auto pb-24"
        onScroll={handleScroll}
        style={{ backgroundColor: "var(--surface-ground)" }}
      >
        {showUserTimeline && (
          <div className="pointer-events-none absolute top-6 bottom-28 left-4 z-10 hidden w-4 md:block">
            <div className="relative h-full w-full">
              <div
                className="absolute top-0 left-1/2 h-full w-px -translate-x-1/2"
                style={{ backgroundColor: "var(--border-default)" }}
              />
              <div className="relative flex h-full flex-col items-center justify-between py-1">
                {userAnchors.map((messageIndex, anchorIndex) => {
                  const isActive = anchorIndex === activeUserAnchor
                  return (
                    <button
                      key={`${messageIndex}-${anchorIndex}`}
                      onClick={() => {
                        virtualizer.scrollToIndex(messageIndex, {
                          align: "center",
                          behavior: "smooth",
                        })
                      }}
                      className="pointer-events-auto flex size-4 items-center justify-center rounded-full transition-all"
                      title={t("chat.message_list.jump_to_message", {
                        index: anchorIndex + 1,
                      })}
                      aria-label={t("chat.message_list.jump_to_message", {
                        index: anchorIndex + 1,
                      })}
                    >
                      <span
                        className="block rounded-full transition-all"
                        style={{
                          width: isActive ? 7 : 5,
                          height: isActive ? 7 : 5,
                          backgroundColor: isActive
                            ? "var(--accent)"
                            : "var(--border-strong)",
                          boxShadow: "0 0 0 2px var(--surface-ground)",
                          opacity: isActive ? 1 : 0.72,
                        }}
                      />
                    </button>
                  )
                })}
              </div>
            </div>
          </div>
        )}
        <div
          className="relative mx-auto max-w-3xl"
          style={{ height: `${virtualizer.getTotalSize()}px` }}
        >
          {virtualizer.getVirtualItems().map((virtualItem) => {
            const message = messages[virtualItem.index]
            if (!message) return null

            return (
              <div
                key={virtualItem.key}
                data-index={virtualItem.index}
                ref={virtualizer.measureElement}
                className="absolute top-0 left-0 w-full"
                style={{
                  transform: `translateY(${virtualItem.start}px)`,
                }}
              >
                <ChatMessageBubble
                  message={message}
                  conversationId={conversationId}
                  onRegenerate={() => {
                    queryClient.invalidateQueries({
                      queryKey: ["ai-messages", conversationId],
                    })
                    queryClient.invalidateQueries({
                      queryKey: ["ai-conversations", conversationId],
                    })
                  }}
                  setIsStreaming={setIsStreaming}
                />
              </div>
            )
          })}
        </div>

        {hasStreamingBubble && (
          <div className="mx-auto w-full max-w-3xl">
            <StreamingBubble
              parts={stream!.parts}
              isDone={stream!.isDone}
              conversationId={conversationId}
            />
          </div>
        )}
      </div>
    </div>
  )
}

function StreamingBubble({
  parts,
  isDone,
  conversationId,
}: {
  parts: StreamPart[]
  isDone: boolean
  conversationId: string
}) {
  const { selectedModel } = useChatPage()
  const [displayParts, setDisplayParts] = useState<StreamPart[]>([])
  const [displayDone, setDisplayDone] = useState(false)
  const contentRef = useRef<HTMLDivElement>(null)
  const latestRef = useRef({ parts, isDone })
  const rafRef = useRef<number>(0)

  useEffect(() => {
    latestRef.current = { parts, isDone }
  })

  const hasParts = parts.length > 0
  useEffect(() => {
    if (!hasParts) return
    const sync = () => {
      const { parts: p, isDone: d } = latestRef.current
      setDisplayParts([...p])
      setDisplayDone(d)
      if (!d) {
        rafRef.current = requestAnimationFrame(sync)
      }
    }
    rafRef.current = requestAnimationFrame(sync)
    return () => cancelAnimationFrame(rafRef.current)
  }, [hasParts])

  useEffect(() => {
    if (contentRef.current) {
      contentRef.current.style.height = "auto"
      requestAnimationFrame(() => {
        if (contentRef.current) {
          contentRef.current.style.height = ""
        }
      })
    }
  }, [displayParts.length])

  const activeThinkingIdx =
    !displayDone && displayParts[displayParts.length - 1]?.type === "thinking"
      ? displayParts.length - 1
      : -1

  return (
    <div
      ref={contentRef}
      className="mx-auto flex w-full max-w-3xl gap-4 rounded-2xl border border-[var(--border-subtle)] bg-[var(--surface-secondary)]/70 px-4 py-3 shadow-sm backdrop-blur"
    >
      <div className="shrink-0 pt-0.5">
        <StreamingModelAvatar modelName={selectedModel?.model_name} size={28} />
      </div>
      <div className="min-w-0 flex-1">
        <div className="mb-1 flex items-center gap-2">
          <span
            className="text-xs font-semibold"
            style={{ color: "var(--text-primary)" }}
          >
            {selectedModel?.model_name || "Assistant"}
          </span>
          {!displayDone && (
            <span
              className="animate-pulse text-[11px]"
              style={{ color: "var(--text-muted)" }}
            >
              {t("chat.message_list.responding")}
            </span>
          )}
        </div>

        <div className="text-sm" style={{ color: "var(--text-primary)" }}>
          {displayParts.map((part, i) => {
            if (part.type === "thinking") {
              return (
                <StreamingReasoningBlock
                  key={i}
                  content={part.content}
                  isActivelyThinking={i === activeThinkingIdx}
                />
              )
            }
            if (part.type === "tool_call") {
              if (part.toolName !== "call_sub_agent") return null
              return (
                <ToolCallBlock
                  key={i}
                  toolName={part.toolName || "unknown"}
                  args={part.toolArgs || {}}
                  status={displayDone ? "ok" : "pending"}
                  childrenId={part.children_id}
                  subAgentOutput={part.subAgentOutput}
                  conversationId={conversationId}
                />
              )
            }
            if (part.type === "tool_result") {
              if (part.toolName !== "call_sub_agent") return null
              return (
                <ToolCallBlock
                  key={i}
                  toolName={part.toolName || "unknown"}
                  args={part.toolArgs || {}}
                  status={part.toolStatus || "ok"}
                  result={part.content}
                  childrenId={part.children_id}
                  subAgentOutput={part.subAgentOutput}
                  conversationId={conversationId}
                />
              )
            }
            const isLast = i === displayParts.length - 1
            return (
              <div key={i}>
                <IrRenderer nodes={part.irNodes} className={PROSE_CLASS} />
                {isLast && !displayDone && <StreamingCursor />}
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}

function StreamingReasoningBlock({
  content,
  isActivelyThinking,
}: {
  content: string
  isActivelyThinking: boolean
}) {
  const [manualOpen, setManualOpen] = useState(false)
  const [autoCollapsed, setAutoCollapsed] = useState(false)
  const wasActivelyThinkingRef = useRef(isActivelyThinking)

  useEffect(() => {
    const wasActivelyThinking = wasActivelyThinkingRef.current

    if (isActivelyThinking && !wasActivelyThinking) {
      setAutoCollapsed(false)
      setManualOpen(false)
    }

    if (!isActivelyThinking && wasActivelyThinking) {
      setAutoCollapsed(false)
      setManualOpen(false)
    }

    wasActivelyThinkingRef.current = isActivelyThinking
  }, [isActivelyThinking])

  const isOpen = isActivelyThinking ? !autoCollapsed : manualOpen

  const handleOpenChange = useCallback(
    (nextOpen: boolean) => {
      if (isActivelyThinking) {
        setAutoCollapsed(!nextOpen)
        return
      }
      setManualOpen(nextOpen)
    },
    [isActivelyThinking]
  )

  return (
    <Reasoning
      autoLifecycle={false}
      isStreaming={isActivelyThinking}
      onOpenChange={handleOpenChange}
      open={isOpen}
    >
      <ReasoningTrigger />
      <ReasoningContent>{content}</ReasoningContent>
    </Reasoning>
  )
}

function StreamingCursor() {
  return (
    <span
      className="ml-[1px] inline-block h-[1.1em] w-[2px] animate-pulse rounded-sm align-text-bottom"
      style={{
        backgroundColor: "var(--accent)",
        animationDuration: "0.8s",
      }}
    />
  )
}

const AVATAR_COLORS = [
  "#6366f1",
  "#8b5cf6",
  "#d946ef",
  "#ec4899",
  "#f43f5e",
  "#ef4444",
  "#f97316",
  "#eab308",
  "#22c55e",
  "#14b8a6",
  "#06b6d4",
  "#3b82f6",
  "#2563eb",
  "#7c3aed",
  "#c026d3",
]

function hashColor(str: string): string {
  let hash = 0
  for (let i = 0; i < str.length; i++) {
    hash = str.charCodeAt(i) + ((hash << 5) - hash)
  }
  return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length]
}

function StreamingModelAvatar({
  modelName,
  size = 28,
}: {
  modelName?: string | null
  size?: number
}) {
  if (!modelName) {
    return (
      <div
        className="flex shrink-0 items-center justify-center rounded-full"
        style={{
          width: size,
          height: size,
          backgroundColor: "var(--accent-muted)",
        }}
      >
        <Sparkles
          className="h-3.5 w-3.5"
          style={{
            color: "var(--accent)",
            width: size * 0.5,
            height: size * 0.5,
          }}
        />
      </div>
    )
  }
  const Icon = getModelIcon(modelName)
  if (Icon) {
    return (
      <div className="shrink-0" style={{ width: size, height: size }}>
        <Icon.Avatar size={size} />
      </div>
    )
  }
  return (
    <div
      className="flex shrink-0 items-center justify-center rounded-full font-bold"
      style={{
        width: size,
        height: size,
        backgroundColor: hashColor(modelName),
        fontSize: Math.max(10, size * 0.35),
        color: "var(--text-inverse)",
      }}
    >
      {modelName[0]?.toUpperCase() || "?"}
    </div>
  )
}
