import { append, foldl, Functor, map, Semigroup, type } from 'funcadelic';

class Box {
  static get of() {
    return (...args) => new this(...args)
  }

  static unwrap(box) {
    return box.value;
  }

  constructor(value) {
    this.value = value;
  }
}

const { unwrap } = Box;

class Id extends Box {};

class Const extends Box {}

Functor.instance(Id, {
  map(fn, id) {
    var next = fn(id.value);
    return next === id.value ? id : Id.of(next);
  }
});

Functor.instance(Const, {
  map(fn, constant) {
    return constant;
  }
})

export function compose(f, g) {
  return (...x) => f(g(...x));
}

export function view(lens, context) {
  let get = compose(unwrap, lens(Const.of));
  return get(context);
}

export function over(lens, fn, context) {
  let update = compose(unwrap, lens(compose(Id.of, fn)));
  return update(context)
}

export function set(lens, value, context) {
  return over(lens, () => value, context);
}

export function Lens(get, set) {
  return f => context => {
    return map(value => set(value, context), f(get(context)));
  }
}

export const transparent = Lens(x => x, y => y);

export function ValueAt(property) {
  let get = context => context != null ? context[property] : undefined;
  let set = (value, context = {}) => {
    if (value === context[property]) {
      return context;
    } else if (Array.isArray(context)) {
      let clone = context.slice();
      clone[Number(property)] = value;
      return clone;
    } else {
      return Semigroup.for(Object).append(context, {[property]: value});
    }
  };

  return Lens(get, set);
}

export function Path(path = []) {
  return foldl((lens, key) => {
    return compose(lens, ValueAt(key))
  }, transparent, path);
}


export const Container = type(class Container {
  At(key, container = {}) {
    return this(container).At(key, container);
  }
})

export const { At } = Container.prototype;

function BuiltinAt(key, defaultValue, embed) {
  function getter(object) {
    if (object != null) {
      return object[key];
    } else {
      return undefined;
    }
  }

  function setter(child, whole = defaultValue) {
    let current = whole[key];
    if (current === child) {
      return whole;
    } else {
      return embed(whole, key, child);
    }
  }
  return Lens(getter, setter);
}

Container.instance(Object, {
  At(key) {
    return BuiltinAt(key, {}, (whole, key, child) => {
      return append(whole, {
        [key]: child
      })
    })
  }
})

Container.instance(Array, {
  At(key) {
    return BuiltinAt(key, [], (whole, key, child) => {
      let next = whole.slice();
      next[key] = child;
      return next;
    })
  }
})

@@ -13,15 +13,8 @@
 end,
- foldl,
  Fun
@@ -37,14 +37,8 @@
 roup
-, type
  %7D f
@@ -1110,21 +1110,16 @@
 unction 
-Value
 At(prope
@@ -1121,16 +1121,32 @@
 property
+, container = %7B%7D
 ) %7B%0A  le
@@ -1230,33 +1230,105 @@
  = (
-value, context = %7B%7D) =%3E %7B
+part, whole) =%3E %7B%0A    let context = whole == null ? (Array.isArray(container) ? %5B%5D : %7B%7D) : whole;
 %0A   
@@ -1332,21 +1332,20 @@
     if (
-value
+part
  === con
@@ -1492,21 +1492,20 @@
 rty)%5D = 
-value
+part
 ;%0A      
@@ -1595,21 +1595,20 @@
 perty%5D: 
-value
+part
 %7D);%0A    
@@ -1690,1024 +1690,71 @@
 urn 
-foldl((lens, key) =%3E %7B%0A    return compose(lens, ValueAt(key))%0A  %7D, transparent, path);%0A%7D%0A%0A%0Aexport const Container = type(class Container %7B%0A  At(key, container = %7B%7D) %7B%0A    return this(container).At(key, container);%0A  %7D%0A%7D)%0A%0Aexport const %7B At %7D = Container.prototype;%0A%0Afunction BuiltinAt(key, defaultValue, embed) %7B%0A  function getter(object) %7B%0A    if (object != null) %7B%0A      return object%5Bkey%5D;%0A    %7D else %7B%0A      return undefined;%0A    %7D%0A  %7D%0A%0A  function setter(child, whole = defaultValue) %7B%0A    let current = whole%5Bkey%5D;%0A    if (current === child) %7B%0A      return whole;%0A    %7D else %7B%0A      return embed(whole, key, child);%0A    %7D%0A  %7D%0A  return Lens(getter, setter);%0A%7D%0A%0AContainer.instance(Object, %7B%0A  At(key) %7B%0A    return BuiltinAt(key, %7B%7D, (whole, key, child) =%3E %7B%0A      return append(whole, %7B%0A        %5Bkey%5D: child%0A      %7D)%0A    %7D)%0A  %7D%0A%7D)%0A%0AContainer.instance(Array, %7B%0A  At(key) %7B%0A    return BuiltinAt(key, %5B%5D, (whole, key, child) =%3E %7B%0A      let next = whole.slice();%0A      next%5Bkey%5D = child;%0A      return next;%0A    %7D)%0A  %7D%0A%7D)
+path.reduce((lens, key) =%3E compose(lens, At(key)), transparent);%0A%7D
 %0A

Remove fully polymorphic lenses.