import type { ChatAttachment, ChatMessage } from "@qjclaw/shared-types"
import { useEffect, useMemo, useState, type ReactNode, type RefObject, type UIEvent } from "react"
import { desktopApi } from "../../lib/desktop-api"
import { getTraceDisplayLines, getTraceLineClassName, getTraceStripTitle } from "./messageTraceDisplay"
import type { MessageTraceState } from "./useMessageTraces"

type ViewMode = "chat" | "experts" | "tasks" | "plugins" | "settings" | "knowledge"
type MessageReaction = "up" | "down"
export type ExpertKey = "xiaohongshu" | "douyin" | "browser" | "general"

export type MessageListMessage = ChatMessage & {
  streamState?: "streaming" | "error"
  statusLabel?: string
  statusDetail?: string
}

interface VideoStatusCardContent {
  title: string
  meta?: string
  hint?: string
}

interface MessageListLabels {
  thinking: string
  hideTrace: string
  traceCollapsed: string
}

interface MessageListProps {
  messages: MessageListMessage[]
  viewMode: ViewMode
  showBindEntry: boolean
  showEmptyState: boolean
  emptyState: ReactNode
  messageListRef: RefObject<HTMLDivElement | null>
  messageTraces: Record<string, MessageTraceState>
  messageReactions: Record<string, MessageReaction | undefined>
  copiedToken: string
  sending: boolean
  activeExpertKey: ExpertKey
  labels: MessageListLabels
  copyIcon: ReactNode
  copiedIcon: ReactNode
  regenerateIcon: ReactNode
  renderThumbIcon: (direction: MessageReaction) => ReactNode
  renderMarkdownContent: (
    content: string,
    options: {
      messageId: string
      copiedToken: string
      onCopy: (token: string, text: string) => void | Promise<void>
    }
  ) => ReactNode[]
  buildDouyinVideoStatusCard: (message: MessageListMessage, expertKey: ExpertKey) => VideoStatusCardContent | null
  formatMessageTimestamp: (value: string) => string
  onScroll: (event: UIEvent<HTMLDivElement>) => void
  onCopyText: (token: string, text: string) => void | Promise<void>
  onTraceExpandedChange: (messageId: string, expanded: boolean) => void
  onRegenerateAssistantMessage: (messageId: string) => void | Promise<void>
  onToggleMessageReaction: (messageId: string, reaction: MessageReaction) => void
}

function getAttachmentKey(attachment: ChatAttachment, index: number): string {
  return `${attachment.localPath || attachment.name}:${index}`
}

function getAttachmentTypeLabel(attachment: ChatAttachment): string {
  const mimeType = attachment.mimeType?.toLowerCase() ?? ""
  const extension = attachment.name.split(".").pop()?.toLowerCase() ?? ""
  if (attachment.kind === "image" || mimeType.startsWith("image/")) {
    return "IMAGE"
  }
  if (mimeType.includes("pdf") || extension === "pdf") {
    return "PDF"
  }
  if (mimeType.includes("mpeg") || mimeType.includes("audio") || extension === "mp3") {
    return "MP3"
  }
  if (["doc", "docx"].includes(extension)) {
    return "DOC"
  }
  if (["xls", "xlsx", "csv", "tsv"].includes(extension)) {
    return "SHEET"
  }
  if (["ppt", "pptx"].includes(extension)) {
    return "PPT"
  }
  if (["txt", "md", "json"].includes(extension)) {
    return "TEXT"
  }
  return "FILE"
}

function AttachmentFileCard({ attachment }: { attachment: ChatAttachment }) {
  const label = getAttachmentTypeLabel(attachment)
  return (
    <div className="message-attachment-file-card" title={attachment.name}>
      <span className="message-attachment-file-icon" aria-hidden="true">
        <span />
      </span>
      <span className="message-attachment-file-body">
        <span className="message-attachment-file-name">{attachment.name}</span>
        <span className="message-attachment-file-type">{label}</span>
      </span>
    </div>
  )
}

