import { charCellWidth } from '@simon_he/vue-tui'

type CellPos = Readonly<{ row: number; col: number }>

interface GraphemeSegment {
  segment: string
}

let graphemeSegmenter: Intl.Segmenter | null = null
try {
  graphemeSegmenter =
    typeof Intl !== 'undefined' && 'Segmenter' in Intl
      ? new Intl.Segmenter(undefined, { granularity: 'grapheme' })
      : null
} catch {
  graphemeSegmenter = null
}

function forEachGrapheme(
  text: string,
  cb: (g: string, startCell: number, endCell: number) => void | false,
): void {
  if (!text) return

  let cells = 0
  const seg = graphemeSegmenter
  if (!seg) {
    for (const ch of text) {
      const w = ch.length > 0 ? charCellWidth(ch) : 0
      const r = cb(ch, cells, cells + w)
      cells += w
      if (r === false) return
    }
    return
  }

  for (const part of seg.segment(text) as any as Iterable<GraphemeSegment>) {
    const g = part.segment
    const w = charCellWidth(g)
    const r = cb(g, cells, cells + w)
    cells += w
    if (r === false) return
  }
}

export function normalizeCellRangeToText(
  text: string,
  startCol: number,
  endCol: number,
): { startCol: number; endCol: number } {
  startCol = Math.max(0, Math.floor(startCol))
  endCol = Math.max(0, Math.floor(endCol))
  if (endCol <= startCol) return { startCol, endCol }

  let normalizedStart = startCol
  let normalizedEnd = endCol

  forEachGrapheme(text, (_g, segStart, segEnd) => {
    if (startCol > segStart && startCol < segEnd) normalizedStart = segStart
    if (endCol > segStart && endCol < segEnd) normalizedEnd = segEnd
    // Early-exit if we've already passed both.
    if (segStart > endCol && segStart > startCol) return false
    return undefined
  })

  return { startCol: normalizedStart, endCol: normalizedEnd }
}

export function sliceByCellsRange(
  text: string,
  startCol: number,
  endCol: number,
): string {
  startCol = Math.max(0, Math.floor(startCol))
  endCol = Math.max(0, Math.floor(endCol))
  if (endCol <= startCol) return ''

  const norm = normalizeCellRangeToText(text, startCol, endCol)
  const start = norm.startCol
  const end = norm.endCol
  if (end <= start) return ''

  let out = ''
  forEachGrapheme(text, (g, segStart, segEnd) => {
    if (segEnd <= start) return undefined
    if (segStart >= end) return false
    out += g
    return undefined
  })
  return out
}

/**
 * Extract selected text from a terminal-like rendered line list.
 *
 * `row` is an absolute index in the full list (not relative to the viewport).
 */
export function extractSelectedText(
  lines: ReadonlyArray<Readonly<{ text?: string }>>,
  start: CellPos | null,
  end: CellPos | null,
): string {
  if (!start || !end) return ''

  const startRow = Math.min(start.row, end.row)
  const endRow = Math.max(start.row, end.row)
  const startCol =
    start.row < end.row
      ? start.col
      : start.row > end.row
        ? end.col
        : Math.min(start.col, end.col)
  const endCol =
    start.row < end.row
      ? end.col
      : start.row > end.row
        ? start.col
        : Math.max(start.col, end.col)

  const selectedLines: string[] = []
  for (let r = startRow; r <= endRow && r < lines.length; r++) {
    const lineText = lines[r]?.text ?? ''
    if (startRow === endRow) {
      selectedLines.push(sliceByCellsRange(lineText, startCol, endCol))
    } else if (r === startRow) {
      selectedLines.push(
        sliceByCellsRange(lineText, startCol, Number.MAX_SAFE_INTEGER),
      )
    } else if (r === endRow) {
      selectedLines.push(sliceByCellsRange(lineText, 0, endCol))
    } else {
      selectedLines.push(lineText)
    }
  }
  return selectedLines.join('\n').trim()
}

export function extractSelectedTextFromProvider(
  getLine: (row: number) => Readonly<{ text?: string }> | undefined,
  lineCount: number,
  start: CellPos | null,
  end: CellPos | null,
): string {
  if (!start || !end) return ''

  const startRow = Math.min(start.row, end.row)
  const endRow = Math.max(start.row, end.row)
  const startCol =
    start.row < end.row
      ? start.col
      : start.row > end.row
        ? end.col
        : Math.min(start.col, end.col)
  const endCol =
    start.row < end.row
      ? end.col
      : start.row > end.row
        ? start.col
        : Math.max(start.col, end.col)

  const safeCount = Math.max(0, Math.floor(lineCount))
  const selectedLines: string[] = []
  for (let r = startRow; r <= endRow && r < safeCount; r++) {
    const lineText = getLine(r)?.text ?? ''
    if (startRow === endRow) {
      selectedLines.push(sliceByCellsRange(lineText, startCol, endCol))
    } else if (r === startRow) {
      selectedLines.push(
        sliceByCellsRange(lineText, startCol, Number.MAX_SAFE_INTEGER),
      )
    } else if (r === endRow) {
      selectedLines.push(sliceByCellsRange(lineText, 0, endCol))
    } else {
      selectedLines.push(lineText)
    }
  }
  return selectedLines.join('\n').trim()
}
