import { Either, Effect } from "effect";
import { createRegistry, RootContext } from "./registry.js";
import { evaluationError } from "./errors.js";
import { registerFunctions } from "./functions.js";
import { registerHttpBuiltins } from "./http-builtins.js";
import { registerJSONBuiltins } from "./json-builtins.js";
import { registerFormBuiltins } from "./form-builtins.js";
import { registerMacros } from "./macros.js";
import { registerOverloads } from "./overloads.js";
import { TypeChecker } from "./type-checker.js";
import { Parser } from "./parser.js";
import { createOptions } from "./options.js";
import { Base } from "./operators.js";
const globalRegistry = createRegistry();
registerFunctions(globalRegistry);
registerOverloads(globalRegistry);
registerMacros(globalRegistry);
registerHttpBuiltins(globalRegistry);
registerJSONBuiltins(globalRegistry);
registerFormBuiltins(globalRegistry);
class Environment {
  #registry;
  #evaluator;
  #typeChecker;
  #evalTypeChecker;
  #parser;
  #opts;
  constructor(opts, inherited) {
    this.#opts = createOptions(opts, inherited instanceof Environment ? inherited.#opts : void 0);
    this.#registry = (inherited instanceof Environment ? inherited.#registry : globalRegistry).clone(this.#opts);
    const childOpts = { registry: this.#registry, opts: this.#opts };
    this.#typeChecker = new TypeChecker(childOpts);
    this.#evalTypeChecker = new TypeChecker(childOpts, true);
    this.#evaluator = new Evaluator(childOpts);
    this.#parser = new Parser(this.#opts.limits, this.#registry);
    Object.freeze(this);
  }
  clone(opts) {
    return new Environment(opts, this);
  }
  registerFunction(signature, handler, opts) {
    this.#registry.registerFunctionOverload(signature, handler, opts);
    return this;
  }
  registerOperator(signature, handler, opts) {
    this.#registry.registerOperatorOverload(signature, handler, opts);
    return this;
  }
  registerType(typename, constructor) {
    this.#registry.registerType(typename, constructor);
    return this;
  }
  registerVariable(name, type, opts) {
    this.#registry.registerVariable(name, type, opts);
    return this;
  }
  registerConstant(name, type, value) {
    this.#registry.registerConstant(name, type, value);
    return this;
  }
  getDefinitions() {
    return this.#registry.getDefinitions();
  }
  check(expression) {
    try {
      const ast = this.#parser.parse(expression);
      const typeDecl = this.#typeChecker.check(ast, new RootContext(this.#registry));
      return Either.right(this.#formatTypeForCheck(typeDecl));
    } catch (error) {
      return Either.left(error);
    }
  }
  #formatTypeForCheck(typeDecl) {
    if (typeDecl.name === `list<dyn>`) return "list";
    if (typeDecl.name === `map<dyn, dyn>`) return "map";
    return typeDecl.name;
  }
  parse(expression) {
    const ast = this.#parser.parse(expression);
    const evaluateFn = this.#evaluateAST.bind(this, ast);
    evaluateFn.check = () => {
      try {
        const typeDecl = this.#typeChecker.check(ast, new RootContext(this.#registry));
        return { valid: true, type: this.#formatTypeForCheck(typeDecl) };
      } catch (error) {
        return { valid: false, error };
      }
    };
    evaluateFn.ast = ast;
    return evaluateFn;
  }
  evaluate(expression, context) {
    return Effect.try({
      try: () => {
        const ast = this.#parser.parse(expression);
        const ctx = new RootContext(this.#registry, context);
        if (!ast.checkedType) this.#evalTypeChecker.check(ast, ctx);
        return { ast, ctx };
      },
      catch: (error) => error
    }).pipe(
      Effect.flatMap(({ ast, ctx }) => ast.evaluate(this.#evaluator, ast, ctx))
    );
  }
  #evaluateAST(ast, context) {
    return Effect.try({
      try: () => {
        const ctx = new RootContext(this.#registry, context);
        if (!ast.checkedType) this.#evalTypeChecker.check(ast, ctx);
        return ctx;
      },
      catch: (error) => error
    }).pipe(
      Effect.flatMap((ctx) => ast.evaluate(this.#evaluator, ast, ctx))
    );
  }
}
class Evaluator extends Base {
  constructor(opts) {
    super(opts);
    this.createError = evaluationError;
  }
  #firstMapElement(coll) {
    if (coll instanceof Map) return coll.entries().next().value;
    for (const key in coll) return [key, coll[key]];
    return void 0;
  }
  debugRuntimeType(value, checkedType) {
    return checkedType?.hasDynType === false ? checkedType : this.debugTypeDeep(value);
  }
  debugTypeDeep(value) {
    const runtimeType = this.debugType(value);
    switch (runtimeType.kind) {
      case "list": {
        const first = value instanceof Array ? value[0] : value.values().next().value;
        if (first === void 0) return runtimeType;
        return this.registry.getListType(this.debugTypeDeep(first));
      }
      case "map": {
        const first = this.#firstMapElement(value);
        if (!first) return runtimeType;
        return this.registry.getMapType(
          runtimeType.keyType.hasDynType ? this.debugTypeDeep(first[0]) : runtimeType.keyType,
          runtimeType.valueType.hasDynType ? this.debugTypeDeep(first[1]) : runtimeType.valueType
        );
      }
      default:
        return runtimeType;
    }
  }
  tryEval(ast, ctx) {
    return Effect.either(this.eval(ast, ctx));
  }
  eval(ast, ctx) {
    return ast.evaluate(this, ast, ctx);
  }
}
const globalEnvironment = new Environment({
  unlistedVariablesAreDyn: true
});
export function parse(expression) {
  return globalEnvironment.parse(expression);
}
export function evaluate(expression, context) {
  return globalEnvironment.evaluate(expression, context);
}
export { Environment };
