import {Call} from 'hotscript'
import {Numbers} from 'hotscript'
import {Tuples} from 'hotscript'

export declare type AdjustIndex<
  Pos extends 'before' | 'after',
  Index extends number,
> = Pos extends 'before' ? Index : Call<Numbers.Add, Index, 1>

export declare type AnyArray<T = any> = T[] | readonly T[]

export declare type AnyEmptyArray = [] | readonly []

export declare type AnyOp = SetOp<unknown> | SetIfMissingOp<unknown> | UnsetOp

export declare type ApplyAtIndex<
  Index extends number,
  Tail extends AnyArray,
  Op extends Operation,
  Arr extends AnyArray,
> = [
  ...Call<Tuples.Take<Index, Arr>>,
  ApplyAtPath<Tail, Op, Arr[Index]>,
  ...Call<Tuples.Drop<Call<Numbers.Add<Index, 1>>, Arr>>,
]

export declare type ApplyAtPath<
  Pth extends Path,
  Op extends Operation,
  Node,
> = Pth extends EmptyArray
  ? ApplyOp<Op, Node>
  : Pth extends [infer Head, ...infer Tail]
    ? Node extends AnyArray
      ? ApplyInArray<Head, Tail, Op, Node>
      : Node extends {
            [K in string]: unknown
          }
        ? ApplyInObject<Head, Tail, Op, Node>
        : never
    : never

export declare type ApplyAtSelector<
  Selector extends KeyedPathElement,
  Tail extends AnyArray,
  Op extends Operation,
  Arr extends AnyArray,
> =
  FirstIndexOf<0, Selector, Arr> extends infer Index
    ? Index extends number
      ? ApplyAtIndex<Index, Tail, Op, Arr>
      : Arr
    : Arr

export declare type ApplyInArray<
  ItemSelector,
  Tail extends AnyArray,
  Op extends Operation,
  Arr extends AnyArray,
> = ItemSelector extends number
  ? ApplyAtIndex<ItemSelector, Tail, Op, Arr>
  : ItemSelector extends KeyedPathElement
    ? ApplyAtSelector<ItemSelector, Tail, Op, Arr>
    : never

export declare function applyInCollection<Doc extends SanityDocumentBase>(
  collection: Doc[],
  mutations: Mutation | Mutation[],
): Doc[]

export declare function applyInIndex<
  Doc extends SanityDocumentBase,
  Index extends DocumentIndex<ToStored<Doc>>,
>(index: Index, mutations: Mutation<Doc>[]): Index

export declare type ApplyInObject<
  Head,
  Tail extends AnyArray,
  Op extends Operation,
  Node,
> = Head extends keyof Node
  ? {
      [K in keyof Node]: K extends Head
        ? ApplyAtPath<Tail, Op, PickOrUndef<Node, Head>>
        : Node[K]
    }
  : Tail extends EmptyArray
    ? Head extends string
      ? Format<
          Node & {
            [K in Head]: ApplyOp<Op, undefined>
          }
        >
      : never
    : Node

export declare type ApplyNodePatch<Patch extends NodePatch, Node> =
  Patch extends NodePatch<infer P, infer Op>
    ? ApplyAtPath<P, Op, Node>
    : ApplyAtPath<Patch['path'], Patch['op'], Node>

export declare function applyNodePatch<
  const Patch extends NodePatch,
  const Doc,
>(patch: Patch, document: Doc): ApplyNodePatch<Patch, Doc>

export declare type ApplyOp<
  O extends Operation,
  Current,
> = Current extends never
  ? never
  : O extends SetOp<infer Next>
    ? Next
    : O extends UnsetOp
      ? undefined
      : O extends IncOp<infer Amount>
        ? Current extends number
          ? number extends Current
            ? number
            : Call<Numbers.Add, Current, Amount>
          : Current
        : O extends DecOp<infer Amount>
          ? Current extends number
            ? number extends Current
              ? number
              : Call<Numbers.Sub, Current, Amount>
            : Current
          : O extends InsertOp<infer Items, infer Pos, infer Ref>
            ? Current extends AnyArray<unknown>
              ? ArrayInsert<
                  NormalizeReadOnlyArray<Current>,
                  NormalizeReadOnlyArray<Items>,
                  Pos,
                  Ref
                >
              : Current
            : O extends ReplaceOp<infer Items, infer Ref>
              ? Current extends any[]
                ? (ArrayElement<Items> | ArrayElement<Current>)[]
                : never
              : O extends AssignOp<infer U>
                ? Assign<Current, U>
                : O extends SetIfMissingOp<infer V>
                  ? Current extends undefined | null
                    ? V
                    : Current
                  : O extends UnassignOp<infer U>
                    ? {
                        [K in keyof Current as Exclude<
                          K,
                          ArrayElement<U>
                        >]: Current[K]
                      }
                    : O extends DiffMatchPatchOp
                      ? string
                      : never

