import { spawn } from 'node:child_process'
import { randomUUID } from 'node:crypto'
import { mkdtemp, rm, stat, unlink, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'
import {
  cacheFileToSessionAttachment,
  isHomeSessionAttachmentPath,
  normalizeSessionId,
  relocateSessionAttachmentPath,
  shouldCacheFileToSessionAttachment,
} from '#cli/shared/session-blob'

type ClipboardImageResult = Readonly<{ path: string; mime: string }>

export const HOME_SESSION_ATTACHMENT_SESSION_ID = '_home'
export const HOME_IMAGE_CACHE_SESSION_ID = HOME_SESSION_ATTACHMENT_SESSION_ID

export function isHomeAttachmentPath(absPath: string): boolean {
  return isHomeSessionAttachmentPath(absPath)
}

export const isHomeImagePath = isHomeAttachmentPath

function isSessionAttachmentPath(absPath: string, sessionId: string): boolean {
  const normalized = String(absPath ?? '').replace(/\\/g, '/')
  return (
    normalized.includes(`/blob-cache/${sessionId}/attachments/`)
    || normalized.includes(`/image-cache/${sessionId}/`)
  )
}

async function relocateHomeAttachments(
  files: readonly string[],
  sessionId: string,
): Promise<string[]> {
  const sid = normalizeSessionId(sessionId)
  if (!sid)
    return [...files]

  const out: string[] = []
  for (const file of files) {
    if (!isHomeAttachmentPath(file)) {
      out.push(file)
      continue
    }
    const moved = await relocateSessionAttachmentPath(file, sid)
    out.push(moved ?? file)
  }
  return out
}

export const relocateHomeAttachmentsToSession = relocateHomeAttachments
export const relocateHomeImagesToSession = relocateHomeAttachments

async function fileLooksValid(filePath: string): Promise<boolean> {
  try {
    const info = await stat(filePath)
    return info.isFile() && info.size > 0
  }
  catch {
    return false
  }
}

async function safeUnlink(filePath: string): Promise<void> {
  try {
    await unlink(filePath)
  }
  catch {
    // ignore
  }
}

async function runCommandToFile(
  cmd: string,
  args: string[],
  filePath: string,
): Promise<boolean> {
  const ok = await new Promise<boolean>((resolve) => {
    try {
      const child = spawn(cmd, args, {
        stdio: ['ignore', 'ignore', 'ignore'],
        windowsHide: true,
      })
      child.on('error', () => resolve(false))
      child.on('close', code => resolve(code === 0))
    }
    catch {
      resolve(false)
    }
  })
  if (!ok)
    return false
  if (await fileLooksValid(filePath))
    return true
  await safeUnlink(filePath)
  return false
}

async function runCommandForBuffer(
  cmd: string,
  args: string[],
): Promise<Buffer | null> {
  return new Promise<Buffer | null>((resolve) => {
    try {
      const child = spawn(cmd, args, {
        stdio: ['ignore', 'pipe', 'ignore'],
        windowsHide: true,
      })
      const chunks: Buffer[] = []
      child.stdout?.on('data', (chunk) => {
        chunks.push(Buffer.from(chunk))
      })
      child.on('error', () => resolve(null))
      child.on('close', (code) => {
        if (code !== 0)
          return resolve(null)
        const buf = chunks.length ? Buffer.concat(chunks) : null
        resolve(buf && buf.length > 0 ? buf : null)
      })
    }
    catch {
      resolve(null)
    }
  })
}

function escapeAppleScriptString(value: string): string {
  return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
}

function escapePowerShellString(value: string): string {
  return value.replace(/'/g, "''")
}

async function tryPasteImageDarwin(
  dir: string,
): Promise<ClipboardImageResult | null> {
  const filePath = path.join(dir, `paste-${Date.now()}-${randomUUID()}.png`)
  if (await runCommandToFile('pngpaste', [filePath], filePath))
    return { path: filePath, mime: 'image/png' }

  const pngClass = '\u00ABclass PNGf\u00BB'
  const escaped = escapeAppleScriptString(filePath)
  const script = [
    `set thePath to "${escaped}"`,
    `set theData to the clipboard as ${pngClass}`,
    'set theFile to open for access POSIX file thePath with write permission',
    'set eof of theFile to 0',
    'write theData to theFile',
    'close access theFile',
  ]
  const args = script.flatMap(line => ['-e', line])
  if (await runCommandToFile('osascript', args, filePath))
    return { path: filePath, mime: 'image/png' }

  await safeUnlink(filePath)
  return null
}

async function tryPasteImageWindows(
  dir: string,
): Promise<ClipboardImageResult | null> {
  const filePath = path.join(dir, `paste-${Date.now()}-${randomUUID()}.png`)
  const escaped = escapePowerShellString(filePath)
  const script = [
    'Add-Type -AssemblyName System.Drawing',
    '$img = Get-Clipboard -Format Image',
    'if ($null -eq $img) { exit 1 }',
    `$img.Save('${escaped}', [System.Drawing.Imaging.ImageFormat]::Png)`,
  ].join('; ')
  const args = ['-NoProfile', '-Command', script]

  if (await runCommandToFile('powershell.exe', args, filePath))
    return { path: filePath, mime: 'image/png' }
  if (await runCommandToFile('powershell', args, filePath))
    return { path: filePath, mime: 'image/png' }
  if (await runCommandToFile('pwsh', args, filePath))
    return { path: filePath, mime: 'image/png' }

  await safeUnlink(filePath)
  return null
}

async function tryPasteImageLinux(
  dir: string,
): Promise<ClipboardImageResult | null> {
  const attempts: Array<{
    cmd: string
    args: string[]
    mime: string
    ext: string
  }> = [
    {
      cmd: 'wl-paste',
      args: ['--type', 'image/png'],
      mime: 'image/png',
      ext: 'png',
    },
    {
      cmd: 'wl-paste',
      args: ['--type', 'image/jpeg'],
      mime: 'image/jpeg',
      ext: 'jpg',
    },
    {
      cmd: 'wl-paste',
      args: ['--type', 'image/webp'],
      mime: 'image/webp',
      ext: 'webp',
    },
    {
      cmd: 'xclip',
      args: ['-selection', 'clipboard', '-t', 'image/png', '-o'],
      mime: 'image/png',
      ext: 'png',
    },
    {
      cmd: 'xclip',
      args: ['-selection', 'clipboard', '-t', 'image/jpeg', '-o'],
      mime: 'image/jpeg',
      ext: 'jpg',
    },
    {
      cmd: 'xclip',
      args: ['-selection', 'clipboard', '-t', 'image/webp', '-o'],
      mime: 'image/webp',
      ext: 'webp',
    },
  ]

  for (const attempt of attempts) {
    const data = await runCommandForBuffer(attempt.cmd, attempt.args)
    if (!data)
      continue
    const filePath = path.join(
      dir,
      `paste-${Date.now()}-${randomUUID()}.${attempt.ext}`,
    )
    try {
      await writeFile(filePath, data)
      if (await fileLooksValid(filePath))
        return { path: filePath, mime: attempt.mime }
    }
    catch {
      // fallthrough
    }
    await safeUnlink(filePath)
  }

  return null
}

export async function pasteClipboardImageToTempFile(
  opts?: Readonly<{ sessionId?: string }>,
): Promise<string | null> {
  const proc: any = (globalThis as any).process
  const platform = String(proc?.platform || '')
  if (!platform)
    return null

  const sessionId = normalizeSessionId(opts?.sessionId)
  if (!sessionId)
    return null

  const scratchDir = await mkdtemp(path.join(tmpdir(), 'dimcode-paste-'))
  try {
    const result = platform === 'darwin'
      ? await tryPasteImageDarwin(scratchDir)
      : platform === 'win32'
        ? await tryPasteImageWindows(scratchDir)
        : await tryPasteImageLinux(scratchDir)
    if (!result?.path)
      return null

    const cached = await cacheFileToSessionAttachment({
      sourcePath: result.path,
      sessionId,
      source: 'clipboard_image',
      preferredBaseName: path.basename(result.path),
      mediaType: result.mime,
    })
    return cached?.filePath ?? null
  }
  finally {
    await rm(scratchDir, { recursive: true, force: true }).catch(() => {})
  }
}

export async function cacheDroppedFileToSession(
  absPath: string,
  opts?: Readonly<{ sessionId?: string }>,
): Promise<string | null> {
  const sessionId = normalizeSessionId(opts?.sessionId)
  if (!sessionId)
    return null

  const cleaned = String(absPath ?? '').trim()
  if (!cleaned)
    return null
  if (!(await fileLooksValid(cleaned)))
    return null
  if (isSessionAttachmentPath(cleaned, sessionId))
    return cleaned
  if (!shouldCacheFileToSessionAttachment(cleaned))
    return cleaned

  const cached = await cacheFileToSessionAttachment({
    sourcePath: cleaned,
    sessionId,
    source: 'dropped_file',
    preferredBaseName: path.basename(cleaned),
  })
  return cached?.filePath ?? cleaned
}
