import type { ChatAttachment, ChatMessage } from "@qjclaw/shared-types"
import {
  COMPOSER_TEXTAREA_DEFAULT_MIN_HEIGHT,
  COMPOSER_TEXTAREA_MAX_HEIGHT,
  COMPOSER_TEXTAREA_MAX_RATIO,
  COMPOSER_TEXTAREA_MIN_RATIO,
  COMPOSER_TEXTAREA_SAFE_MIN_HEIGHT
} from "./constants.ts"

export type MessageStreamState = "streaming" | "error"

export type UiChatMessage = ChatMessage & {
  streamState?: MessageStreamState
  statusLabel?: string
  statusDetail?: string
}

export function getComposerTextareaBounds(workspaceHeight: number): { min: number; max: number } {
  const safeWorkspaceHeight = Number.isFinite(workspaceHeight) && workspaceHeight > 0 ? workspaceHeight : 0
  const minByWorkspace = safeWorkspaceHeight > 0
    ? Math.min(COMPOSER_TEXTAREA_DEFAULT_MIN_HEIGHT, safeWorkspaceHeight * COMPOSER_TEXTAREA_MIN_RATIO)
    : COMPOSER_TEXTAREA_DEFAULT_MIN_HEIGHT
  const maxByWorkspace = safeWorkspaceHeight * COMPOSER_TEXTAREA_MAX_RATIO
  const dynamicMinHeight = Math.max(COMPOSER_TEXTAREA_SAFE_MIN_HEIGHT, minByWorkspace)
  return {
    min: dynamicMinHeight,
    max: Math.max(dynamicMinHeight, Math.min(COMPOSER_TEXTAREA_MAX_HEIGHT, maxByWorkspace || COMPOSER_TEXTAREA_MAX_HEIGHT))
  }
}

export function clampComposerTextareaHeight(height: number, workspaceHeight: number): number {
  const bounds = getComposerTextareaBounds(workspaceHeight)
  return Math.min(bounds.max, Math.max(bounds.min, height))
}

export function shouldOfferHomeExpertSwitch(prompt: string): boolean {
  const normalized = prompt.normalize("NFKC").toLowerCase()
  return /小红书|抖音|xiaohongshu|rednote|xhs|douyin|tiktok/.test(normalized)
}

export function resolvePreferredSessionId(sessions: Array<{ id: string }>, activeSessionId?: string | null): string | undefined {
  return sessions.find((session) => session.id === activeSessionId)?.id ?? sessions[0]?.id
}

export function createClientMessageId(prefix: string): string {
  return globalThis.crypto?.randomUUID?.() ?? `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`
}

export function toUiChatMessage(message: ChatMessage, streamState?: MessageStreamState, statusLabel?: string, statusDetail?: string): UiChatMessage {
  const resolvedStreamState = streamState ?? message.streamState
  const resolvedStatusLabel = statusLabel ?? message.statusLabel
  const resolvedStatusDetail = statusDetail ?? message.statusDetail
  return resolvedStreamState || resolvedStatusLabel || resolvedStatusDetail
    ? { ...message, streamState: resolvedStreamState, statusLabel: resolvedStatusLabel, statusDetail: resolvedStatusDetail }
    : { ...message }
}

export function toPlainMessages(items: UiChatMessage[]): ChatMessage[] {
  return items.map(({ streamState, statusLabel, statusDetail, ...message }) => message)
}

export function isPrimaryChatMessage(message: ChatMessage): boolean {
  return message.role === "user" || message.role === "assistant"
}

function hasLocalTransientMessage(messages: UiChatMessage[]): boolean {
  return messages.some((message) => Boolean(message.streamState || message.statusLabel || message.statusDetail))
}

export function mergeSessionHistory(current: UiChatMessage[], nextMessages: UiChatMessage[]): UiChatMessage[] {
  if (!current.length) {
    return nextMessages
  }

  if (!hasLocalTransientMessage(current)) {
    return nextMessages
  }

  if (!nextMessages.length) {
    return current
  }

  const currentById = new Map(current.map((message) => [message.id, message] as const))
  const merged = nextMessages.map((message) => {
    const local = currentById.get(message.id)
    if (!local) {
      return message
    }

    const hasLocalStreamingState = Boolean(local.streamState || local.statusLabel || local.statusDetail)
    if (!hasLocalStreamingState) {
      return message
    }

    if (local.streamState === "streaming" && local.content.length > message.content.length) {
      return {
        ...message,
        content: local.content,
        createdAt: message.createdAt || local.createdAt,
        streamState: local.streamState ?? message.streamState,
        statusLabel: local.statusLabel ?? message.statusLabel,
        statusDetail: local.statusDetail ?? message.statusDetail
      }
    }

    return message
  })
  const nextIds = new Set(nextMessages.map((message) => message.id))
  const localOnlyMessages = current.filter((message) => !nextIds.has(message.id))
  return localOnlyMessages.length > 0 ? [...merged, ...localOnlyMessages] : merged
}

export function appendSmokeStatusLabel(currentLabels: string[] | undefined, label: string): string[] {
  const trimmed = label.trim()
  if (!trimmed) {
    return currentLabels ?? []
  }

  const labels = currentLabels ?? []
  if (labels.at(-1) === trimmed) {
    return labels
  }

  return [...labels, trimmed].slice(-20)
}

export function buildUserMessage(content: string, attachments?: ChatAttachment[]): UiChatMessage {
  return {
    id: createClientMessageId("user"),
    role: "user",
    content,
    createdAt: new Date().toISOString(),
    attachments
  }
}

export function formatMessageTimestamp(value: string): string {
  const date = new Date(value)
  if (Number.isNaN(date.getTime())) {
    return value
  }

  const pad = (part: number) => String(part).padStart(2, "0")
  return [
    date.getFullYear(),
    pad(date.getMonth() + 1),
    pad(date.getDate())
  ].join("-") + " " + [
    pad(date.getHours()),
    pad(date.getMinutes()),
    pad(date.getSeconds())
  ].join(":")
}

export function buildAssistantPlaceholder(statusLabel: string): UiChatMessage {
  return {
    id: createClientMessageId("assistant"),
    role: "assistant",
    content: "",
    createdAt: new Date().toISOString(),
    streamState: "streaming",
    statusLabel,
    statusDetail: undefined
  }
}
