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" | "automation" | "plugins" | "settings" | "knowledge"
type MessageReaction = "up" | "down"
export type ExpertKey = "xiaohongshu" | "douyin" | "browser" | "general" | "planner" | "wechat" | "geo" | "zhihu"

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
  deleteIcon: 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>
  onDeleteMessage: (messageId: string) => 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,
  deleteIcon,
  regenerateIcon,
  renderThumbIcon,
  renderMarkdownContent,
  buildDouyinVideoStatusCard,
  formatMessageTimestamp,
  onScroll,
  onCopyText,
  onDeleteMessage,
  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 hasVisibleAttachments = message.role === "user" && Array.isArray(message.attachments) && message.attachments.some((attachment) => attachment.localPath && attachment.name)
        const canDeleteMessage = message.streamState !== "streaming" && (canCopyMessage || hasVisibleAttachments)
        const copyToken = `message:${message.id}`
        const reaction = messageReactions[message.id]
        void reaction
        const messageActions = (canCopyMessage || canDeleteMessage) ? (
          <div className="message-card-actions">
            {canCopyMessage ? (
              <button
                type="button"
                className={"message-action-icon" + (copiedToken === copyToken ? " copied" : "")}
                onClick={() => void onCopyText(copyToken, message.content)}
                aria-label="复制消息"
                title="复制消息"
              >
                {copiedToken === copyToken ? copiedIcon : copyIcon}
              </button>
            ) : null}
            {canDeleteMessage ? (
              <button
                type="button"
                className="message-action-icon message-action-delete"
                onClick={() => onDeleteMessage(message.id)}
                aria-label="删除消息"
                title="删除消息"
              >
                {deleteIcon}
              </button>
            ) : null}
            {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

        return (
          <article key={message.id} className={"message-card group " + message.role + (message.streamState ? " " + message.streamState : "")}>
            <div className={"message-card-body message-card-body-" + message.role}>
              <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>
                      {message.role === "user" ? <MessageAttachmentStrip attachments={message.attachments} /> : null}
                    </>
                  )
                ) : message.role === "user" ? (
                  <>
                    <MessageAttachmentStrip attachments={message.attachments} />
                  </>
                ) : null}
              </div>
              <div className="message-card-meta">
                <span className="message-timestamp" aria-hidden="true">{formatMessageTimestamp(message.createdAt)}</span>
                {messageActions}
              </div>
            </div>
          </article>
        )
      })}
      {!messages.length && !showBindEntry && showEmptyState ? emptyState : null}
    </div>
  )
}
