import { Arr, memoizeFunction, Result } from 'ts-data-forge';
import {
type FixedLengthTuple,
type StructuralPrefixLength,
} from 'ts-type-forge';
import { type Type } from '../type.mjs';
import {
createAssertFn,
createCastFn,
createIsFn,
createPrimitiveValidationError,
prependIndexToValidationErrors,
type ValidationError,
} from '../utils/index.mjs';
export type { FixedLengthTuple } from 'ts-type-forge';
export const fixedLengthTuple = (
size: N,
elementType: Type,
options?: Partial<
Readonly<{
typeName: string;
defaultValue: FixedLengthTuple;
}>
>,
): Type> => {
type T = FixedLengthTuple;
const typeName =
options?.typeName ?? `FixedLengthTuple<${size}, ${elementType.typeName}>`;
const getDefaultValue = memoizeFunction(
(): T =>
options?.defaultValue ??
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
(Arr.create(size, elementType.defaultValue) as T),
);
const validate: Type['validate'] = (a) => {
if (!Arr.isArray(a)) {
return Result.err([
createPrimitiveValidationError({
actualValue: a,
expectedType: 'array',
typeName,
details: undefined,
}),
]);
}
if (!Arr.isFixedLengthTuple(a, size)) {
return Result.err([
{
path: [],
actualValue: a,
expectedType: typeName,
typeName,
details: {
kind: 'array-length',
expectedLength: size,
actualLength: a.length,
},
} satisfies ValidationError,
]);
}
const errors: readonly ValidationError[] = Arr.generate(function* () {
for (const [index, el] of a.entries()) {
const res = elementType.validate(el);
if (Result.isErr(res)) {
yield* prependIndexToValidationErrors(res.value, index);
}
}
});
if (Arr.isNonEmpty(errors)) {
return Result.err(errors);
}
return Result.ok(a);
};
const fill: Type['fill'] = (a) =>
!Arr.isArray(a)
? getDefaultValue()
: // TODO: remove as
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
(Arr.map(
Arr.seq(size),
(i) => elementType.fill(a[i]) satisfies A,
) as T);
const prune = (a: T): T =>
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
Arr.map(a, (el) => elementType.prune(el)) as T;
return {
typeName,
get defaultValue() {
return getDefaultValue();
},
fill,
prune,
validate,
is: createIsFn(validate),
cast: createCastFn(validate),
assertIs: createAssertFn(validate),
};
};