/**
 * output-style/helpers.mjs — C22 composable render helpers.
 *
 * All helpers accept `{ color }` (default `true`). When `color` is false, the
 * return value is plain ASCII/Unicode with no ANSI escapes — so pipelines into
 * `jq`, `tee -a file`, or a log line always look identical.
 *
 * The underlying SGR wrapping lives in ansi.mjs. This module is pure string
 * formatting; no I/O, no process globals, no date `Date.now()` mutation —
 * relative-time takes an explicit `now` so tests stay deterministic.
 */
import { bold, cyan, dim, gray, green, red, yellow } from './ansi.mjs';
const BULLET = '·';
const ICON_OK = '✓';
const ICON_FAIL = '✗';
const ICON_PENDING = '⋯';
const EM_DASH = '—';
/**
 * @typedef {{ color?: boolean }} ColorOpts
 */
/**
 * Bold header line with optional trailing newline suppressed for composition.
 *
 * @param {string} title
 * @param {ColorOpts} [opts]
 * @returns {string}
 */
export function section(title, { color = true } = {}) {
    return bold(String(title), { color });
}
/**
 * `· text` bullet with an optional status icon.
 *
 * status:
 *   'ok'      → green ✓
 *   'fail'    → red   ✗
 *   'pending' → yellow ⋯
 *   undefined → bare   ·
 *
 * @param {string} text
 * @param {'ok'|'fail'|'pending'|string} [status]
 * @param {ColorOpts} [opts]
 * @returns {string}
 */
export function bullet(text, status, { color = true } = {}) {
    if (!status) {
        return `${dim(BULLET, { color })} ${text}`;
    }
    let icon;
    if (status === 'ok')
        icon = green(ICON_OK, { color });
    else if (status === 'fail')
        icon = red(ICON_FAIL, { color });
    else if (status === 'pending')
        icon = yellow(ICON_PENDING, { color });
    else
        icon = dim(BULLET, { color });
    return `${icon} ${text}`;
}
/**
 * `dim(key): bold(value)` — the canonical "one fact per line" format used in
 * the TTY-sidecar previews on `state` / `active`.
 *
 * @param {string} key
 * @param {unknown} value
 * @param {ColorOpts} [opts]
 * @returns {string}
 */
export function kv(key, value, { color = true } = {}) {
    const k = dim(`${key}:`, { color });
    const v = bold(value === null || value === undefined ? EM_DASH : String(value), { color });
    return `${k} ${v}`;
}
/**
 * Minimal aligned ASCII table. `rows` is an array of objects; `headers`
 * defaults to the keys of the first row. Returns `''` for an empty rows list
 * (no blank-table noise on TTY-sidecar output).
 *
 * @param {Array<Record<string, unknown>> | null | undefined} rows
 * @param {string[]} [headers]
 * @param {ColorOpts} [opts]
 * @returns {string}
 */
export function table(rows, headers, { color = true } = {}) {
    if (!Array.isArray(rows) || rows.length === 0)
        return '';
    const cols = Array.isArray(headers) && headers.length > 0 ? headers : Object.keys(rows[0] || {});
    if (cols.length === 0)
        return '';
    // Pre-stringify every cell so we can compute widths without double work.
    const body = rows.map((row) => cols.map((c) => {
        const v = /** @type {Record<string, unknown>} */ (row)[c];
        return v === undefined || v === null ? '' : String(v);
    }));
    const widths = cols.map((c, i) => Math.max(c.length, ...body.map((r) => r[i].length)));
    const head = cols.map((c, i) => bold(c.padEnd(widths[i]), { color })).join('  ');
    const sep = widths.map((w) => '-'.repeat(w)).join('  ');
    const lines = body.map((r) => r.map((cell, i) => cell.padEnd(widths[i])).join('  '));
    return [head, sep, ...lines].join('\n');
}
/**
 * Relative-time formatter — "just now", "5s ago", "12m ago", "3h ago",
 * "2d ago". Accepts any ISO string or epoch ms. Takes explicit `now` so unit
 * tests don't need to freeze clocks.
 *
 * Returns `'—'` when the input is missing/unparseable so composition always
 * produces a fixed-width line.
 *
 * @param {string | number | null | undefined} when
 * @param {number} [now]
 * @returns {string}
 */
export function relativeTime(when, now = Date.now()) {
    if (!when)
        return EM_DASH;
    const t = typeof when === 'number' ? when : Date.parse(when);
    if (!Number.isFinite(t))
        return EM_DASH;
    const diffMs = now - t;
    if (diffMs < 0)
        return 'in the future';
    const s = Math.floor(diffMs / 1000);
    if (s < 5)
        return 'just now';
    if (s < 60)
        return `${s}s ago`;
    const m = Math.floor(s / 60);
    if (m < 60)
        return `${m}m ago`;
    const h = Math.floor(m / 60);
    if (h < 24)
        return `${h}h ago`;
    const d = Math.floor(h / 24);
    return `${d}d ago`;
}
/**
 * @typedef {Object} StatusLineData
 * @property {number | Record<string, unknown>} [active_tasks]
 * @property {string | null} [current_phase]
 * @property {string | number | null} [last_activity]
 * @property {number} [team_count]
 */
/**
 * @typedef {Object} StatusLineOpts
 * @property {boolean} [color]
 * @property {number} [now]
 */
// C56 B5 — compact phase labels. Keeps the statusline narrow when the
// underlying lifecycle value is multi-hyphenated (`shipped-pending-merge`).
// Unknown values render verbatim — additive enum values pass through.
/** @type {Record<string, string>} */
const PHASE_LABEL = {
    'shipped-pending-merge': 'shipped',
};
/**
 * Compact single-line status string suitable for embedding in a shell prompt
 * or external statusline tool. Missing fields render as dim em-dashes so the
 * line has a consistent shape.
 *
 * Shape:
 *   {icon} aperant {dim(·)} tasks {count} {dim(·)} phase {name} {dim(·)} team {n} {dim(·)} {relative}
 *
 * @param {StatusLineData} [data]
 * @param {StatusLineOpts} [opts]
 * @returns {string}
 */
export function statusLine({ active_tasks = 0, current_phase = null, last_activity = null, team_count = 0 } = {}, { color = true, now = undefined } = {}) {
    const taskCount = typeof active_tasks === 'number' ? active_tasks : Object.keys(active_tasks || {}).length;
    const icon = taskCount > 0 ? green('●', { color }) : gray('○', { color });
    const sep = dim(BULLET, { color });
    const phaseLabel = current_phase ? (PHASE_LABEL[current_phase] ?? String(current_phase)) : null;
    const phase = phaseLabel ? cyan(phaseLabel, { color }) : dim(EM_DASH, { color });
    const rel = last_activity ? relativeTime(last_activity, now) : EM_DASH;
    const relStyled = last_activity ? dim(rel, { color }) : dim(EM_DASH, { color });
    const label = bold('aperant', { color });
    return `${icon} ${label} ${sep} tasks ${bold(String(taskCount), { color })} ${sep} phase ${phase} ${sep} team ${bold(String(team_count), { color })} ${sep} ${relStyled}`;
}
//# sourceMappingURL=helpers.mjs.map