import { Effect, Option } from "effect";
import { evaluationError, parseError } from "./errors.js";
export function optionalOf(value) {
  return value === void 0 ? Option.none() : Option.some(value);
}
export function optionalValue(opt) {
  if (Option.isNone(opt)) {
    throw evaluationError("optional_value_missing", "Optional value is not present");
  }
  return opt.value;
}
class OptionalNamespace {
  #kind = "optional";
  toString() {
    return this.#kind;
  }
}
const optionalNamespace = new OptionalNamespace();
export function register(registry) {
  const functionOverload = (sig, handler) => registry.registerFunctionOverload(sig, handler);
  registry.registerType("OptionalNamespace", OptionalNamespace);
  registry.registerConstant("optional", "OptionalNamespace", optionalNamespace);
  functionOverload("optional.hasValue(): bool", (v) => Option.isSome(v));
  functionOverload("optional<A>.value(): A", (v) => optionalValue(v));
  registry.registerFunctionOverload("OptionalNamespace.none(): optional<T>", () => Option.none());
  functionOverload("OptionalNamespace.of(A): optional<A>", (_, value) => optionalOf(value));
  function ensureOptional(value, ast, description) {
    if (Option.isOption(value)) return value;
    throw evaluationError("optional_expected", `${description} must be optional`, ast);
  }
  function evaluateOptional(ev, macro, ctx) {
    return Effect.gen(function* () {
      const v = yield* ev.eval(macro.receiver, ctx);
      const optional = ensureOptional(v, macro.receiver, `${macro.functionDesc} receiver`);
      if (Option.isSome(optional)) return macro.onHasValue(optional);
      return yield* macro.onEmpty(ev, macro, ctx);
    });
  }
  function ensureOptionalType(checker, node, ctx, description) {
    const type = checker.check(node, ctx);
    if (type.kind === "optional") return type;
    if (type.kind === "dyn") return checker.getType("optional");
    throw checker.createError("optional_expected", `${description} must be optional, got '${type}'`, node);
  }
  function createOptionalMacro({ functionDesc, evaluate, typeCheck, onHasValue, onEmpty }) {
    return (opts) => {
      const { ast, args, receiver } = opts;
      return {
        ast,
        functionDesc,
        receiver,
        arg: args[0],
        evaluate,
        typeCheck,
        onHasValue,
        onEmpty
      };
    };
  }
  const invalidOrValueReceiver = "optional.orValue() receiver";
  const invalidOrReceiver = "optional.or(optional) receiver";
  const invalidOrArg = "optional.or(optional) argument";
  registry.registerFunctionOverload(
    "optional.or(ast): optional<dyn>",
    createOptionalMacro({
      functionDesc: "optional.or(optional)",
      evaluate: evaluateOptional,
      typeCheck(check, macro, ctx) {
        const l = ensureOptionalType(check, macro.receiver, ctx, invalidOrReceiver);
        const r = ensureOptionalType(check, macro.arg, ctx, invalidOrArg);
        const unified = l.unify(check.registry, r);
        if (unified) return unified;
        throw check.createError(
          "incompatible_argument_type",
          `${macro.functionDesc} argument must be compatible type, got '${l}' and '${r}'`,
          macro.arg
        );
      },
      onHasValue: (optional) => optional,
      onEmpty(ev, macro, ctx) {
        const ast = macro.arg;
        return ev.eval(ast, ctx).pipe(
          Effect.map((v) => ensureOptional(v, ast, invalidOrArg))
        );
      }
    })
  );
  registry.registerFunctionOverload(
    "optional.orValue(ast): dyn",
    createOptionalMacro({
      functionDesc: "optional.orValue(value)",
      onHasValue: (opt) => opt.value,
      onEmpty(ev, macro, ctx) {
        return ev.eval(macro.arg, ctx);
      },
      evaluate: evaluateOptional,
      typeCheck(check, macro, ctx) {
        const l = ensureOptionalType(check, macro.receiver, ctx, invalidOrValueReceiver).valueType;
        const r = check.check(macro.arg, ctx);
        if (!l) return r;
        const unified = l.unify(check.registry, r);
        if (unified) return unified;
        throw check.createError(
          "incompatible_argument_type",
          `${macro.functionDesc} argument must be compatible type, got '${l}' and '${r}'`,
          macro.arg
        );
      }
    })
  );
  registry.registerFunctionOverload(
    "optional<A>.flatMap(ast, ast): optional<dyn>",
    createFlatMapExpander()
  );
}
function createFlatMapExpander() {
  const functionDesc = "optional.flatMap(var, body)";
  const invalidVar = `${functionDesc} invalid iteration variable`;
  const invalidReceiver = `${functionDesc} receiver`;
  const invalidBody = `${functionDesc} body`;
  function ensureOptional(value, ast, description) {
    if (Option.isOption(value)) return value;
    throw evaluationError("optional_expected", `${description} must be optional`, ast);
  }
  function ensureOptionalType(checker, node, ctx, description) {
    const type = checker.check(node, ctx);
    if (type.kind === "optional") return type;
    if (type.kind === "dyn") return checker.getType("optional");
    throw checker.createError("optional_expected", `${description} must be optional, got '${type}'`, node);
  }
  function typeCheck(checker, macro, ctx) {
    const receiverType = ensureOptionalType(checker, macro.receiver, ctx, invalidReceiver);
    const innerType = receiverType.valueType ?? checker.getType("dyn");
    macro.iterCtx = ctx.forkWithVariable(macro.iterVarName, innerType);
    return ensureOptionalType(checker, macro.body, macro.iterCtx, invalidBody);
  }
  function evaluate(ev, macro, ctx) {
    return Effect.gen(function* () {
      const recv = yield* ev.eval(macro.receiver, ctx);
      const optional = ensureOptional(recv, macro.receiver, invalidReceiver);
      if (Option.isNone(optional)) return Option.none();
      const iterCtx = macro.iterCtx.reuse(ctx);
      const result = yield* ev.eval(macro.body, iterCtx.setIterValue(optional.value, ev));
      return ensureOptional(result, macro.body, invalidBody);
    });
  }
  return (opts) => {
    const { args, receiver, ast } = opts;
    const iterVar = args[0];
    if (iterVar.op !== "id") throw parseError("invalid_macro_argument", invalidVar, iterVar);
    return {
      ast,
      receiver,
      functionDesc,
      iterVarName: iterVar.args,
      body: args[1],
      iterCtx: void 0,
      evaluate,
      typeCheck
    };
  };
}
