import createLinuxCncInterpModule from "../../../build/wasm/core/linuxcnc_interp.js";

function allocCString(mod, value) {
  const bytes = mod.lengthBytesUTF8(value) + 1;
  const ptr = mod._malloc(bytes);
  mod.stringToUTF8(value, ptr, bytes);
  return ptr;
}

function ensureParentPath(mod, path) {
  const parts = path.split("/").filter(Boolean);
  let current = "";
  for (const part of parts.slice(0, -1)) {
    current += `/${part}`;
    try {
      mod.FS.mkdir(current);
    } catch {
      // Directory already exists.
    }
  }
}

function copyResultString(mod, resultPtr, functionName) {
  if (!resultPtr) {
    throw new Error(`${functionName} returned null`);
  }
  return mod.UTF8ToString(resultPtr);
}

function callStringResult(mod, functionName, ...values) {
  const ptrs = values.map((value) => allocCString(mod, value));
  let resultPtr = 0;
  try {
    resultPtr = mod[`_${functionName}`](...ptrs);
    return copyResultString(mod, resultPtr, functionName);
  } finally {
    if (resultPtr) {
      mod._lcinterp_free_string(resultPtr);
    }
    for (const ptr of ptrs) {
      mod._free(ptr);
    }
  }
}

export async function createLinuxCncInterpSdk(moduleOptions = {}) {
  const mod = await createLinuxCncInterpModule(moduleOptions);

  return {
    module: mod,

    writeTextFile(path, text) {
      ensureParentPath(mod, path);
      mod.FS.writeFile(path, text, { encoding: "utf8" });
    },

    readTextFile(path) {
      return mod.FS.readFile(path, { encoding: "utf8" });
    },

    runSimConfigProgram({
      iniPath,
      programPath,
      files = [],
      executionMode = "fileWithIni",
    }) {
      for (const file of files) {
        ensureParentPath(mod, file.path);
        mod.FS.writeFile(file.path, file.text, { encoding: "utf8" });
        if (file.executable) {
          mod.FS.chmod(file.path, 0o755);
        }
      }

      if (executionMode === "fiveAxisRemap") {
        return callStringResult(
          mod,
          "lcinterp_run_fiveaxis_remap_file",
          programPath,
          iniPath,
        );
      }
      if (executionMode === "fileWithIni") {
        return callStringResult(mod, "lcinterp_run_file_with_ini", programPath, iniPath);
      }
      throw new Error(`unsupported sim config execution mode: ${executionMode}`);
    },

    runProgram(programText) {
      return callStringResult(mod, "lcinterp_run_program", programText);
    },

    runProgramWithIni(programText, iniPath) {
      return callStringResult(mod, "lcinterp_run_program_with_ini", programText, iniPath);
    },

    runFile(path) {
      return callStringResult(mod, "lcinterp_run_file", path);
    },

    runFileWithIni(path, iniPath) {
      return callStringResult(mod, "lcinterp_run_file_with_ini", path, iniPath);
    },

    runFileWithIniContinueOnError(path, iniPath) {
      return callStringResult(
        mod,
        "lcinterp_run_file_with_ini_continue_on_error",
        path,
        iniPath,
      );
    },

    runFiveAxisRemapFile(path, iniPath) {
      return callStringResult(mod, "lcinterp_run_fiveaxis_remap_file", path, iniPath);
    },

    runRemapFile(path, iniPath) {
      return callStringResult(mod, "lcinterp_run_remap_file", path, iniPath);
    },

    runRemapFileContinueOnError(path, iniPath) {
      return callStringResult(mod, "lcinterp_run_remap_file_continue_on_error", path, iniPath);
    },

    runRemapIoMdiSequence(iniPath) {
      return callStringResult(mod, "lcinterp_run_remap_io_mdi_sequence", iniPath);
    },

    restoreParameters(path) {
      return callStringResult(mod, "lcinterp_restore_parameters", path);
    },

    saveParameters(path, values) {
      const assignments = Object.entries(values)
        .map(([parameter, value]) => `${parameter} ${value}`)
        .join("\n");
      return callStringResult(mod, "lcinterp_save_parameters", path, assignments);
    },

    loadToolTable(path, options = {}) {
      const functionName = options.randomToolChanger
        ? "lcinterp_load_tool_table_random"
        : "lcinterp_load_tool_table";
      return callStringResult(mod, functionName, path);
    },

    saveToolTable(path) {
      return callStringResult(mod, "lcinterp_save_tool_table", path);
    },

    probeInitAndSynch() {
      return callStringResult(mod, "lcinterp_probe_init_and_synch");
    },

    probeIndexer() {
      return callStringResult(mod, "lcinterp_probe_indexer");
    },

    probeNamedParameters(iniPath) {
      return callStringResult(mod, "lcinterp_probe_named_parameters", iniPath);
    },
  };
}