export declare function applyOp<const Op extends AnyOp, const CurrentValue>(
  op: Op,
  currentValue: CurrentValue,
): ApplyOp<Op, CurrentValue>

export declare function applyOp<
  const Op extends NumberOp,
  const CurrentValue extends number,
>(op: Op, currentValue: CurrentValue): ApplyOp<Op, CurrentValue>

export declare function applyOp<
  const Op extends StringOp,
  const CurrentValue extends string,
>(op: Op, currentValue: CurrentValue): ApplyOp<Op, CurrentValue>

export declare function applyOp<
  const Op extends ObjectOp,
  const CurrentValue extends {
    [k in keyof any]: unknown
  },
>(op: Op, currentValue: CurrentValue): ApplyOp<Op, CurrentValue>

export declare function applyOp<
  const Op extends ArrayOp,
  const CurrentValue extends AnyArray,
>(op: Op, currentValue: CurrentValue): ApplyOp<Op, CurrentValue>

export declare type ApplyPatches<Patches, Node> = Patches extends [
  infer HeadPatch,
  ...infer TailPatch,
]
  ? HeadPatch extends NodePatch
    ? TailPatch extends []
      ? ApplyNodePatch<HeadPatch, Node>
      : TailPatch extends NodePatch[]
        ? ApplyPatches<TailPatch, ApplyNodePatch<HeadPatch, Node>>
        : Node
    : Node
  : Node

export declare function applyPatches<Patches extends NodePatchList, const Doc>(
  patches: Patches,
  document: Doc,
): ApplyPatches<NormalizeReadOnlyArray<Patches>, Doc>

export declare type ApplyPatchMutation<
  Mutation extends PatchMutation,
  Doc extends SanityDocumentBase,
> =
  Mutation extends PatchMutation<infer Patches>
    ? ApplyPatches<NormalizeReadOnlyArray<Patches>, Doc>
    : Doc

export declare function applyPatchMutation<
  const Mutation extends PatchMutation,
  const Doc extends SanityDocumentBase,
>(mutation: Mutation, document: Doc): ApplyPatchMutation<Mutation, Doc>

export declare type ArrayElement<A> = A extends readonly (infer T)[] ? T : never

export declare type ArrayInsert<
  Current extends unknown[],
  Items extends unknown[],
  Pos extends 'before' | 'after',
  Ref extends number | KeyedPathElement,
> = Current extends (infer E)[]
  ? number extends Ref
    ? (E | ArrayElement<Items>)[]
    : Ref extends number
      ? InsertAtIndex<Current, Items, Pos, Ref>
      : (E | ArrayElement<Items>)[]
  : Current

export declare type ArrayLength<T extends AnyArray> = T extends never[]
  ? 0
  : T['length']

export declare type ArrayOp =
  | InsertOp<AnyArray, RelativePosition, Index | KeyedPathElement>
  | UpsertOp<AnyArray, RelativePosition, Index | KeyedPathElement>
  | ReplaceOp<AnyArray, Index | KeyedPathElement>
  | TruncateOp

export declare type Assign<Current, Attrs> = {
  [K in keyof Attrs | keyof Current]: K extends keyof Attrs
    ? Attrs[K]
    : K extends keyof Current
      ? Current[K]
      : never
}

export declare function assignId<Doc extends SanityDocumentBase>(
  doc: Doc,
  generateId: () => string,
): Doc & {
  _id: string
}

export declare type AssignOp<T extends object = object> = {
  type: 'assign'
  value: T
}

export declare type Between<
  Num extends number,
  Min extends number,
  Max extends number,
