/** * EmbedHelpers — utilities for working within Discord embed limits * and controlling inline field grid layout. * * Usage: * import { EmbedHelpers } from "@ui/embed-helpers"; * * EmbedHelpers.chunkRows(rows) * EmbedHelpers.addNationFields(embed, header, rows, inline) * EmbedHelpers.addPerPlayerGrid(embed, [{ header, rows }, { header, rows }]) */ interface EmbedLike { addFields: (...fields: { name: string; value: string; inline: boolean }[]) => any; } // ─── Chunking (for non-inline / single-field cases) ────────────────────────── function chunkRows(rows: string[], separator: string = "\n", maxLen: number = 1024): string[] { if (rows.length === 0) return []; const chunks: string[] = []; let current = ""; for (const row of rows) { const candidate = current ? `${current}${separator}${row}` : row; if (candidate.length > maxLen && current) { chunks.push(current); current = row; } else { current = candidate; } } if (current) chunks.push(current); console.log(`[EmbedHelpers] chunkRows: ${rows.length} rows -> ${chunks.length} chunk(s), lengths: ${chunks.map(c => c.length).join(", ")}`); return chunks; } function addNationFields( embed: EmbedLike, header: string, rows: string[], inline: boolean = false ): void { const chunks = chunkRows(rows); if (chunks.length === 0) { embed.addFields({ name: header, value: "—", inline }); return; } chunks.forEach((chunk, i) => { embed.addFields({ name: i === 0 ? header : "\u200b", value: chunk, inline, }); }); } // ─── Per-player grid ────────────────────────────────────────────────────────── /** * Render two columns as a clean 2-column grid, one player per field row. * The nation header is embedded as bold text at the top of the first * player's field (not a separate field) to avoid an extra header-to-content gap. */ function addPerPlayerGrid( embed: EmbedLike, columns: { header: string; rows: string[] }[] ): void { if (columns.length !== 2) { for (const col of columns) { addNationFields(embed, col.header, col.rows, true); } return; } const [left, right] = columns; const maxLen = Math.max(left.rows.length, right.rows.length, 1); for (let i = 0; i < maxLen; i++) { const leftRow = left.rows[i]; const rightRow = right.rows[i]; const leftValue = i === 0 ? `**${left.header}**\n${leftRow ?? "—"}` : (leftRow ?? "\u200b"); const rightValue = i === 0 ? `**${right.header}**\n${rightRow ?? "—"}` : (rightRow ?? "\u200b"); embed.addFields( { name: "\u200b", value: leftValue, inline: true }, { name: "\u200b", value: rightValue, inline: true }, { name: "\u200b", value: "\u200b", inline: true }, ); } } // ─── Single-column per-player fields ────────────────────────────────────────── /** * Render a single-column list, one player per field (no chunking needed — * each field only holds one row, well under the 1024 char limit regardless * of padding/content length). Header embedded as bold text in the first * player's field to avoid an extra header-to-content gap. */ function addPerPlayerColumn( embed: EmbedLike, header: string, rows: string[] ): void { if (rows.length === 0) { embed.addFields({ name: header, value: "—", inline: false }); return; } rows.forEach((row, i) => { const value = i === 0 ? `**${header}**\n\u200b\n${row}` : row; embed.addFields({ name: "\u200b", value, inline: false }); }); } // ─── Namespace ──────────────────────────────────────────────────────────────── export const EmbedHelpers = { chunkRows, addNationFields, addPerPlayerGrid, addPerPlayerColumn, };