import { computed, get } from "@ember/object";
import CoreObject from "@ember/object/core";
import extractValue from "./extract-value";

export default function handleDescriptor(target, key, desc, params = []) {
  const val = extractValue(desc);

  if (typeof val === "function" && target instanceof CoreObject) {
    // We're in a native class, so convert the method to a getter first
    desc.writable = false;
    desc.initializer = undefined;
    desc.value = undefined;
    desc.get = callUserSuppliedGet(params, val);

    return computed(...params)(target, key, desc);
  } else {
    return {
      enumerable: desc.enumerable,
      configurable: desc.configurable,
      writable: desc.writable,
      initializer() {
        let computedDescriptor;

        if (desc.writable) {
          if (typeof val === "object") {
            let value = {};
            if (val.get) {
              value.get = callUserSuppliedGet(params, val.get);
            }
            if (val.set) {
              value.set = callUserSuppliedSet(params, val.set);
            }
            computedDescriptor = value;
          } else {
            computedDescriptor = callUserSuppliedGet(params, val);
          }
        } else {
          throw new Error(
            "ember-computed-decorators does not support using getters and setters"
          );
        }

        return computed.apply(null, params.concat(computedDescriptor));
      },
    };
  }
}

function niceAttr(attr) {
  const parts = attr.split(".");
  let i;

  for (i = 0; i < parts.length; i++) {
    if (parts[i] === "@each" || parts[i] === "[]" || parts[i].includes("{")) {
      break;
    }
  }

  return parts.slice(0, i).join(".");
}

function callUserSuppliedGet(params, func) {
  params = params.map(niceAttr);
  return function () {
    let paramValues = params.map((p) => get(this, p));

    return func.apply(this, paramValues);
  };
}

function callUserSuppliedSet(params, func) {
  params = params.map(niceAttr);
  return function (key, value) {
    let paramValues = params.map((p) => get(this, p));
    paramValues.unshift(value);

    return func.apply(this, paramValues);
  };
}