> =
  Call<Numbers.GreaterThanOrEqual<Num, Min>> extends true
    ? Call<Numbers.LessThanOrEqual<Num, Max>> extends true
      ? true
      : false
    : false

export declare type ByIndex<P extends number, T extends AnyArray> = T[P]

export declare type Concat<
  R extends Result<any, any>,
  Arr extends any[],
> = R[1] extends any[] ? Ok<[...R[1], ...Arr]> : R

export declare type ConcatInner<
  R extends Result<any, any>,
  R2 extends Result<any, any>,
> = R2[1] extends any[] ? Concat<R, R2[1]> : R2

export declare type CreateIfNotExistsMutation<Doc extends SanityDocumentBase> =
  {
    type: 'createIfNotExists'
    document: Doc
  }

export declare type CreateMutation<
  Doc extends Optional<SanityDocumentBase, '_id'>,
> = {
  type: 'create'
  document: Doc
}

export declare type CreateOrReplaceMutation<Doc extends SanityDocumentBase> = {
  type: 'createOrReplace'
  document: Doc
}

export declare const createStore: <Doc extends SanityDocumentBase>(
  initialEntries?: Doc[],
) => {
  readonly version: number
  entries: () => [string, Format<ToStored<Doc & SanityDocumentBase>>][]
  get: <Id extends string>(
    id: Id,
  ) => Format<
    Omit<Format<ToStored<Doc & SanityDocumentBase>>, '_id'> & {
      _id: Id
    }
  >
  apply: (mutations: Mutation[] | Mutation) => void
}

export declare type DecOp<Amount extends number> = {
  type: 'dec'
  amount: Amount
}

export declare type DeleteMutation = {
  type: 'delete'
  id: string
}

export declare type DiffMatchPatchOp = {
  type: 'diffMatchPatch'
  value: string
}

export declare type Digit =
  | '0'
  | '1'
  | '2'
  | '3'
  | '4'
  | '5'
  | '6'
  | '7'
  | '8'
  | '9'

export declare type DocumentIndex<Doc extends SanityDocumentBase> = {
  [id: string]: Doc
}

export declare type ElementType<T extends AnyArray> =
  T extends AnyArray<infer E> ? E : unknown

export declare type EmptyArray = never[] | readonly never[] | [] | readonly []

export declare type Err<E> = Result<E, null>

export declare type FindBy<P, T extends AnyArray> = T extends AnyEmptyArray
  ? undefined
  : T[0] extends P
    ? T[0]
    : T extends [any, ...infer Tail] | readonly [any, ...infer Tail]
      ? FindBy<P, Tail>
      : ElementType<T>

export declare type FindInArray<
  P extends KeyedPathElement | number,
  T extends AnyArray,
> = P extends KeyedPathElement
  ? FindBy<P, T>
  : P extends number
    ? ByIndex<P, T>
    : never

export declare type FirstIndexOf<
  StartIndex extends number,
  Selector extends KeyedPathElement,
  Arr extends AnyArray,
> = Arr extends [infer Head, ...infer Tail]
  ? Head extends Selector
    ? StartIndex
    : FirstIndexOf<Call<Numbers.Add<StartIndex>, 1>, Selector, Tail>
  : null

/**
 * Formats an intersection object type, so it outputs as `{"foo": 1, "bar": 1}` instead of `{"foo": 1} & {"bar": 2}``
 */
export declare type Format<A> = A extends {
  [Key in keyof A]: A[Key]
}
  ? {
      [Key in keyof A]: A[Key]
    }
  : A

export declare type Get<
  P extends number | KeyedPathElement | Readonly<KeyedPathElement> | string,
  T,
> = T extends AnyArray
  ? P extends KeyedPathElement | Readonly<KeyedPathElement> | number
    ? FindInArray<P, T>
    : undefined
  : P extends keyof T
    ? T[P]
    : never

export declare type GetAtPath<
  P extends readonly PathElement[],
  T,
> = P extends []
  ? T
  : P extends [infer Head, ...infer Tail]
    ? Head extends PathElement
      ? Tail extends PathElement[]
        ? GetAtPath<Tail, Get<Head, T>>
        : undefined
      : undefined
    : undefined

