type Scalar<T> = T extends List<infer U> ? U : T

export class List<T> {
  public values: T[]

  constructor(values: T[]) {
    this.values = values
  }

  public static create<T = number>(...values: List<T>[]): List<T>
  public static create<T = number>(...values: T[]): List<T>
  public static create<T = number>(...values: T[]): List<T> {
    // Do *not* construct any array literal ([]) in your solution.
    // Do *not* construct any arrays through new Array in your solution.
    // DO *not* use any of the Array.prototype methods in your solution.

    // You may use the destructuring and spreading (...) syntax from Iterable.
    let _values: T[] = []
    for (let value of values) {
      if (value instanceof List) {
        _values = [..._values, ...value.values]
      } else {
        _values = [..._values, value]
      }
    }
    return new List(_values)
  }

  append(list: List<T>): List<T> {
    return new List([...this.values, ...list.values])
  }

  forEach(cb: (value: T) => void) {
    for (let value of this.values) cb(value)
  }

  concat(...lists: List<T>[]): List<T> {
    let values: T[] = [...this.values]
    for (let list of lists) {
      values = [...values, ...list.values]
    }
    return new List(values)
  }

  filter<T1 extends T>(fn: (el: T1) => boolean): List<T>
  filter(fn: (el: T) => boolean): List<T>
  filter(fn: (el: T) => boolean): List<T> {
    let values: T[] = []
    for (let value of this.values) {
      if (!fn(value)) continue
      values = [...values, value]
    }
    return new List(values)
  }

  map<T1 extends T>(fn: (el: T1) => T1): List<T>
  map(fn: (el: T) => T): List<T>
  map<T1 extends T>(fn: (el: T) => T1): List<T> {
    let values: T[] = []
    for (let value of this.values) {
      values = [...values, fn(value)]
    }
    return new List(values)
  }

  foldl<T1 extends T, T2>(fn: (acc: T2, value: T1) => T2, seed: T2): List<T>
  foldl<T2>(fn: (acc: T2, el: T) => T2, seed: T2): T2
  foldl<T2>(fn: (acc: T2, el: T) => T2, seed: T2): T2 {
    let result = seed
    for (let value of this.values) {
      result = fn(result, value)
    }
    return result
  }

  foldr<T1 extends T, T2>(fn: (acc: T2, value: T1) => T2, seed: T2): List<T>
  foldr<T2>(fn: (acc: T2, el: T) => T2, seed: T2): T2
  foldr<T2>(fn: (acc: T2, el: T) => T2, seed: T2): T2 {
    let result = seed
    for (let i = this.length() - 1; i >= 0; i -= 1) {
      result = fn(result, this.values[i])
    }
    return result
  }

  reverse(): List<T> {
    let values: T[] = []
    for (let i = this.length() - 1; i >= 0; i -= 1) {
      values = [...values, this.values[i]]
    }
    return new List(values)
  }

  length(): number {
    let count = 0
    for (let _ of this.values) count++
    return count
  }
}
