import {
VisitorDataType,
CompoundField,
FieldNode,
FieldByType,
PrimitiveField,
RecordField,
TupleField,
VariantField,
} from "./types.js"
// ════════════════════════════════════════════════════════════════════════════
// Type Guards
// ════════════════════════════════════════════════════════════════════════════
/**
* Type guard for checking specific field types.
*
* @example
* ```tsx
* function FieldInput({ field }: { field: FieldNode }) {
* if (isFieldType(field, 'record')) {
* // field is now typed as RecordField
* return
* }
* if (isFieldType(field, 'text')) {
* // field is now typed as TextField
* return
* }
* // ...
* }
* ```
*/
export function isFieldType(
field: FieldNode,
type: T
): field is FieldByType {
return field.type === type
}
/**
* Check if a field is a compound type (contains other fields).
* Compound types: record, variant, tuple, optional, vector, recursive
*/
export function isCompoundField(field: FieldNode): field is CompoundField {
return [
"record",
"variant",
"tuple",
"optional",
"vector",
"recursive",
].includes(field.type)
}
/**
* Check if a field is a primitive type.
* Primitive types: principal, number, text, boolean, null
*/
export function isPrimitiveField(field: FieldNode): field is PrimitiveField {
return ["principal", "number", "text", "boolean", "null"].includes(field.type)
}
/**
* Check if a field has child fields array (for iteration).
* Applies to: record (fields), tuple (fields)
*/
export function hasChildFields(
field: FieldNode
): field is RecordField | TupleField {
return (
(field.type === "record" || field.type === "tuple") &&
"fields" in field &&
Array.isArray((field as RecordField).fields)
)
}
/**
* Check if a field has variant options (for iteration).
* Applies to: variant (options)
*/
export function hasOptions(field: FieldNode): field is VariantField {
return (
field.type === "variant" &&
"options" in field &&
Array.isArray((field as VariantField).options)
)
}
// ════════════════════════════════════════════════════════════════════════════
// Label Formatting Utilities
// ════════════════════════════════════════════════════════════════════════════
/**
* Format a raw Candid label into a human-readable display label.
* Handles common patterns like "__arg0", "_0_", "snake_case", etc.
*
* @example
* ```ts
* formatLabel("__arg0") // "Arg 0"
* formatLabel("_0_") // "Item 0"
* formatLabel("created_at") // "Created At"
* formatLabel("userAddress") // "User Address"
* formatLabel("__ret0") // "Result 0"
* ```
*/
export function formatLabel(label: string): string {
// Handle argument labels: __arg0 -> Arg 0
if (label.startsWith("__arg")) {
const num = label.slice(5)
return `Arg ${num}`
}
// Handle return labels: __ret0 -> Result 0
if (label.startsWith("__ret")) {
const num = label.slice(5)
return `Result ${num}`
}
// Handle tuple index labels: _0_ -> Item 0
if (/^_\d+_$/.test(label)) {
const num = label.slice(1, -1)
return `Item ${num}`
}
// Handle simple index labels: _0 -> Item 0
if (/^_\d+$/.test(label)) {
const num = label.slice(1)
return `Item ${num}`
}
// Handle item labels for vectors: label_item -> Item
if (label.endsWith("_item")) {
return "Item"
}
// Convert snake_case or just clean up underscores
// and capitalize each word
const trimmed = (() => {
let start = 0
let end = label.length
while (start < end && label[start] === "_") start++
while (end > start && label[end - 1] === "_") end--
return label.slice(start, end)
})()
return trimmed
.replace(/_/g, " ") // Replace underscores with spaces
.replace(/([a-z])([A-Z])/g, "$1 $2") // Add space before capitals (camelCase)
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ")
}