import type { Style } from '@simon_he/vue-tui'
import type { DiffLine } from './render-utils'
import { textCellWidth } from '@simon_he/vue-tui'
import { computeLineDiff } from './render-utils'
import { sanitizeTerminalText } from './sanitize-terminal-text'

export interface SideBySideDiffRow {
  leftIndex: number | null
  rightIndex: number | null
}

export function computeSideBySideDiffRows(
  lines: readonly DiffLine[],
): SideBySideDiffRow[] {
  const rows: SideBySideDiffRow[] = []
  let i = 0

  while (i < lines.length) {
    const line = lines[i]!
    if (line.type === 'removed') {
      const removedStart = i
      let removedEnd = i
      while (removedEnd < lines.length && lines[removedEnd]!.type === 'removed')
        removedEnd++

      const addedStart = removedEnd
      let addedEnd = addedStart
      while (addedEnd < lines.length && lines[addedEnd]!.type === 'added')
        addedEnd++

      const removedCount = removedEnd - removedStart
      const addedCount = addedEnd - addedStart
      const pairs = Math.max(removedCount, addedCount)

      for (let k = 0; k < pairs; k++) {
        rows.push({
          leftIndex: k < removedCount ? removedStart + k : null,
          rightIndex: k < addedCount ? addedStart + k : null,
        })
      }

      i = addedEnd
      continue
    }

    if (line.type === 'added') {
      rows.push({ leftIndex: null, rightIndex: i })
      i++
      continue
    }

    // Context: render on both sides.
    rows.push({ leftIndex: i, rightIndex: i })
    i++
  }

  return rows
}

export function getTrimmedDiffLines(
  oldString: string,
  newString: string,
  opts?: Readonly<{ maxContextLines?: number; maxLines?: number }>,
): DiffLine[] {
  if (!oldString && !newString) return []

  const diffLines = computeLineDiff(oldString, newString)
  const maxDiffLines = opts?.maxLines ?? 24
  const maxContextLines = opts?.maxContextLines ?? 3
  return trimDiffLines(diffLines, maxContextLines, maxDiffLines)
}

interface RenderDiffContext {
  pushLine: (text: string, style?: Style) => void
  detailStyle: Style
  width: number
  maxLines?: number
  lineStyles?: Partial<Record<DiffLine['type'], Style>>
}

/**
 * Renders a detailed diff view with color-coded additions and removals.
 */
export function renderDiffDetail(
  ctx: RenderDiffContext,
  oldString: string,
  newString: string,
): void {
  if (!oldString && !newString) return

  const trimmedDiff = getTrimmedDiffLines(oldString, newString, {
    maxContextLines: 3,
    maxLines: ctx.maxLines ?? 24,
  })

  const maxOld = trimmedDiff.reduce(
    (m, l) => Math.max(m, Number(l.oldLine ?? 0)),
    0,
  )
  const maxNew = trimmedDiff.reduce(
    (m, l) => Math.max(m, Number(l.newLine ?? 0)),
    0,
  )
  const lnW = Math.max(2, String(Math.max(maxOld, maxNew, 1)).length)

  const codeStyle = (ctx.lineStyles?.context ?? {
    ...ctx.detailStyle,
    fg: 'yellowBright',
  }) satisfies Style
  const removeStyle = (ctx.lineStyles?.removed ?? {
    ...ctx.detailStyle,
    fg: 'red',
  }) satisfies Style
  const addStyle = (ctx.lineStyles?.added ?? {
    ...ctx.detailStyle,
    fg: 'green',
  }) satisfies Style

  for (const line of trimmedDiff) {
    const marker =
      line.type === 'removed' ? '-' : line.type === 'added' ? '+' : ' '
    const lineNumber =
      line.type === 'removed'
        ? line.oldLine
        : line.type === 'added'
          ? line.newLine
          : (line.newLine ?? line.oldLine)
    const lnText =
      typeof lineNumber === 'number'
        ? String(lineNumber).padStart(lnW, ' ')
        : ' '.repeat(lnW)
    const lineStyle =
      line.type === 'removed'
        ? removeStyle
        : line.type === 'added'
          ? addStyle
          : codeStyle
    const content = sanitizeTerminalText(line.content)
    const raw = `  ${lnText} ${marker} ${content}`
    const shouldPad = Boolean(lineStyle?.bg)
    if (!shouldPad) {
      ctx.pushLine(raw, lineStyle)
      continue
    }

    // For lines with a background, pad to terminal width so the bg fills the whole row.
    // `pushLine` will prepend 2 spaces (contentIndent), so pad to `width - 2`.
    const target = Math.max(0, ctx.width - 2)
    const current = textCellWidth(raw)
    const padCells = Math.max(0, target - current)
    ctx.pushLine(`${raw}${' '.repeat(padCells)}`, lineStyle)
  }
}

/**
 * Trims diff lines to show relevant context around changes.
 * Shows up to maxContextLines consecutive context lines, then adds ellipsis.
 */
function trimDiffLines(
  diffLines: DiffLine[],
  maxContextLines: number,
  maxLines: number,
): DiffLine[] {
  const result: DiffLine[] = []
  let contextCount = 0

  for (let i = 0; i < diffLines.length; i++) {
    const line = diffLines[i]
    if (line.type === 'context') {
      // Show context lines near changes, skip middle context
      const prevLine = i > 0 ? diffLines[i - 1] : null
      const nextLine = i < diffLines.length - 1 ? diffLines[i + 1] : null
      const nearChange =
        prevLine?.type !== 'context' || nextLine?.type !== 'context'

      if (nearChange || contextCount < maxContextLines) {
        result.push(line)
        if (nearChange) contextCount = 0
        else contextCount++
      } else if (contextCount === maxContextLines) {
        // Add ellipsis for skipped context
        result.push({ type: 'context', content: '…' })
        contextCount++
      }
    } else {
      result.push(line)
      contextCount = 0
    }

    if (result.length >= maxLines) {
      result.push({ type: 'context', content: '… (more lines truncated)' })
      break
    }
  }

  return result
}
