import ansi from 'bare-ansi-escapes'
const { SGR } = ansi.constants
// --- Text styling (wraps string with SGR codes) ---
export function bold (s) { return ansi.modifierBold + s + ansi.modifierNormal }
export function dim (s) { return ansi.modifierDim + s + ansi.modifierNormal }
export function italic (s) { return ansi.modifierItalic + s + ansi.modifierNotItalic }
export function underline (s) { return ansi.modifierUnderline + s + ansi.modifierNotUnderline }
export function red (s) { return ansi.colorRed + s + ansi.colorDefault }
export function green (s) { return ansi.colorGreen + s + ansi.colorDefault }
export function yellow (s) { return ansi.colorYellow + s + ansi.colorDefault }
export function blue (s) { return ansi.colorBlue + s + ansi.colorDefault }
export function magenta (s) { return ansi.colorMagenta + s + ansi.colorDefault }
export function cyan (s) { return ansi.colorCyan + s + ansi.colorDefault }
export function white (s) { return ansi.colorWhite + s + ansi.colorDefault }
export function brightBlack (s) { return ansi.colorBrightBlack + s + ansi.colorDefault }
export function brightCyan (s) { return ansi.colorBrightCyan + s + ansi.colorDefault }
export function brightGreen (s) { return ansi.colorBrightGreen + s + ansi.colorDefault }
export function brightYellow (s) { return ansi.colorBrightYellow + s + ansi.colorDefault }
export function bg256 (n, s) { return SGR('48;5;' + n) + s + SGR(49) }
export function fg256 (n, s) { return SGR('38;5;' + n) + s + SGR(39) }
export function invert (s) { return SGR(7) + s + SGR(27) }
// --- String measurement (strips ANSI) ---
const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g
export function stripAnsi (s) {
return s.replace(ANSI_RE, '')
}
export function visibleLength (s) {
return stripAnsi(s).length
}
// --- Layout helpers ---
export function truncate (s, maxWidth) {
if (maxWidth <= 0) return ''
const stripped = stripAnsi(s)
if (stripped.length <= maxWidth) return s
// Walk through, tracking visible chars
let visible = 0
let result = ''
let i = 0
const raw = s
while (i < raw.length && visible < maxWidth - 1) {
if (raw[i] === '\x1b') {
const end = raw.indexOf('m', i)
if (end !== -1) {
result += raw.slice(i, end + 1)
i = end + 1
continue
}
}
result += raw[i]
visible++
i++
}
return result + ansi.modifierReset + '\u2026'
}
export function padRight (s, width) {
const vl = visibleLength(s)
if (vl >= width) return s
return s + ' '.repeat(width - vl)
}
export function padLeft (s, width) {
const vl = visibleLength(s)
if (vl >= width) return s
return ' '.repeat(width - vl) + s
}
export function center (s, width) {
const vl = visibleLength(s)
if (vl >= width) return s
const left = Math.floor((width - vl) / 2)
const right = width - vl - left
return ' '.repeat(left) + s + ' '.repeat(right)
}
// --- Box drawing ---
const BOX = {
rounded: { tl: '\u256d', tr: '\u256e', bl: '\u2570', br: '\u256f', h: '\u2500', v: '\u2502' },
single: { tl: '\u250c', tr: '\u2510', bl: '\u2514', br: '\u2518', h: '\u2500', v: '\u2502' },
double: { tl: '\u2554', tr: '\u2557', bl: '\u255a', br: '\u255d', h: '\u2550', v: '\u2551' },
heavy: { tl: '\u250f', tr: '\u2513', bl: '\u2517', br: '\u251b', h: '\u2501', v: '\u2503' }
}
export function hline (width, style = 'single') {
const ch = (BOX[style] || BOX.single).h
return ch.repeat(width)
}
export function vline (style = 'single') {
return (BOX[style] || BOX.single).v
}
export function box (lines, width, opts = {}) {
const style = opts.border || 'rounded'
const title = opts.title || ''
const b = BOX[style] || BOX.rounded
const inner = width - 2
if (inner <= 0) return lines.map(() => '')
let topLine = b.tl
if (title) {
const t = ' ' + title + ' '
topLine += t + b.h.repeat(Math.max(0, inner - t.length))
} else {
topLine += b.h.repeat(inner)
}
topLine += b.tr
const bottom = b.bl + b.h.repeat(inner) + b.br
const result = [topLine]
for (const line of lines) {
const content = truncate(padRight(line, inner), inner)
result.push(b.v + content + b.v)
}
result.push(bottom)
return result
}