import { Effect, Either, Option } from "effect";
import { celTypes } from "./registry.js";
import { Decimal } from "./decimal.js";
import { objKeys, isArray, hasOwn } from "./globals.js";
import { attachErrorAst } from "./errors.js";
const dynType = celTypes.dyn;
export class Base {
  dynType = celTypes.dyn;
  optionalType = celTypes.optional;
  stringType = celTypes.string;
  numberType = celTypes.number;
  boolType = celTypes.bool;
  nullType = celTypes.null;
  listType = celTypes.list;
  mapType = celTypes.map;
  opts;
  registry;
  objectTypes;
  objectTypesByConstructor;
  constructor(opts) {
    this.opts = opts.opts;
    this.registry = opts.registry;
    this.objectTypes = this.registry.objectTypes;
    this.objectTypesByConstructor = this.registry.objectTypesByConstructor;
  }
  getType(typeName) {
    return this.registry.getType(typeName);
  }
  debugType(v) {
    switch (typeof v) {
      case "string":
        return this.stringType;
      case "number":
      case "bigint":
        return this.numberType;
      case "boolean":
        return this.boolType;
      case "object":
        if (v === null) return this.nullType;
        if (v instanceof Decimal) return this.numberType;
        if (Option.isOption(v)) return this.optionalType;
        switch (v.constructor) {
          case void 0:
          case Object:
          case Map:
            return this.mapType;
          case Array:
          case Set:
            return this.listType;
          default:
            return this.objectTypesByConstructor.get(v.constructor)?.type || unsupportedType(this, v.constructor?.name || typeof v);
        }
      default:
        unsupportedType(this, typeof v);
    }
  }
}
function unsupportedType(self, type) {
  throw self.createError("unsupported_type", `Unsupported type: ${type}`);
}
function checkAccessNode(chk, ast, ctx) {
  ast.right = ast.args[1];
  const leftType = chk.check(ast.left = ast.args[0], ctx);
  if (ast.op === "[]") chk.check(ast.right, ctx);
  ast.handle = leftType !== dynType ? fieldAccessStatic : fieldAccess;
  if (leftType.kind !== "optional") return chk.checkAccessOnType(ast, ctx, leftType);
  return chk.registry.getOptionalType(chk.checkAccessOnType(ast, ctx, leftType.valueType, true));
}
function checkOptionalAccessNode(chk, ast, ctx) {
  ast.right = ast.args[1];
  const leftType = chk.check(ast.left = ast.args[0], ctx);
  if (ast.op === "[?]") chk.check(ast.right, ctx);
  ast.handle = oFieldAccess;
  const actualType = leftType.kind === "optional" ? leftType.valueType : leftType;
  return chk.registry.getOptionalType(chk.checkAccessOnType(ast, ctx, actualType, true));
}
function spreadInner(node) {
  return node.args;
}
function spreadListElementType(chk, ctx, node) {
  const t = chk.check(spreadInner(node), ctx);
  if (t.kind === "dyn") return chk.dynType;
  if (t.kind === "list") return t.valueType;
  throw chk.createError(
    "invalid_spread",
    `Cannot spread '${chk.formatType(t)}' into a list (expected a list)`,
    node
  );
}
function spreadMapEntryTypes(chk, ctx, node) {
  const t = chk.check(spreadInner(node), ctx);
  if (t.kind === "dyn") return [chk.dynType, chk.dynType];
  if (t.kind === "map") return [t.keyType, t.valueType];
  if (t.kind === "message") {
    const Base2 = chk;
    return [Base2.stringType, chk.dynType];
  }
  throw chk.createError(
    "invalid_spread",
    `Cannot spread '${chk.formatType(t)}' into a map (expected a map)`,
    node
  );
}
const SPREAD_SKIP_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
function assignFragmentEntry(obj, k, v) {
  const key = k;
  if (SPREAD_SKIP_KEYS.has(key)) return;
  obj[key] = v;
}
function mergeMapFragments(ev, frags) {
  const obj = {};
  for (const f of frags) {
    if ("kv" in f) {
      assignFragmentEntry(obj, f.kv[0], f.kv[1]);
      continue;
    }
    const src = f.spread;
    if (src instanceof Map) {
      for (const [k, v] of src) assignFragmentEntry(obj, k, v);
      continue;
    }
    if (src !== null && typeof src === "object" && !isArray(src)) {
      for (const k in src) {
        if (hasOwn(src, k)) assignFragmentEntry(obj, k, src[k]);
      }
      continue;
    }
    throw ev.createError(
      "invalid_spread",
      `Cannot spread a non-map value into a map`,
      f.node
    );
  }
  return obj;
}
function evaluateSpreadList(ev, ast, ctx) {
  const arr = ast.args;
  return Effect.all(
    arr.map(
      (el) => el.op === "spread" ? ev.eval(spreadInner(el), ctx).pipe(
        Effect.flatMap(
          (v) => isArray(v) ? Effect.succeed(v) : Effect.fail(
            ev.createError(
              "invalid_spread",
              `Cannot spread a non-list value into a list`,
              el
            )
          )
        )
      ) : ev.eval(el, ctx).pipe(Effect.map((v) => [v]))
    )
  ).pipe(Effect.map((parts) => parts.flat()));
}
function evaluateSpreadMap(ev, ast, ctx) {
  const arr = ast.args;
  return Effect.all(
    arr.map((e) => {
      if (e.length === 1 || e[0].op === "spread") {
        const node = e[0];
        return ev.eval(spreadInner(node), ctx).pipe(
          Effect.map((src) => ({ spread: src, node }))
        );
      }
      const pair = e;
      return Effect.all([ev.eval(pair[0], ctx), ev.eval(pair[1], ctx)]).pipe(
        Effect.map(([k, v]) => ({ kv: [k, v] }))
      );
    })
  ).pipe(
    Effect.flatMap(
      (frags) => Effect.try({
        try: () => mergeMapFragments(ev, frags),
        catch: (e) => e
      })
    )
  );
}
function ternaryConditionError(ev, value, node) {
  const type = ev.debugRuntimeType(value);
  return ev.createError(
    "invalid_condition_type",
    `${node.meta.label || "Ternary condition must be bool"}, got '${type}'`,
    node
  );
}
function handleTernary(c, ev, ast, ctx) {
  if (c === true) return ev.eval(ast.left, ctx);
  if (c === false) return ev.eval(ast.right, ctx);
  return Effect.fail(ternaryConditionError(ev, c, ast.condition));
}
function logicalOperandError(ev, value, node) {
  const type = ev.debugRuntimeType(value);
  return ev.createError(
    "invalid_logical_operand",
    `Logical operator requires bool operands, got '${type}'`,
    node
  );
}
function checkLogicalOp(chk, ast, ctx) {
  const leftType = chk.check(ast.left = ast.args[0], ctx);
  const rightType = chk.check(ast.right = ast.args[1], ctx);
  if (!leftType.isDynOrBool()) {
    throw chk.createError(
      "invalid_logical_operand",
      `Logical operator requires bool operands, got '${chk.formatType(leftType)}'`,
      ast
    );
  }
  if (!rightType.isDynOrBool()) {
    throw chk.createError(
      "invalid_logical_operand",
      `Logical operator requires bool operands, got '${chk.formatType(rightType)}'`,
      ast
    );
  }
  return chk.boolType;
}
function checkUnary(chk, ast, ctx) {
  const op = ast.op;
  const right = chk.check(ast.args, ctx);
  ast.candidates = chk.registry.operatorCandidates(op);
  if (right.kind === "dyn") {
    ast.handle = handleUnary;
    return ast.candidates.returnType;
  }
  const overload = ast.candidates.findUnaryOverload(right);
  if (!overload) {
    throw chk.createError(
      "no_such_overload",
      `no such overload: ${op[0]}${chk.formatType(right)}`,
      ast
    );
  }
  ast.handle = overload.handler;
  return overload.returnType;
}
function handleUnary(left, ast, ev) {
  const a = ast;
  const e = ev;
  const leftType = e.debugRuntimeType(left, a.args.checkedType);
  const overload = a.candidates.findUnaryOverload(leftType);
  if (overload) return overload.handler(left);
  throw e.createError("no_such_overload", `no such overload: ${a.op[0]}${leftType}`, a);
}
function evaluateUnary(ev, ast, ctx) {
  return ev.eval(ast.args, ctx).pipe(
    Effect.flatMap(
      (val) => Effect.try({
        try: () => ast.handle(val, ast, ev),
        catch: (e) => e
      })
    )
  );
}
function checkBinary(chk, ast, ctx) {
  const op = ast.op;
  const left = chk.check(ast.left = ast.args[0], ctx);
  const right = chk.check(ast.right = ast.args[1], ctx);
  ast.candidates = chk.registry.operatorCandidates(op);
  const overload = left.hasDynType || right.hasDynType ? void 0 : ast.candidates.findBinaryOverload(left, right);
  ast.handle = overload ? overload.handler : handleBinary;
  if (overload) return overload.returnType;
  const type = ast.candidates.checkBinaryOverload(left, right);
  if (!left.hasDynType) ast.leftStaticType = left;
  if (!right.hasDynType) ast.rightStaticType = right;
  if (type) return type;
  throw chk.createError(
    "no_such_overload",
    `no such overload: ${chk.formatType(left)} ${op} ${chk.formatType(right)}`,
    ast
  );
}
function evaluateBinary(ev, ast, ctx) {
  return Effect.all([ev.eval(ast.left, ctx), ev.eval(ast.right, ctx)]).pipe(
    Effect.flatMap(
      ([l, r]) => Effect.try({
        try: () => ast.handle(l, r, ast, ev),
        catch: (e) => e
      })
    )
  );
}
function evaluateBinaryFirst(ev, ast, ctx) {
  return ev.eval(ast.left, ctx).pipe(
    Effect.flatMap(
      (l) => Effect.try({
        try: () => ast.handle(l, ast.right, ast, ev),
        catch: (e) => e
      })
    )
  );
}
function handleBinary(left, right, ast, ev) {
  const a = ast;
  const e = ev;
  const leftType = a.leftStaticType || e.debugTypeDeep(left).wrappedType;
  const rightType = a.rightStaticType || e.debugTypeDeep(right).wrappedType;
  const overload = a.candidates.findBinaryOverload(leftType, rightType);
  if (overload) return overload.handler(left, right, ast, ev);
  throw e.createError(
    "no_such_overload",
    `no such overload: ${leftType} ${a.op} ${rightType}`,
    a
  );
}
function callFunctionHandler(handler, ev, args, ast) {
  return Effect.try({
    try: () => handler.apply(ev, args),
    catch: (error) => attachErrorAst(error, ast)
  }).pipe(
    Effect.flatMap(
      (result) => Effect.isEffect(result) && !Option.isOption(result) ? result.pipe(Effect.mapError((error) => attachErrorAst(error, ast))) : Effect.succeed(result)
    )
  );
}
function callFn(args, ast, ev) {
  const a = ast;
  const e = ev;
  const argArr = args;
  const argAst = a.args[1];
  const types = a.argTypes;
  let i = argAst.length;
  while (i--) types[i] = e.debugRuntimeType(argArr[i], argAst[i].checkedType);
  const decl = a.candidates.findFunction(types);
  if (decl) return callFunctionHandler(decl.handler, e, argArr, a);
  return Effect.fail(
    e.createError(
      "no_matching_overload",
      `found no matching overload for '${a.args[0]}(${types.map((t) => t.unwrappedType).join(", ")})'`,
      a
    )
  );
}
function callRecFn(args, ev, ast) {
  const a = ast;
  const e = ev;
  const argArr = args;
  const [, receiverAst, argAst] = a.args;
  const types = a.argTypes;
  for (let i = 0; i < types.length; i++)
    types[i] = e.debugRuntimeType(argArr[i + 1], argAst[i].checkedType);
  const receiverType = e.debugRuntimeType(argArr[0], receiverAst.checkedType);
  const decl = a.candidates.findFunction(types, receiverType);
  if (decl) return callFunctionHandler(decl.handler, e, argArr, a);
  return Effect.fail(
    e.createError(
      "no_matching_overload",
      `found no matching overload for '${receiverType.type}.${a.args[0]}(${types.map((t) => t.unwrappedType).join(", ")})'`,
      a
    )
  );
}
function resolveAstArray(ev, astArray, ctx) {
  if (astArray.length === 0) return Effect.succeed([]);
  return Effect.all(astArray.map((a) => ev.eval(a, ctx)));
}
function safeFromEntries(entries) {
  const obj = {};
  for (let i = 0; i < entries.length; i++) {
    const [k, v] = entries[i];
    if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
    obj[k] = v;
  }
  return obj;
}
function comprehensionElementType(chk, iterable, ctx) {
  const iterType = chk.check(iterable, ctx);
  if (iterType.kind === "dyn") return iterType;
  if (iterType.kind === "list") return iterType.valueType;
  if (iterType.kind === "map") return iterType.keyType;
  throw chk.createError(
    "invalid_comprehension_range",
    `Expression of type '${chk.formatType(
      iterType
    )}' cannot be range of a comprehension (must be list, map, or dynamic).`,
    iterable
  );
}
function toIterable(ev, args, coll) {
  if (coll instanceof Set) return [...coll];
  if (coll instanceof Map) return [...coll.keys()];
  if (coll && typeof coll === "object") return objKeys(coll);
  throw ev.createError(
    "invalid_comprehension_range",
    `Expression of type '${ev.debugType(
      coll
    )}' cannot be range of a comprehension (must be list, map, or dynamic).`,
    args.iterable
  );
}
function runQualifier(items, args, ev, ctx) {
  return Effect.gen(function* () {
    if (!isArray(items)) items = toIterable(ev, args, items);
    let accu = yield* ev.eval(args.init, ctx = args.iterCtx.reuse(ctx));
    ctx.accuValue = accu;
    let error = null;
    const condition = args.condition;
    const step = args.step;
    const len = items.length;
    for (let i = 0; i < len; i++) {
      if (!condition(accu)) return args.result(accu);
      const stp = yield* Effect.either(ev.eval(step, ctx.setIterValue(items[i], ev)));
      if (Either.isLeft(stp)) {
        error ??= stp.left;
        continue;
      }
      accu = stp.right;
      ctx.accuValue = accu;
    }
    if (error && condition(accu)) return yield* Effect.fail(error);
    return args.result(accu);
  });
}
function runComprehension(items, args, ev, ctx) {
  return Effect.gen(function* () {
    if (!isArray(items)) items = toIterable(ev, args, items);
    let accu = yield* ev.eval(args.init, ctx = args.iterCtx.reuse(ctx));
    ctx.accuValue = accu;
    const condition = args.condition;
    const step = args.step;
    const len = items.length;
    for (let i = 0; i < len; i++) {
      if (condition && !condition(accu)) break;
      accu = yield* ev.eval(step, ctx.setIterValue(items[i], ev));
    }
    return args.result(accu);
  });
}
function oFieldAccess(left, right, ast, ev) {
  const e = ev;
  return e.optionalType.field(left, right, ast, e);
}
function fieldAccessStatic(left, right, ast, ev) {
  const a = ast;
  const e = ev;
  return a.left.checkedType.field(left, right, a, e);
}
const empty = /* @__PURE__ */ Object.create(null);
function fieldAccess(left, right, ast, ev) {
  const a = ast;
  const e = ev;
  switch (left?.constructor) {
    case void 0:
    case Object: {
      const v = hasOwn(left || empty, right) ? left[right] : void 0;
      if (v !== void 0) return e.debugType(v), v;
      break;
    }
    case Map: {
      const v = left.get(right);
      if (v !== void 0) return e.debugType(v), v;
      break;
    }
    case Array:
    case Set:
      return e.listType.field(left, right, a, e);
    default: {
      const t = e.objectTypesByConstructor.get(left.constructor);
      if (t) return t.type.field(left, right, a, e);
      else if (typeof left === "object") unsupportedType(e, left.constructor?.name ?? "object");
    }
  }
  throw e.createError("no_such_key", `No such key: ${right}`, a);
}
const emptyList = () => Effect.succeed([]);
const emptyMap = () => Effect.succeed({});
const OPERATORS_MAP = {
  "value": {
    check(chk, ast) {
      return chk.debugType(ast.args);
    },
    evaluate(_ev, ast) {
      return Effect.succeed(ast.args);
    }
  },
  "id": {
    check(chk, ast, ctx) {
      const variable = ctx.getVariable(ast.args);
      if (!variable) throw chk.createError("unknown_variable", `Unknown variable: ${ast.args}`, ast);
      if (variable.constant) {
        const alternate = ast.clone(OPERATORS_MAP.value, variable.value);
        ast.setMeta("alternate", alternate);
        return chk.check(alternate, ctx);
      }
      return variable.type;
    },
    evaluate(ev, ast, ctx) {
      return Effect.try({
        try: () => ctx.getCheckedValue(ev, ast),
        catch: (e) => e
      });
    }
  },
  ".": {
    alias: "fieldAccess",
    check: checkAccessNode,
    evaluate: evaluateBinaryFirst
  },
  ".?": {
    alias: "optionalFieldAccess",
    check: checkOptionalAccessNode,
    evaluate: evaluateBinaryFirst
  },
  "[]": {
    alias: "bracketAccess",
    check: checkAccessNode,
    evaluate: evaluateBinary
  },
  "[?]": {
    alias: "optionalBracketAccess",
    check: checkOptionalAccessNode,
    evaluate: evaluateBinary
  },
  "call": {
    check(chk, ast, ctx) {
      const [functionName, args] = ast.args;
      const candidates = ast.candidates = chk.registry.functionCandidates(
        false,
        functionName,
        args.length
      );
      const argTypes = ast.argTypes = args.map((a) => chk.check(a, ctx));
      const decl = candidates.findFunction(argTypes);
      if (!decl) {
        throw chk.createError(
          "no_matching_overload",
          `found no matching overload for '${functionName}(${chk.formatTypeList(argTypes)})'`,
          ast
        );
      }
      const handle = argTypes.some((t) => t.hasDynType) ? callFn : ((args2, _ast, e) => callFunctionHandler(decl.handler, e, args2, _ast));
      ast.handle = handle;
      return decl.returnType;
    },
    evaluate(ev, ast, ctx) {
      return resolveAstArray(ev, ast.args[1], ctx).pipe(
        Effect.flatMap((args) => ast.handle(args, ast, ev))
      );
    }
  },
  "rcall": {
    check(chk, ast, ctx) {
      const [methodName, receiver, args] = ast.args;
      const receiverType = chk.check(receiver, ctx);
      const candidates = ast.candidates = chk.registry.functionCandidates(
        true,
        methodName,
        args.length
      );
      const argTypes = ast.argTypes = args.map((a) => chk.check(a, ctx));
      ast.receiverWithArgs = [receiver, ...args];
      ast.handle = callRecFn;
      if (receiverType.kind === "dyn" && candidates.returnType) return candidates.returnType;
      const decl = candidates.findFunction(argTypes, receiverType);
      if (!decl) {
        throw chk.createError(
          "no_matching_overload",
          `found no matching overload for '${receiverType.type}.${methodName}(${chk.formatTypeList(
            argTypes
          )})'`,
          ast
        );
      }
      if (!receiverType.hasPlaceholderType && !argTypes.some((t) => t.hasDynType)) {
        const fn = decl.handler;
        const handle = (a, ev, _ast) => callFunctionHandler(fn, ev, a, _ast);
        ast.handle = handle;
      }
      return decl.returnType;
    },
    evaluate(ev, ast, ctx) {
      return resolveAstArray(ev, ast.receiverWithArgs, ctx).pipe(
        Effect.flatMap((args) => ast.handle(args, ev, ast))
      );
    }
  },
  "list": {
    check(chk, ast, ctx) {
      const arr = ast.args;
      const arrLen = arr.length;
      if (arrLen === 0) return ast.setMeta("evaluate", emptyList) && chk.getType("list<T>");
      let hasSpread = false;
      let valueType;
      for (const el of arr) {
        let t;
        if (el.op === "spread") {
          hasSpread = true;
          t = spreadListElementType(chk, ctx, el);
        } else {
          t = chk.check(el, ctx);
        }
        valueType = valueType === void 0 ? t : valueType.unify(chk.registry, t) ?? dynType;
      }
      if (hasSpread) ast.setMeta("evaluate", evaluateSpreadList);
      return chk.registry.getListType(valueType);
    },
    evaluate(ev, ast, ctx) {
      return resolveAstArray(ev, ast.args, ctx);
    }
  },
  "map": {
    check(chk, ast, ctx) {
      const arr = ast.args;
      const arrLen = arr.length;
      if (arrLen === 0) return ast.setMeta("evaluate", emptyMap) && chk.getType("map<K, V>");
      let hasSpread = false;
      let keyType;
      let valueType;
      for (const e of arr) {
        let k;
        let v;
        if (e.length === 1 || e[0].op === "spread") {
          hasSpread = true;
          [k, v] = spreadMapEntryTypes(chk, ctx, e[0]);
        } else {
          k = chk.check(e[0], ctx);
          v = chk.check(e[1], ctx);
        }
        keyType = keyType === void 0 ? k : keyType.unify(chk.registry, k) ?? dynType;
        valueType = valueType === void 0 ? v : valueType.unify(chk.registry, v) ?? dynType;
      }
      if (hasSpread) ast.setMeta("evaluate", evaluateSpreadMap);
      return chk.registry.getMapType(keyType, valueType);
    },
    evaluate(ev, ast, ctx) {
      const astEntries = ast.args;
      return Effect.all(
        astEntries.map(
          (e) => Effect.all([ev.eval(e[0], ctx), ev.eval(e[1], ctx)])
        )
      ).pipe(Effect.map(safeFromEntries));
    }
  },
  "spread": {
    // Reached only via direct `ast.check`/`ast.evaluate` — parents consume the
    // node inline, so these are defensive guards.
    check(chk, ast) {
      throw chk.createError("misplaced_spread", `'...' is only valid inside a list or map literal`, ast);
    },
    evaluate(ev, ast) {
      return Effect.fail(ev.createError("misplaced_spread", `'...' is only valid inside a list or map literal`, ast));
    }
  },
  "comprehension": {
    check(chk, ast, ctx) {
      const args = ast.args;
      args.iterCtx = ctx.forkWithVariable(args.iterVarName, comprehensionElementType(chk, args.iterable, ctx)).setAccuType(chk.check(args.init, ctx));
      const stepType = chk.check(args.step, args.iterCtx);
      const handler = args.errorsAreFatal ? runComprehension : runQualifier;
      ast.handle = handler;
      return args.kind === "quantifier" ? chk.boolType : stepType;
    },
    evaluate(ev, ast, ctx) {
      return ev.eval(ast.args.iterable, ctx).pipe(
        Effect.flatMap((items) => ast.handle(items, ast.args, ev, ctx))
      );
    }
  },
  "accuValue": {
    check(_chk, _ast, ctx) {
      return ctx.accuType;
    },
    evaluate(_ev, _ast, ctx) {
      return Effect.succeed(ctx.accuValue);
    }
  },
  "accuInc": {
    check(_chk, _ast, ctx) {
      return ctx.accuType;
    },
    evaluate(_ev, _ast, ctx) {
      return Effect.succeed(ctx.accuValue = ctx.accuValue + 1);
    }
  },
  "accuPush": {
    check(chk, ast, ctx) {
      const listType = ctx.accuType;
      const itemType = chk.check(ast.args, ctx);
      if (listType.kind === "list" && listType.valueType.kind !== "param") return listType;
      return chk.registry.getListType(itemType);
    },
    evaluate(ev, ast, ctx) {
      return ev.eval(ast.args, ctx).pipe(
        Effect.map((el) => {
          const arr = ctx.accuValue;
          arr.push(el);
          return arr;
        })
      );
    }
  },
  "?:": {
    alias: "ternary",
    check(chk, ast, ctx) {
      const condast = ast.condition = ast.args[0];
      const leftast = ast.left = ast.args[1];
      const rightast = ast.right = ast.args[2];
      const condType = chk.check(condast, ctx);
      if (!condType.isDynOrBool()) {
        throw chk.createError(
          "invalid_condition_type",
          `${condast.meta.label || "Ternary condition must be bool"}, got '${chk.formatType(condType)}'`,
          condast
        );
      }
      const leftType = chk.check(leftast, ctx);
      const rightType = chk.check(rightast, ctx);
      const unified = leftType.unify(chk.registry, rightType);
      ast.handle = handleTernary;
      if (unified) return unified;
      throw chk.createError(
        "incompatible_ternary_branches",
        `Ternary branches must have the same type, got '${chk.formatType(
          leftType
        )}' and '${chk.formatType(rightType)}'`,
        ast
      );
    },
    evaluate(ev, ast, ctx) {
      return ev.eval(ast.condition, ctx).pipe(
        Effect.flatMap((c) => handleTernary(c, ev, ast, ctx))
      );
    }
  },
  "||": {
    check: checkLogicalOp,
    evaluate(ev, ast, ctx) {
      return Effect.gen(function* () {
        const leftResult = yield* ev.tryEval(ast.left, ctx);
        if (Either.isRight(leftResult)) {
          const l = leftResult.right;
          if (l === true) return true;
          if (l === false) {
            const right = yield* ev.eval(ast.right, ctx);
            if (typeof right === "boolean") return right;
            return yield* Effect.fail(logicalOperandError(ev, right, ast.right));
          }
          const rightResult2 = yield* ev.tryEval(ast.right, ctx);
          if (Either.isRight(rightResult2)) {
            const r = rightResult2.right;
            if (r === true) return true;
            if (typeof r === "boolean") return yield* Effect.fail(logicalOperandError(ev, l, ast.left));
            return yield* Effect.fail(logicalOperandError(ev, r, ast.right));
          }
          return yield* Effect.fail(logicalOperandError(ev, l, ast.left));
        }
        const rightResult = yield* ev.tryEval(ast.right, ctx);
        if (Either.isRight(rightResult)) {
          const r = rightResult.right;
          if (r === true) return true;
          if (r === false) return yield* Effect.fail(leftResult.left);
          return yield* Effect.fail(logicalOperandError(ev, r, ast.right));
        }
        return yield* Effect.fail(leftResult.left);
      });
    }
  },
  "&&": {
    check: checkLogicalOp,
    evaluate(ev, ast, ctx) {
      return Effect.gen(function* () {
        const leftResult = yield* ev.tryEval(ast.left, ctx);
        if (Either.isRight(leftResult)) {
          const l = leftResult.right;
          if (l === false) return false;
          if (l === true) {
            const right = yield* ev.eval(ast.right, ctx);
            if (typeof right === "boolean") return right;
            return yield* Effect.fail(logicalOperandError(ev, right, ast.right));
          }
          const rightResult2 = yield* ev.tryEval(ast.right, ctx);
          if (Either.isRight(rightResult2)) {
            const r = rightResult2.right;
            if (r === false) return false;
            if (typeof r === "boolean") return yield* Effect.fail(logicalOperandError(ev, l, ast.left));
            return yield* Effect.fail(logicalOperandError(ev, r, ast.right));
          }
          return yield* Effect.fail(logicalOperandError(ev, l, ast.left));
        }
        const rightResult = yield* ev.tryEval(ast.right, ctx);
        if (Either.isRight(rightResult)) {
          const r = rightResult.right;
          if (r === false) return false;
          if (r === true) return yield* Effect.fail(leftResult.left);
          return yield* Effect.fail(logicalOperandError(ev, r, ast.right));
        }
        return yield* Effect.fail(leftResult.left);
      });
    }
  },
  "!_": { alias: "unaryNot", check: checkUnary, evaluate: evaluateUnary },
  "-_": { alias: "unaryMinus", check: checkUnary, evaluate: evaluateUnary }
};
const binaryOperators = ["!=", "==", "in", "+", "-", "*", "/", "%", "<", "<=", ">", ">="];
for (const op of binaryOperators) OPERATORS_MAP[op] = { check: checkBinary, evaluate: evaluateBinary };
for (const op of objKeys(OPERATORS_MAP)) {
  const obj = OPERATORS_MAP[op];
  obj.name = op;
  if (obj.alias) OPERATORS_MAP[obj.alias] = obj;
}
export const OPERATORS = OPERATORS_MAP;
