import { type Opaque } from 'type-fest';

export type NonEmptyArray<T> = [T, ...T[]];

export type ReadonlyDeep<T> = T extends object
  ? { readonly [P in keyof T]: ReadonlyDeep<T[P]> }
  : T;

/**
 * Converts a value to JSON, while preserving its type for future inspection.
 *
 * Sometimes, we need to stringify a value (e.g., to use the string as a key),
 * but we'd still like to Typescript to track the original type that we
 * stringified, so that we can have type checking on the data we'll get back
 * if/when we JSON.parse the string later. That's what this `jsonStringify`
 * helper function does. See {@link jsonParse}
 */
export function jsonStringify<T>(it: T) {
  return JSON.stringify(it) as JsonOf<T>;
}

/**
 * Parses the JSON, and returns its original type, for JSON generated by
 * {@link jsonStringify}.
 */
export function jsonParse<T extends JsonOf<unknown>>(it: T) {
  return JSON.parse(it) as (typeof it)[typeof meta];
}

declare const meta: unique symbol;
export type JsonOf<T> = Opaque<string, 'JSON'> & { [meta]: T };

// A convenient helper for building types that have different values based on
// some boolean generic.
export type If<Cond extends boolean, IfTrue, IfFalse> = Cond extends true
  ? IfTrue
  : Cond extends false
    ? IfFalse
    : IfTrue | IfFalse;
