import type { BlobLogger, SessionBlobWriteResult } from '../shared/session-blob'
import {
  cacheTextToSessionBlob as _cacheTextToSessionBlob,
  cleanupExpiredSessionCaches as _cleanupExpiredSessionCaches,
  cleanupSessionCache as _cleanupSessionCache,
  deleteSessionBlobByPath as _deleteSessionBlobByPath,
  relocateSessionBlobPath as _relocateSessionBlobPath,
  writeSessionBlob as _writeSessionBlob,
  normalizeSessionId,
  readBlobMeta,
} from '../shared/session-blob'
import { debugLog } from './debug-log'

// ---------------------------------------------------------------------------
// Re-export SDK primitives, injecting the CLI debug logger
// ---------------------------------------------------------------------------

export type { SessionBlobWriteResult } from '../shared/session-blob'

const logger: BlobLogger = debugLog

export async function cacheTextToSessionBlob(
  opts: Readonly<{
    content: string
    sessionId?: string
    source?: string
  }>,
): Promise<SessionBlobWriteResult | null> {
  return _cacheTextToSessionBlob({ ...opts, logger })
}

export async function deleteSessionBlobByPath(
  absPath: string,
): Promise<boolean> {
  return _deleteSessionBlobByPath(absPath, logger)
}

export async function relocateSessionBlobPath(
  absPath: string,
  targetSessionId: string,
): Promise<string | null> {
  return _relocateSessionBlobPath(absPath, targetSessionId)
}

export async function cleanupSessionCache(sessionId: string): Promise<void> {
  return _cleanupSessionCache(sessionId, logger)
}

export async function cleanupExpiredSessionCaches(
  opts?: Readonly<{ ttlMs?: number }>,
): Promise<void> {
  return _cleanupExpiredSessionCaches({ ...opts, logger })
}

export async function readLongInputMeta(
  sessionId: string,
  fileId: string,
): Promise<Record<string, unknown> | null> {
  return readBlobMeta(sessionId, fileId)
}

// ---------------------------------------------------------------------------
// CLI-specific constants and helpers (not in SDK)
// ---------------------------------------------------------------------------

const DEFAULT_LINE_THRESHOLD = 400
const DEFAULT_TOKEN_THRESHOLD = 12000
const DEFAULT_PREVIEW_LINES = 20
const DEFAULT_FIRST_RANGE_LINES = 300
const DEFAULT_PREVIEW_MAX_LINE_CHARS = 240
const DEFAULT_PREVIEW_MAX_CHARS = 4000

function isNodeLike(): boolean {
  const p = (globalThis as any).process
  return Boolean(p && typeof p?.versions?.node === 'string')
}

function estimateTokens(text: string): number {
  const raw = String(text ?? '')
  const chars = raw.length
  const bytes = Buffer.byteLength(raw, 'utf-8')
  let nonAsciiChars = 0
  for (let i = 0; i < raw.length; i++) {
    if (raw.charCodeAt(i) > 0x7F)
      nonAsciiChars++
  }
  const asciiChars = Math.max(0, chars - nonAsciiChars)

  const byChars = Math.ceil(chars / 4)
  const byBytes = Math.ceil(bytes / 3)
  const byMixed = Math.ceil(asciiChars / 4 + nonAsciiChars * 0.9)
  return Math.max(byChars, byBytes, byMixed)
}

function countLines(text: string): number {
  if (!text)
    return 0
  return text.split('\n').length
}

function buildPreview(text: string, maxLines: number): string {
  const lines = text.split('\n').slice(0, Math.max(1, maxLines))
  const out: string[] = []
  let used = 0
  const maxLineChars = DEFAULT_PREVIEW_MAX_LINE_CHARS
  const maxChars = DEFAULT_PREVIEW_MAX_CHARS
  for (const line of lines) {
    let next = line
    if (next.length > maxLineChars)
      next = `${next.slice(0, maxLineChars)}...`
    const size = next.length + (out.length > 0 ? 1 : 0)
    if (used + size > maxChars)
      break
    out.push(next)
    used += size
  }
  return out.join('\n')
}

