import { Decimal } from "./decimal.js";
import { evaluationError } from "./errors.js";
import { isArray, hasOwn, objKeys } from "./globals.js";
import { isBefore, isAfter, isEqual as isDateEqual } from "date-fns";
export function registerOverloads(registry) {
  const unaryOverload = (op, t, h, ret) => registry.unaryOverload(op, t, h, ret, false);
  const binaryOverload = (l, op, r, h, ret) => registry.binaryOverload(l, op, r, h, ret, false);
  function throwDivisionByZero(ast) {
    throw evaluationError("division_by_zero", "division by zero", ast);
  }
  function throwModuloByZero(ast) {
    throw evaluationError("modulo_by_zero", "modulo by zero", ast);
  }
  unaryOverload("!", "bool", (a) => !a);
  unaryOverload("-", "number", (a) => Decimal.from(a).neg());
  binaryOverload("number", "*", "number", (a, b) => Decimal.from(a).mul(b));
  binaryOverload("number", "+", "number", (a, b) => Decimal.from(a).add(b));
  binaryOverload("number", "-", "number", (a, b) => Decimal.from(a).sub(b));
  binaryOverload("number", "/", "number", (a, b, ast) => {
    const bd = Decimal.from(b);
    if (bd.isZero()) return throwDivisionByZero(ast);
    return Decimal.from(a).div(bd);
  });
  binaryOverload("number", "%", "number", (a, b, ast) => {
    const bd = Decimal.from(b);
    if (bd.isZero()) return throwModuloByZero(ast);
    return Decimal.from(a).mod(bd);
  });
  binaryOverload("string", "+", "string", (a, b) => a + b);
  binaryOverload("list<V>", "+", "list<V>", (a, b) => [...a, ...b]);
  binaryOverload("bytes", "+", "bytes", (a, b) => {
    const ab = a;
    const bb = b;
    if (!ab.length) return bb;
    if (!bb.length) return ab;
    const result = new Uint8Array(ab.length + bb.length);
    result.set(ab, 0);
    result.set(bb, ab.length);
    return result;
  });
  binaryOverload("Date", "==", "Date", (a, b) => isDateEqual(a, b));
  binaryOverload("Date", "<", "Date", (a, b) => isBefore(a, b));
  binaryOverload("Date", "<=", "Date", (a, b) => !isAfter(a, b));
  binaryOverload("Date", ">", "Date", (a, b) => isAfter(a, b));
  binaryOverload("Date", ">=", "Date", (a, b) => !isBefore(a, b));
  function listIncludes(value, list, ast, ev) {
    const listVal = list;
    if (listVal instanceof Set && listVal.has(value)) return true;
    for (const v of listVal) if (isEqual(value, v, ast, ev)) return true;
    return false;
  }
  function mapIncludes(a, b) {
    const bMap = b;
    if (bMap instanceof Map) return bMap.get(a) !== void 0;
    return hasOwn(bMap, a) ? bMap[a] !== void 0 : false;
  }
  function listMembership(value, list, ast, ev) {
    return listIncludes(value, list, ast, ev);
  }
  binaryOverload("V", "in", "list<V>", listMembership);
  binaryOverload("K", "in", "map<K, V>", mapIncludes);
  for (const t of ["type", "null", "bool", "string"]) {
    binaryOverload(t, "==", t, (a, b) => a === b);
  }
  binaryOverload("number", "==", "number", (a, b) => Decimal.from(a).eq(b));
  binaryOverload("bytes", `==`, "bytes", (a, b) => {
    if (a === b) return true;
    const ab = a;
    const bb = b;
    let i = ab.length;
    if (i !== bb.length) return false;
    while (i--) if (ab[i] !== bb[i]) return false;
    return true;
  });
  binaryOverload("list<V>", `==`, "list<V>", (a, b, ast, ev) => {
    if (a === b) return true;
    if (isArray(a) && isArray(b)) {
      const arr1 = a;
      const arr2 = b;
      const length = arr1.length;
      if (length !== arr2.length) return false;
      for (let i = 0; i < length; i++) {
        if (!isEqual(arr1[i], arr2[i], ast, ev)) return false;
      }
      return true;
    }
    if (a instanceof Set && b instanceof Set) {
      if (a.size !== b.size) return false;
      for (const value of a) if (!b.has(value)) return false;
      return true;
    }
    const arr = a instanceof Set ? b : a;
    const set = a instanceof Set ? a : b;
    if (!isArray(arr)) return false;
    const arrTyped = arr;
    const setTyped = set;
    if (arrTyped.length !== setTyped?.size) return false;
    for (let i = 0; i < arrTyped.length; i++) if (!setTyped.has(arrTyped[i])) return false;
    return true;
  });
  binaryOverload("map<K, V>", `==`, "map<K, V>", (a, b, ast, ev) => {
    if (a === b) return true;
    if (a instanceof Map && b instanceof Map) {
      if (a.size !== b.size) return false;
      for (const [key, value] of a)
        if (!(b.has(key) && isEqual(value, b.get(key), ast, ev))) return false;
      return true;
    }
    if (a instanceof Map || b instanceof Map) {
      const obj = a instanceof Map ? b : a;
      const map = a instanceof Map ? a : b;
      const keysObj = objKeys(obj);
      if (map.size !== keysObj.length) return false;
      for (const [key, value] of map) {
        if (!(key in obj && isEqual(value, obj[key], ast, ev))) return false;
      }
      return true;
    }
    const ao = a;
    const bo = b;
    const keysA = objKeys(ao);
    const keysB = objKeys(bo);
    if (keysA.length !== keysB.length) return false;
    for (let i = 0; i < keysA.length; i++) {
      const key = keysA[i];
      if (!(key in bo && isEqual(ao[key], bo[key], ast, ev))) return false;
    }
    return true;
  });
  for (const [left, right] of [
    ["bool", "bool"],
    ["number", "number"],
    ["string", "string"]
  ]) {
    const isNum = left === "number";
    binaryOverload(left, "<", right, isNum ? (a, b) => Decimal.from(a).lt(b) : (a, b) => a < b);
    binaryOverload(left, "<=", right, isNum ? (a, b) => Decimal.from(a).lte(b) : (a, b) => a <= b);
    binaryOverload(left, ">", right, isNum ? (a, b) => Decimal.from(a).gt(b) : (a, b) => a > b);
    binaryOverload(left, ">=", right, isNum ? (a, b) => Decimal.from(a).gte(b) : (a, b) => a >= b);
  }
}
function isEqual(a, b, ast, ev) {
  if (a === b) return true;
  switch (typeof a) {
    case "undefined":
    case "string":
    case "boolean":
      return false;
    case "number":
    case "bigint":
      if (typeof b === "number" || typeof b === "bigint" || b instanceof Decimal)
        return Decimal.from(a).eq(b);
      return false;
    case "object": {
      if (a instanceof Decimal) {
        if (b instanceof Decimal || typeof b === "number" || typeof b === "bigint")
          return a.eq(b);
        return false;
      }
      if (typeof b !== "object") return false;
      const leftType = ev.debugType(a);
      const rightType = ev.debugType(b);
      if (leftType !== rightType) return false;
      const overload = ev.registry.findBinaryOverload("==", leftType, rightType);
      if (!overload) return false;
      return overload.handler(a, b, ast, ev);
    }
  }
  throw evaluationError("invalid_comparison_type", `Cannot compare values of type ${typeof a}`, ast);
}
