import { spawn } from 'node:child_process'

export type TextExtractionResult = {
  extractor: string
  extractorVersion: string
  language: string
  status: 'succeeded' | 'unsupported' | 'failed'
  text: string
  error?: string
}

export interface TextExtractor {
  provider: string
  extract(file: { buffer: Buffer; mimetype: string }): Promise<TextExtractionResult>
}

function plainText(file: { buffer: Buffer; mimetype: string }, maximumCharacters: number): TextExtractionResult | null {
  if (!file.mimetype.startsWith('text/')) return null
  return { extractor: 'plain-text', extractorVersion: '1', language: 'und', status: 'succeeded', text: file.buffer.toString('utf8').slice(0, maximumCharacters) }
}

function runTesseract(command: string, buffer: Buffer, languages: string, pageSegmentationMode: string, timeoutMs: number) {
  return new Promise<string>((resolve, reject) => {
    const child = spawn(command, ['stdin', 'stdout', '-l', languages, '--psm', pageSegmentationMode], { stdio: ['pipe', 'pipe', 'pipe'] })
    const stdout: Buffer[] = []
    const stderr: Buffer[] = []
    let settled = false
    const timer = setTimeout(() => {
      if (settled) return
      settled = true
      child.kill('SIGKILL')
      reject(new Error(`OCR timed out after ${timeoutMs}ms`))
    }, timeoutMs)
    child.stdout.on('data', chunk => stdout.push(Buffer.from(chunk)))
    child.stderr.on('data', chunk => stderr.push(Buffer.from(chunk)))
    child.once('error', error => {
      if (settled) return
      settled = true
      clearTimeout(timer)
      reject(error)
    })
    child.once('close', code => {
      if (settled) return
      settled = true
      clearTimeout(timer)
      if (code === 0) resolve(Buffer.concat(stdout).toString('utf8').trim())
      else reject(new Error(Buffer.concat(stderr).toString('utf8').trim() || `OCR exited with status ${code}`))
    })
    child.stdin.end(buffer)
  })
}

export function createTextExtractorFromEnv(): TextExtractor {
  const enabled = process.env.OCR_ENABLED !== 'false'
  const command = process.env.OCR_COMMAND || 'tesseract'
  const languages = process.env.OCR_LANGUAGES || 'nor+eng'
  const extractorVersion = process.env.OCR_ENGINE_VERSION || 'tesseract-cli-5'
  const pageSegmentationMode = process.env.OCR_PAGE_SEGMENTATION_MODE || '3'
  const timeoutMs = Math.max(1_000, Number(process.env.OCR_TIMEOUT_MS || 20_000))
  const maximumBytes = Math.max(1, Number(process.env.MAX_OCR_BYTES || 15 * 1024 * 1024))
  const maximumCharacters = Math.max(1_000, Number(process.env.MAX_EXTRACTED_TEXT_CHARACTERS || 200_000))
  return {
    provider: enabled ? 'tesseract' : 'disabled',
    async extract(file) {
      const direct = plainText(file, maximumCharacters)
      if (direct) return direct
      if (!file.mimetype.startsWith('image/')) return { extractor: 'none', extractorVersion: '1', language: 'und', status: 'unsupported', text: '' }
      if (!enabled) return { extractor: 'tesseract', extractorVersion, language: languages, status: 'unsupported', text: '' }
      if (file.buffer.byteLength > maximumBytes) return { extractor: 'tesseract', extractorVersion, language: languages, status: 'failed', text: '', error: `Image exceeds the ${maximumBytes}-byte OCR limit` }
      try {
        const text = (await runTesseract(command, file.buffer, languages, pageSegmentationMode, timeoutMs)).slice(0, maximumCharacters)
        return { extractor: 'tesseract', extractorVersion, language: languages, status: 'succeeded', text }
      } catch (error) {
        return { extractor: 'tesseract', extractorVersion, language: languages, status: 'failed', text: '', error: error instanceof Error ? error.message : 'OCR failed' }
      }
    },
  }
}