function buildReferenceMessage(
  opts: Readonly<{
    fileId: string
    lineCount: number
    sha256: string
    preview: string
    firstRangeLines: number
  }>,
): string {
  const endLine = Math.max(1, Math.min(opts.lineCount, opts.firstRangeLines))
  return [
    '[long_input_reference]',
    'Large pasted content is stored in a session-scoped blob.',
    'Read it via `Read` tool using `file_id` and line ranges.',
    '',
    `file_id: ${opts.fileId}`,
    `line_count: ${opts.lineCount}`,
    `sha256: ${opts.sha256}`,
    `recommended_first_range: 1-${endLine}`,
    '',
    'tool_call_example:',
    `{"file_id":"${opts.fileId}","start_line":1,"end_line":${endLine}}`,
    '',
    'preview:',
    '```text',
    opts.preview,
    '```',
    '',
    'Continue reading in chunks of at most 300 lines and cite line numbers in conclusions.',
  ].join('\n')
}

// ---------------------------------------------------------------------------
// CLI-specific public APIs
// ---------------------------------------------------------------------------

export type LongInputRewriteResult = Readonly<{
  rewritten: boolean
  content: string
  fileId?: string
  lineCount?: number
  estimatedTokens?: number
}>

export async function rewriteOversizedInputToReference(
  opts: Readonly<{
    content: string
    sessionId?: string
    source?: string
    lineThreshold?: number
    tokenThreshold?: number
    previewLines?: number
    firstRangeLines?: number
  }>,
): Promise<LongInputRewriteResult> {
  const content = String(opts.content ?? '')
  if (!content)
    return { rewritten: false, content }
  if (!isNodeLike())
    return { rewritten: false, content }

  const sessionId = normalizeSessionId(opts.sessionId)
  if (!sessionId)
    return { rewritten: false, content }

  const lineCount = countLines(content)
  const estimatedToks = estimateTokens(content)
  const lineThreshold = Math.max(
    1,
    Math.floor(opts.lineThreshold ?? DEFAULT_LINE_THRESHOLD),
  )
  const tokenThreshold = Math.max(
    1,
    Math.floor(opts.tokenThreshold ?? DEFAULT_TOKEN_THRESHOLD),
  )
  const oversized = lineCount > lineThreshold || estimatedToks > tokenThreshold
  if (!oversized) {
    return {
      rewritten: false,
      content,
      lineCount,
      estimatedTokens: estimatedToks,
    }
  }

  await cleanupExpiredSessionCaches({ ttlMs: 24 * 60 * 60 * 1000 }).catch(
    () => {},
  )

  const written = await _writeSessionBlob({
    content,
    sessionId,
    source: String(opts.source ?? 'pasted_text'),
    logger,
  })
  if (!written) {
    return {
      rewritten: false,
      content,
      lineCount,
      estimatedTokens: estimatedToks,
    }
  }

  const { fileId, lineCount: persistedLineCount, sha256, byteSize } = written
  const preview = buildPreview(
    content,
    Math.max(1, opts.previewLines ?? DEFAULT_PREVIEW_LINES),
  )
  const firstRangeLines = Math.max(
    1,
    Math.floor(opts.firstRangeLines ?? DEFAULT_FIRST_RANGE_LINES),
  )

  debugLog('long_input_rewritten', {
    sessionId,
    fileId,
    lineCount: persistedLineCount,
    estimatedTokens: estimatedToks,
    byteSize,
  })

  const rewritten = buildReferenceMessage({
    fileId,
    lineCount: persistedLineCount,
    sha256,
    preview,
    firstRangeLines,
  })
  return {
    rewritten: true,
    content: rewritten,
    fileId,
    lineCount,
    estimatedTokens: estimatedToks,
  }
}
