import type { ACPMessage } from '#cli/core/acp'
import { readFile, stat } from 'node:fs/promises'
import path from 'node:path'

const IMAGE_EXTENSIONS = new Set([
  '.png',
  '.jpg',
  '.jpeg',
  '.gif',
  '.webp',
  '.bmp',
  '.ico',
  '.svg',
])

const EXT_MIME: Record<string, string> = {
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.gif': 'image/gif',
  '.webp': 'image/webp',
  '.bmp': 'image/bmp',
  '.ico': 'image/x-icon',
  '.svg': 'image/svg+xml',
}

const MAX_IMAGE_BYTES = 10 * 1024 * 1024

function isAbsPath(p: string): boolean {
  return p.startsWith('/') || /^[A-Z]:[\\/]/i.test(p)
}

function isImagePath(p: string): boolean {
  return IMAGE_EXTENSIONS.has(path.extname(p).toLowerCase())
}

async function readImageAsBase64(
  absPath: string,
): Promise<{ data: string, mimeType: string } | null> {
  try {
    const st = await stat(absPath)
    if (!st.isFile() || st.size <= 0 || st.size > MAX_IMAGE_BYTES)
      return null
    const buf = await readFile(absPath)
    const ext = path.extname(absPath).toLowerCase()
    return {
      data: buf.toString('base64'),
      mimeType: EXT_MIME[ext] || 'application/octet-stream',
    }
  }
  catch {
    return null
  }
}

type ContentPart = { type: string, [key: string]: unknown }
type ModelCapabilities = {
  vision?: boolean
}

type ImageResolverState = {
  config?: {
    modelCapabilities?: ModelCapabilities
    model_capabilities?: ModelCapabilities
  }
}

function extractMarkdownLinks(
  text: string,
): Array<{ label: string, target: string, start: number, end: number }> {
  const links: Array<{
    label: string
    target: string
    start: number
    end: number
  }> = []
  const re = /\[([^\]]*)\]\(((?:\\\)|[^)])+)\)/g
  let m: RegExpExecArray | null = re.exec(text)
  while (m) {
    const target = m[2]!.replace(/\\\)/g, ')').trim()
    if (isAbsPath(target) && isImagePath(target)) {
      links.push({
        label: m[1]!,
        target,
        start: m.index,
        end: m.index + m[0].length,
      })
    }
    m = re.exec(text)
  }
  return links
}

async function resolveUserMessageContent(
  content: string,
): Promise<string | ContentPart[]> {
  const links = extractMarkdownLinks(content)
  if (links.length === 0)
    return content

  const parts: ContentPart[] = []
  let lastIdx = 0

  for (const link of links) {
    const before = content.slice(lastIdx, link.start)
    if (before.trim())
      parts.push({ type: 'text', text: before })

    const img = await readImageAsBase64(link.target)
    if (img) {
      parts.push({
        type: 'image',
        data: img.data,
        mimeType: img.mimeType,
      })
    }
    else {
      parts.push({ type: 'text', text: content.slice(link.start, link.end) })
    }
    lastIdx = link.end
  }

  const tail = content.slice(lastIdx)
  if (tail.trim())
    parts.push({ type: 'text', text: tail })

  return parts.length > 0 ? parts : content
}

function hasVisionCapability(state: ImageResolverState): boolean {
  const config = state.config
  if (!config)
    return false
  const capabilities = config.modelCapabilities ?? config.model_capabilities
  if (capabilities)
    return Boolean(capabilities.vision)
  return true
}

export function createConversationImageResolver(
  state: ImageResolverState,
): (conversation: ACPMessage[]) => Promise<void> {
  return async (conversation: ACPMessage[]) => {
    if (!hasVisionCapability(state))
      return

    for (let i = conversation.length - 1; i >= 0; i--) {
      const msg = conversation[i]
      if (msg?.role !== 'user')
        continue
      if (typeof msg.content !== 'string')
        break

      const resolved = await resolveUserMessageContent(msg.content)
      if (resolved !== msg.content)
        conversation[i] = { ...msg, content: resolved }
      break
    }
  }
}