export declare function getAtPath<const Head extends PathElement, const T>(
  path: [head: Head],
  value: T,
): Get<Head, T>

export declare function getAtPath<
  const Head extends PathElement,
  const Tail extends PathElement[],
  T,
>(path: [head: Head, ...tail: Tail], value: T): GetAtPath<[Head, ...Tail], T>

export declare function getAtPath<T>(path: [], value: T): T

export declare function getAtPath(path: Path, value: unknown): unknown

export declare function hasId(doc: SanityDocumentBase): doc is StoredDocument

export declare type IncOp<Amount extends number> = {
  type: 'inc'
  amount: Amount
}

export declare type Index = number

export declare type InsertAtIndex<
  Current extends unknown[],
  Values extends unknown[],
  Pos extends 'before' | 'after',
  Index extends number,
> = _InsertAtIndex<
  Current,
  Values,
  Pos,
  NormalizeIndex<Index, ArrayLength<Current>>
>

export declare type _InsertAtIndex<
  Current extends unknown[],
  Values extends unknown[],
  Pos extends 'before' | 'after',
  NormalizedIndex extends number,
> =
  Between<NormalizedIndex, 0, ArrayLength<Current>> extends true
    ? SplitAtPos<Current, NormalizedIndex, Pos> extends [infer Head, infer Tail]
      ? Head extends AnyArray
        ? Tail extends AnyArray
          ? [
              ...(Head extends never[] ? [] : Head),
              ...Values,
              ...(Tail extends never[] ? [] : Tail),
            ]
          : never
        : never
      : never
    : Current

export declare type InsertOp<
  Items extends AnyArray,
  Pos extends RelativePosition,
  ReferenceItem extends Index | KeyedPathElement,
> = {
  type: 'insert'
  referenceItem: ReferenceItem
  position: Pos
  items: Items
}

export declare function isArrayElement(
  element: PathElement,
): element is KeyedPathElement | number

export declare function isElementEqual(
  segmentA: PathElement,
  segmentB: PathElement,
): boolean

export declare function isEqual(path: Path, otherPath: Path): boolean

export declare function isIndexElement(segment: PathElement): segment is number

export declare function isKeyedElement(
  element: PathElement,
): element is KeyedPathElement

export declare function isKeyElement(
  segment: PathElement,
): segment is KeyedPathElement

export declare function isPropertyElement(
  element: PathElement,
): element is string

export declare type KeyedPathElement = {
  _key: string
}

export declare type LastIndexOnEmptyArray<Index, Length> = Length extends 0
  ? Index extends -1
    ? true
    : false
  : false

export declare type Merge<R extends Result<any, any>, E> = R[0] extends null
  ? Ok<R[1] & E>
  : R

export declare type MergeInner<
  R extends Result<any, any>,
  R2 extends Result<any, any>,
> = R2[0] extends null ? Merge<R, R2[1]> : R

export declare type Mutation<Doc extends SanityDocumentBase = any> =
  | CreateMutation<Doc>
  | CreateIfNotExistsMutation<Doc>
  | CreateOrReplaceMutation<Doc>
  | DeleteMutation
  | PatchMutation

export declare type NodePatch<
  P extends Path = Path,
  O extends Operation = Operation,
> = {
  path: P
  op: O
}

export declare type NodePatchList =
  | [NodePatch, ...NodePatch[]]
  | NodePatch[]
  | readonly NodePatch[]
  | readonly [NodePatch, ...NodePatch[]]

export declare function normalize(path: string | Readonly<Path>): Readonly<Path>

export declare type NormalizeIndex<
  Index extends number,
  Length extends number,
> =
  LastIndexOnEmptyArray<Index, Length> extends true
    ? 0
    : Call<Numbers.LessThan<Index, 0>> extends true
      ? Call<Numbers.Add, Length, Index>
      : Index

export declare type NormalizeReadOnlyArray<T> = T extends readonly [
  infer NP,
  ...infer Rest,
]
  ? [NP, ...Rest]
  : T extends readonly (infer NP)[]
    ? NP[]
    : T

export declare type NumberOp = IncOp<number> | DecOp<number>

export declare type ObjectOp = AssignOp | UnassignOp

export declare type Ok<V> = Result<null, V>