function ImageAttachmentPreview({ attachment }: { attachment: ChatAttachment }) {
  const [previewUrl, setPreviewUrl] = useState<string | null>(null)
  const [previewFailed, setPreviewFailed] = useState(false)

  useEffect(() => {
    let cancelled = false
    setPreviewUrl(null)
    setPreviewFailed(false)

    void desktopApi.chat.readImageAttachmentDataUrl(attachment)
      .then((dataUrl) => {
        if (cancelled) {
          return
        }
        if (dataUrl) {
          setPreviewUrl(dataUrl)
        } else {
          setPreviewFailed(true)
        }
      })
      .catch(() => {
        if (!cancelled) {
          setPreviewFailed(true)
        }
      })

    return () => {
      cancelled = true
    }
  }, [attachment.kind, attachment.localPath, attachment.mimeType, attachment.name])

  if (!previewUrl || previewFailed) {
    return <AttachmentFileCard attachment={attachment} />
  }

  return (
    <figure className="message-attachment-image">
      <img src={previewUrl} alt={attachment.name} loading="lazy" onError={() => setPreviewFailed(true)} />
      <figcaption>{attachment.name}</figcaption>
    </figure>
  )
}

function MessageAttachmentStrip({ attachments }: { attachments: ChatAttachment[] | undefined }) {
  const visibleAttachments = useMemo(
    () => (Array.isArray(attachments) ? attachments.filter((attachment) => attachment.localPath && attachment.name) : []),
    [attachments]
  )

  if (!visibleAttachments.length) {
    return null
  }

  return (
    <div className="message-attachment-strip" aria-label="消息附件">
      {visibleAttachments.map((attachment, index) => (
        <div key={getAttachmentKey(attachment, index)} className="message-attachment-item">
          {attachment.kind === "image" ? (
            <ImageAttachmentPreview attachment={attachment} />
          ) : (
            <AttachmentFileCard attachment={attachment} />
          )}
        </div>
      ))}
    </div>
  )
}

