import { pipe } from "../prelude/compose"
import { none, type Option, some } from "../prelude/option"
// Phantom property type
declare const __props: unique symbol
type Props
= { [__props]?: P }
/**
* Tracked array with phantom properties.
* Properties like "Sorted" or "NonEmpty" are tracked at type level.
*/
export type Arr = readonly T[] & Props
// Property markers
export type Sorted = "Sorted"
export type NonEmpty = "NonEmpty"
// Constructor
export const arr = (xs: readonly T[]): Arr => xs as Arr
// NonEmpty check - returns Option to track the property
export const nonEmpty = (
xs: Arr,
): Option> =>
xs.length > 0 ? some(xs as Arr) : none
// Safe accessors for NonEmpty arrays
export const head = (xs: Arr): T => xs[0]!
export const last = (xs: Arr): T =>
xs[xs.length - 1]!
// Sorting - adds Sorted property
export const sortNum = (
xs: Arr,
): Arr =>
[...xs].sort((a, b) => a - b) as Arr
export const sortBy =
(key: (t: T) => number) =>
(xs: Arr): Arr =>
[...xs].sort((a, b) => key(a) - key(b)) as Arr
// Transformations - preserve properties where valid
export const map =
(f: (t: T) => U) =>
(xs: Arr): Arr> =>
xs.map(f) as Arr>
export const filter =
(pred: (t: T) => boolean) =>
(xs: Arr): Arr> =>
xs.filter(pred) as Arr>
export const reduce =
(f: (acc: U, t: T) => U, initial: U) =>
(xs: Arr): U =>
xs.reduce(f, initial)
// Take/drop
export const take =
(n: number) =>
(xs: Arr): Arr> =>
xs.slice(0, n) as Arr>
export const drop =
(n: number) =>
(xs: Arr): Arr> =>
xs.slice(n) as Arr>
// Generic binary search with comparator
export const binarySearch =
(compare: (a: T, b: T) => number) =>
(target: T) =>
(xs: Arr): Option => {
const go = (lo: number, hi: number): Option =>
lo > hi
? none
: pipe(Math.floor((lo + hi) / 2), (mid) =>
((cmp) =>
cmp === 0
? some(mid)
: cmp < 0
? go(lo, mid - 1)
: go(mid + 1, hi))(compare(target, xs[mid]!)),
)
return go(0, xs.length - 1)
}
// Binary search specialized for numbers
export const binarySearchNum = binarySearch((a, b) => a - b)