export declare type OnlyDigits<S> = S extends `${infer Head}${infer Tail}`
  ? Head extends Digit
    ? Tail extends ''
      ? true
      : OnlyDigits<Tail> extends true
        ? true
        : false
    : false
  : false

export declare type Operation = PrimitiveOp | ArrayOp | ObjectOp

export declare type Optional<T, K extends keyof T> = Omit<T, K> &
  Partial<Pick<T, K>>

export declare function parse<const T extends string>(path: T): StringToPath<T>

export declare type ParseAllProps<Props extends string[]> = Props extends [
  `${infer Head}`,
  ...infer Tail,
]
  ? Tail extends string[]
    ? ConcatInner<ParseProperty<Trim<Head>>, ParseAllProps<Tail>>
    : ParseProperty<Trim<Head>>
  : Ok<[]>

export declare type ParseError<T extends string = 'unknown'> = T & {
  error: true
}

export declare type ParseExpressions<S extends string> =
  S extends `[${infer Expr}]${infer Remainder}`
    ? Trim<Remainder> extends ''
      ? ToArray<ParseInnerExpression<Trim<Expr>>>
      : ConcatInner<
          ToArray<ParseInnerExpression<Trim<Expr>>>,
          ParseExpressions<Remainder>
        >
    : Err<ParseError<`Cannot parse object from "${S}"`>>

export declare type ParseInnerExpression<S extends string> = S extends ''
  ? Err<ParseError<'Saw an empty expression'>>
  : Try<ParseNumber<S>, ParseObject<S>>

export declare type ParseKVPair<S extends string> =
  Split<S, '=='> extends [`${infer LHS}`, `${infer RHS}`]
    ? ParseValue<Trim<RHS>> extends infer Res
      ? Res extends [null, infer Value]
        ? Ok<{
            [P in Trim<LHS>]: Value
          }>
        : Err<
            ParseError<`Can't parse right hand side as a value in "${S}" (Invalid value ${RHS})`>
          >
      : never
    : Err<ParseError<`Can't parse key value pair from ${S}`>>

export declare type ParseNumber<S extends string> =
  S extends `${infer Head}${infer Tail}`
    ? Head extends '-'
      ? OnlyDigits<Tail> extends true
        ? Ok<ToNumber<S>>
        : Err<ParseError<`Invalid integer value "${S}"`>>
      : OnlyDigits<S> extends true
        ? Ok<ToNumber<S>>
        : Err<ParseError<`Invalid integer value "${S}"`>>
    : Err<ParseError<`Invalid integer value "${S}"`>>

export declare type ParseObject<S extends string> =
  S extends `${infer Pair},${infer Remainder}`
    ? Trim<Remainder> extends ''
      ? Ok<Record<never, never>>
      : MergeInner<ParseKVPair<Pair>, ParseObject<Remainder>>
    : ParseKVPair<S>

export declare type ParseProperty<S extends string> =
  Trim<S> extends ''
    ? Err<ParseError<'Empty property'>>
    : Split<Trim<S>, '[', true> extends [`${infer Prop}`, `${infer Expression}`]
      ? Trim<Prop> extends ''
        ? ParseExpressions<Trim<Expression>>
        : ConcatInner<Ok<[Trim<Prop>]>, ParseExpressions<Trim<Expression>>>
      : Ok<[Trim<S>]>

export declare type ParseValue<S extends string> = string extends S
  ? Err<ParseError<'ParseValue got generic string type'>>
  : S extends 'null'
    ? Ok<null>
    : S extends 'true'
      ? Ok<true>
      : S extends 'false'
        ? Ok<false>
        : S extends `"${infer Value}"`
          ? Ok<Value>
          : Try<
              ParseNumber<S>,
              Err<
                ParseError<`ParseValue failed. Can't parse "${S}" as a value.`>
              >
            >

export declare type PatchMutation<
  Patches extends NodePatchList = NodePatchList,
> = {
  type: 'patch'
  id: string
  patches: Patches
  options?: PatchOptions
}

export declare type PatchOptions = {
  ifRevision?: string
}

export declare type Path = PathElement[] | readonly PathElement[]

export declare type PathElement = PropertyName | Index | KeyedPathElement