export function MessageList({
  messages,
  viewMode,
  showBindEntry,
  showEmptyState,
  emptyState,
  messageListRef,
  messageTraces,
  messageReactions,
  copiedToken,
  sending,
  activeExpertKey,
  labels,
  copyIcon,
  copiedIcon,
  regenerateIcon,
  renderThumbIcon,
  renderMarkdownContent,
  buildDouyinVideoStatusCard,
  formatMessageTimestamp,
  onScroll,
  onCopyText,
  onTraceExpandedChange,
  onRegenerateAssistantMessage,
  onToggleMessageReaction
}: MessageListProps) {
  const messageListClassName = [
    "message-list",
    "chat-scroll-smooth",
    viewMode === "chat" ? "message-list-home" : "",
    viewMode === "experts" && activeExpertKey === "xiaohongshu" ? "message-list-xiaohongshu-empty" : ""
  ].filter(Boolean).join(" ")

  return (
    <div ref={messageListRef} onScroll={onScroll} className={messageListClassName}>
      {messages.map((message) => {
        const isWaitingForContent = message.role === "assistant" && message.streamState === "streaming" && !message.content.trim()
        const videoStatusCard = isWaitingForContent ? buildDouyinVideoStatusCard(message, activeExpertKey) : null
        const messageTrace = message.role === "assistant" ? messageTraces[message.id] : undefined
        const hasTrace = Boolean(messageTrace?.items.length)
        const isTraceExpanded = Boolean(messageTrace?.expanded)
        const isReasoningActive = message.streamState === "streaming" || message.streamState === "error"
        const showReasoningStrip = message.role === "assistant" && !videoStatusCard && isReasoningActive && (hasTrace || message.streamState === "error" || isWaitingForContent)
        const traceDisplayLines = hasTrace ? getTraceDisplayLines(messageTrace?.items ?? []) : []
        const traceTitle = getTraceStripTitle(messageTrace?.items ?? [], labels.thinking, message.statusLabel, message.statusDetail)
        const canCopyMessage = Boolean(message.content.trim())
        const copyToken = `message:${message.id}`
        const reaction = messageReactions[message.id]
        void reaction

        return (
          <article key={message.id} className={"message-card group " + message.role + (message.streamState ? " " + message.streamState : "")}>
            <div className={"message-bubble" + (message.role === "assistant" ? " message-bubble-assistant" : " message-bubble-user")}>
              {showReasoningStrip ? (
                <div className={"message-trace" + (message.streamState === "streaming" ? " streaming" : "")} aria-live={message.streamState === "streaming" ? "polite" : undefined}>
                  <button
                    type="button"
                    className={"reasoning-strip" + (isTraceExpanded ? " expanded" : "") + (message.streamState === "error" ? " error" : "") + (message.streamState === "streaming" ? " streaming" : "") + (isWaitingForContent ? " waiting" : "")}
                    onClick={() => onTraceExpandedChange(message.id, !isTraceExpanded)}
                    aria-expanded={isTraceExpanded}
                  >
                    <span className="reasoning-strip-leading" aria-hidden="true">
                      <span className="reasoning-strip-dots">
                        <span className="reasoning-strip-dot" />
                        <span className="reasoning-strip-dot" />
                        <span className="reasoning-strip-dot" />
                      </span>
                    </span>
                    <span className="reasoning-strip-title">{traceTitle}</span>
                    <span className="reasoning-strip-action">{isTraceExpanded ? labels.hideTrace : labels.traceCollapsed}</span>
                  </button>
                  {isTraceExpanded ? (
                    <div className="message-trace-content" aria-label="公开推理摘要">
                      {traceDisplayLines.map((line) => {
                        return (
                          <p key={line.key} className={getTraceLineClassName(line.tone, line.kind)}>
                            <span className="message-trace-marker" aria-hidden="true" />
                            <span className="message-trace-main">
                              <span className="message-trace-row">
                                <span className="message-trace-text">{line.title}</span>
                                <span className="message-trace-time">{line.time}</span>
                              </span>
                              {line.detail ? <span className="message-trace-detail">{line.detail}</span> : null}
                            </span>
                          </p>
                        )
                      })}
                    </div>
                  ) : null}
                </div>
              ) : null}
              {isWaitingForContent && videoStatusCard ? (
                <div className="generation-status-card" aria-live="polite">
                  <div className="generation-status-leading">
                    <span className="thinking-spinner generation-status-spinner" aria-hidden="true" />
                  </div>
                  <div className="generation-status-body">
                    <span className="generation-status-kicker">抖音专家执行中</span>
                    <strong className="generation-status-title">{videoStatusCard.title}</strong>
                    {videoStatusCard.meta ? <span className="generation-status-meta">{videoStatusCard.meta}</span> : null}
                    {videoStatusCard.hint ? <span className="generation-status-hint">{videoStatusCard.hint}</span> : null}
                    <span className="generation-status-progress" aria-hidden="true" />
                  </div>
                </div>
              ) : message.content ? (
                message.role === "assistant" ? (
                  <div className="markdown-body">
                    {renderMarkdownContent(message.content, {
                      messageId: message.id,
                      copiedToken,
                      onCopy: onCopyText
                    })}
                    {message.streamState === "streaming" ? <span className="message-cursor" aria-hidden="true" /> : null}
                  </div>
                ) : (
                  <p className="message-plain-text">
                    {message.content}
                    {message.streamState === "streaming" ? <span className="message-cursor" aria-hidden="true" /> : null}
                  </p>
                )
              ) : null}
              {message.role === "user" ? <MessageAttachmentStrip attachments={message.attachments} /> : null}
            </div>
            <span className="message-timestamp" aria-hidden="true">{formatMessageTimestamp(message.createdAt)}</span>
            {message.role === "assistant" && canCopyMessage ? (
              <div className="message-card-actions">
                <button
                  type="button"
                  className={"message-action-icon" + (copiedToken === copyToken ? " copied" : "")}
                  onClick={() => void onCopyText(copyToken, message.content)}
                  aria-label="复制消息"
                  title="复制消息"
                >
                  {copiedToken === copyToken ? copiedIcon : copyIcon}
                </button>
                {message.role === "assistant" ? (
                  <>
                    <button
                      type="button"
                      className="hidden"
                      onClick={() => void onRegenerateAssistantMessage(message.id)}
                      disabled={sending}
                      aria-label="重新生成"
                      title="重新生成"
                    >
                      {regenerateIcon}
                    </button>
                    <button
                      type="button"
                      className="hidden"
                      onClick={() => onToggleMessageReaction(message.id, "up")}
                      aria-label="赞"
                      title="赞"
                    >
                      {renderThumbIcon("up")}
                    </button>
                    <button
                      type="button"
                      className="hidden"
                      onClick={() => onToggleMessageReaction(message.id, "down")}
                      aria-label="踩"
                      title="踩"
                    >
                      {renderThumbIcon("down")}
                    </button>
                  </>
                ) : null}
              </div>
            ) : null}
          </article>
        )
      })}
      {!messages.length && !showBindEntry && showEmptyState ? emptyState : null}
    </div>
  )
}
