import { normalizeMarkdown } from '@/lib/markdown-diff'

type DiffOperation =
  | { type: 'equal'; line: string }
  | { type: 'delete'; line: string }
  | { type: 'insert'; line: string }

export type DiffHunk = {
  id: string
  originalStart: number
  originalEnd: number
  modifiedStart: number
  modifiedEnd: number
  removedLines: string[]
  addedLines: string[]
  preview: string
}

function diffOperations(originalLines: string[], modifiedLines: string[]) {
  const rows = originalLines.length
  const cols = modifiedLines.length
  const dp = Array.from({ length: rows + 1 }, () => new Array(cols + 1).fill(0))

  for (let row = 1; row <= rows; row += 1) {
    for (let col = 1; col <= cols; col += 1) {
      if (originalLines[row - 1] === modifiedLines[col - 1]) {
        dp[row][col] = dp[row - 1][col - 1] + 1
      } else {
        dp[row][col] = Math.max(dp[row - 1][col], dp[row][col - 1])
      }
    }
  }

  const operations: DiffOperation[] = []
  let row = rows
  let col = cols

  while (row > 0 || col > 0) {
    if (row > 0 && col > 0 && originalLines[row - 1] === modifiedLines[col - 1]) {
      operations.push({ type: 'equal', line: originalLines[row - 1] })
      row -= 1
      col -= 1
      continue
    }

    if (col > 0 && (row === 0 || dp[row][col - 1] >= dp[row - 1][col])) {
      operations.push({ type: 'insert', line: modifiedLines[col - 1] })
      col -= 1
      continue
    }

    operations.push({ type: 'delete', line: originalLines[row - 1] })
    row -= 1
  }

  return operations.reverse()
}

export function computeDiffHunks(original: string, modified: string): DiffHunk[] {
  const originalLines = normalizeMarkdown(original).split('\n')
  const modifiedLines = normalizeMarkdown(modified).split('\n')
  const operations = diffOperations(originalLines, modifiedLines)
  const hunks: DiffHunk[] = []
  let originalLine = 1
  let modifiedLine = 1
  let current:
    | (Omit<DiffHunk, 'id' | 'originalEnd' | 'modifiedEnd' | 'preview'> & {
        idSeed: number
      })
    | null = null

  const flush = () => {
    if (!current) {
      return
    }

    const previewSource = current.addedLines.join(' ').trim() || current.removedLines.join(' ').trim()
    hunks.push({
      id: `hunk-${current.idSeed}`,
      originalStart: current.originalStart,
      originalEnd: originalLine - 1,
      modifiedStart: current.modifiedStart,
      modifiedEnd: modifiedLine - 1,
      removedLines: current.removedLines,
      addedLines: current.addedLines,
      preview: previewSource.slice(0, 120) || '空白改动',
    })
    current = null
  }

  operations.forEach((operation) => {
    if (operation.type === 'equal') {
      flush()
      originalLine += 1
      modifiedLine += 1
      return
    }

    if (!current) {
      current = {
        idSeed: hunks.length + 1,
        originalStart: originalLine,
        modifiedStart: modifiedLine,
        removedLines: [],
        addedLines: [],
      }
    }

    if (operation.type === 'delete') {
      current.removedLines.push(operation.line)
      originalLine += 1
      return
    }

    current.addedLines.push(operation.line)
    modifiedLine += 1
  })

  flush()

  return hunks
}

export function applySelectedDiffHunks(
  original: string,
  hunks: DiffHunk[],
  selectedIds: Set<string>,
) {
  const originalLines = normalizeMarkdown(original).split('\n')
  const resultLines: string[] = []
  let cursor = 1

  hunks.forEach((hunk) => {
    const unchangedEnd = Math.max(hunk.originalStart - 1, cursor - 1)
    resultLines.push(...originalLines.slice(cursor - 1, unchangedEnd))

    if (selectedIds.has(hunk.id)) {
      resultLines.push(...hunk.addedLines)
    } else if (hunk.originalEnd >= hunk.originalStart) {
      resultLines.push(...originalLines.slice(hunk.originalStart - 1, hunk.originalEnd))
    }

    cursor = Math.max(hunk.originalEnd + 1, hunk.originalStart)
  })

  resultLines.push(...originalLines.slice(cursor - 1))

  return resultLines.join('\n')
}