export declare type PickOrUndef<T, Head> = Head extends keyof T
  ? T[Head]
  : undefined

export declare type PrimitiveOp = AnyOp | StringOp | NumberOp

export declare type PropertyName = string

export declare type RelativePosition = 'before' | 'after'

export declare type ReplaceOp<
  Items extends AnyArray,
  ReferenceItem extends Index | KeyedPathElement,
> = {
  type: 'replace'
  referenceItem: ReferenceItem
  items: Items
}

export declare type RequiredSelect<T, K extends keyof T> = Omit<T, K> & {
  [P in K]-?: T[P]
}

export declare type Result<E, V> = [E, V]

export declare type SafePath<S extends string> = StripError<StringToPath<S>>

export declare type SanityDocumentBase = {
  _id?: string
  _type: string
  _createdAt?: string
  _updatedAt?: string
  _rev?: string
}

export declare type SetIfMissingOp<T> = {
  type: 'setIfMissing'
  value: T
}

export declare type SetOp<T> = {
  type: 'set'
  value: T
}

export declare type Split<
  S extends string,
  Char extends string,
  IncludeSeparator extends boolean = false,
> = S extends `${infer First}${Char}${infer Remainder}`
  ? [First, `${IncludeSeparator extends true ? Char : ''}${Remainder}`]
  : [S]

export declare type SplitAll<
  S extends string,
  Char extends string,
> = S extends `${infer First}${Char}${infer Remainder}`
  ? [First, ...SplitAll<Remainder, Char>]
  : [S]

export declare type SplitAtPos<
  Current extends unknown[],
  NormalizedIndex extends number,
  Pos extends 'before' | 'after',
> = Call<Tuples.SplitAt<AdjustIndex<Pos, NormalizedIndex>, Current>>

export declare function startsWith(parentPath: Path, path: Path): boolean

export declare type StoredDocument = ToStored<SanityDocumentBase>

export declare function stringify(pathArray: Path): string

export declare type StringOp = DiffMatchPatchOp

export declare type StringToPath<S extends string> = Unwrap<
  ParseAllProps<SplitAll<Trim<S>, '.'>>
>

export declare type StripError<
  S extends StringToPath<string> | ParseError<string>,
> = S extends ParseError<string> ? never : S

export declare type ToArray<R extends Result<any, any>> = R extends [
  infer E,
  infer V,
]
  ? E extends null
    ? V extends any[]
      ? R
      : Ok<[R[1]]>
    : R
  : R

export declare type ToIdentified<Doc extends SanityDocumentBase> =
  RequiredSelect<Doc, '_id'>

export declare type ToNumber<T extends string> =
  T extends `${infer N extends number}` ? N : never

export declare type ToStored<Doc extends SanityDocumentBase> = Doc &
  Required<SanityDocumentBase>

export declare type Trim<
  S extends string,
  Char extends string = ' ',
> = TrimRight<TrimLeft<S, Char>, Char>

export declare type TrimLeft<
  Str extends string,
  Char extends string = ' ',
> = string extends Str
  ? Str
  : Str extends `${Char}${infer Trimmed}`
    ? TrimLeft<Trimmed, Char>
    : Str

export declare type TrimRight<
  Str extends string,
  Char extends string = ' ',
> = string extends Str
  ? Str
  : Str extends `${infer Trimmed}${Char}`
    ? TrimRight<Trimmed, Char>
    : Str

export declare type TruncateOp = {
  type: 'truncate'
  startIndex: number
  endIndex?: number
}

export declare type Try<R extends Result<any, any>, Handled> = R[1] extends null
  ? Handled
  : R

export declare type UnassignOp<
  K extends readonly string[] = readonly string[],
> = {
  type: 'unassign'
  keys: K
}

export declare type UnsetOp = {
  type: 'unset'
}

export declare type Unwrap<R extends Result<any, any>> = R extends [
  infer E,
  infer V,
]
  ? E extends null
    ? V
    : E
  : never

export declare type UpsertOp<
  Items extends AnyArray,
  Pos extends RelativePosition,
  ReferenceItem extends Index | KeyedPathElement,
> = {
  type: 'upsert'
  items: Items
  referenceItem: ReferenceItem
  position: Pos
}

export {}
