#ifdef unix
#undef unix
#endif

#include "nix/expr/eval.hh"
#include "nix/expr/primops.hh"
#include "nix/expr/value.hh"
#include "nix/store/store-api.hh"
#include "nix/util/source-path.hh"

#include "irc/ir_gen.h"
#include "irc/parser.h"
#include "irc/resolver.h"
#include "irc/serializer.h"
#include "irc/types.h"
#include "irc/evaluator.h"

#include <fstream>
#include <iostream>
#include <memory>
#include <optional>

namespace nix_ir_plugin {

using namespace nix;
using namespace nix_irc;

/**
 * Load and evaluate a pre-compiled IR bundle
 * Usage: builtins.nixIR.loadIR "/path/to/file.nixir"
 */
static void prim_loadIR(EvalState &state, const PosIdx pos, Value **args,
                        Value &v) {
  auto path = state.forceStringNoCtx(
      *args[0], pos,
      "while evaluating the first argument to builtins.nixIR.loadIR");

  std::string pathStr(path);

  Deserializer deserializer;
  IRModule module;

  try {
    module = deserializer.deserialize(pathStr);
  } catch (const std::exception &e) {
    state.error<EvalError>("failed to deserialize IR bundle: %s", e.what())
        .atPos(pos)
        .debugThrow();
  }

  if (!module.entry) {
    state.error<EvalError>("IR bundle has no entry point")
        .atPos(pos)
        .debugThrow();
  }

  try {
    Evaluator evaluator(state);
    evaluator.eval_to_nix(module.entry, v);
  } catch (const std::exception &e) {
    state.error<EvalError>("failed to evaluate IR: %s", e.what())
        .atPos(pos)
        .debugThrow();
  }
}

/**
 * Compile Nix source to IR on-the-fly
 * Usage: builtins.nixIR.compile "{ x = 1; }"
 */
static void prim_compileNix(EvalState &state, const PosIdx pos, Value **args,
                            Value &v) {
  auto source = state.forceStringNoCtx(
      *args[0], pos,
      "while evaluating the first argument to builtins.nixIR.compile");

  std::string sourceStr(source);

  try {
    Parser parser;
    auto ast = parser.parse(sourceStr, "<inline>");

    if (!ast) {
      state.error<EvalError>("failed to parse Nix expression")
          .atPos(pos)
          .debugThrow();
    }

    IRGenerator ir_gen;
    auto ir = ir_gen.generate(ast);

    Evaluator evaluator(state);
    evaluator.eval_to_nix(ir, v);

  } catch (const std::exception &e) {
    state.error<EvalError>("IR compilation failed: %s", e.what())
        .atPos(pos)
        .debugThrow();
  }
}

/**
 * Get information about the IR plugin
 * Usage: builtins.nixIR.info
 */
static void prim_info(EvalState &state, const PosIdx pos, Value **args,
                      Value &v) {
  auto bindings = state.buildBindings(3);

  Value *vName = state.allocValue();
  vName->mkString("nix-ir-plugin");
  bindings.insert(state.symbols.create("name"), vName);

  Value *vVersion = state.allocValue();
  vVersion->mkString("0.1.0");
  bindings.insert(state.symbols.create("version"), vVersion);

  Value *vStatus = state.allocValue();
  vStatus->mkString("runtime-active");
  bindings.insert(state.symbols.create("status"), vStatus);

  v.mkAttrs(bindings.finish());
}

// Register primops on plugin load
static RegisterPrimOp rp_loadIR({
    .name = "__nixIR_loadIR",
    .args = {"path"},
    .arity = 1,
    .doc = R"(
      Load a pre-compiled IR bundle from the given path.
      Returns an attrset with information about the loaded bundle.

    )",
    .fun = prim_loadIR,
});

static RegisterPrimOp rp_compileNix({
    .name = "__nixIR_compile",
    .args = {"source"},
    .arity = 1,
    .doc = R"(
      Compile Nix source code to IR on-the-fly.
      Returns an attrset with information about the compiled IR.

    )",
    .fun = prim_compileNix,
});

static RegisterPrimOp rp_info({
    .name = "__nixIR_info",
    .args = {},
    .arity = 0,
    .doc = R"(
      Get information about the IR plugin.
      Returns an attrset with plugin name, version, and status.
    )",
    .fun = prim_info,
});

} // namespace nix_ir_plugin

// Plugin initialization message
__attribute__((constructor)) static void init_plugin() {
  std::cerr << "nix-ir-plugin loaded" << std::endl;
}
