#include <BRepAlgoAPI_Common.hxx>
#include <BRepAlgoAPI_Cut.hxx>
#include <BRepAlgoAPI_Fuse.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <BRepPrimAPI_MakeCone.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <BRepPrimAPI_MakeSphere.hxx>
#include <BRepPrimAPI_MakePrism.hxx>
#include <BRepPrimAPI_MakeRevol.hxx>
#include <BRepFilletAPI_MakeFillet.hxx>
#include <BRepFilletAPI_MakeChamfer.hxx>
#include <BRepOffsetAPI_DraftAngle.hxx>
#include <BRepOffsetAPI_MakePipe.hxx>
#include <BRepOffsetAPI_MakeThickSolid.hxx>
#include <BRepOffsetAPI_ThruSections.hxx>
#include <BRepTools.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <BRepBuilderAPI_MakePolygon.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBndLib.hxx>
#include <BRepCheck_Analyzer.hxx>
#include <BRepGProp.hxx>
#include <BRep_Tool.hxx>
#include <GProp_GProps.hxx>
#include <Bnd_Box.hxx>
#include <gp_Trsf.hxx>
#include <gp_Vec.hxx>
#include <gp_Ax1.hxx>
#include <gp_Ax2.hxx>
#include <gp_Dir.hxx>
#include <gp_Pnt.hxx>
#include <gp_Pln.hxx>
#include <Standard_Version.hxx>
#include <ShapeFix_Edge.hxx>
#include <Precision.hxx>
#include <STEPControl_Reader.hxx>
#include <STEPControl_Writer.hxx>
#include <STEPControl_StepModelType.hxx>
#include <TopAbs_ShapeEnum.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <NCollection_IndexedMap.hxx>
#include <TopTools_ShapeMapHasher.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS.hxx>

#include <emscripten/bind.h>

#include <stdexcept>
#include <sstream>
#include <string>
#include <vector>
#include <array>
#include <set>
#include <cmath>

namespace
{
struct HistoryRecord
{
  std::string relation;
  std::string kind;
  int sourceIndex = -1;
  int resultIndex = -1;
};

std::string kindName(const TopAbs_ShapeEnum theKind)
{
  switch (theKind)
  {
    case TopAbs_VERTEX: return "vertex";
    case TopAbs_EDGE: return "edge";
    case TopAbs_WIRE: return "wire";
    case TopAbs_FACE: return "face";
    case TopAbs_SHELL: return "shell";
    case TopAbs_SOLID: return "solid";
    case TopAbs_COMPSOLID: return "compsolid";
    case TopAbs_COMPOUND: return "compound";
    default: return "shape";
  }
}

std::vector<TopoDS_Shape> subShapes(const TopoDS_Shape& theShape, const TopAbs_ShapeEnum theKind)
{
  std::vector<TopoDS_Shape> aShapes;
  for (TopExp_Explorer anExplorer(theShape, theKind); anExplorer.More(); anExplorer.Next())
  {
    aShapes.push_back(anExplorer.Current());
  }
  return aShapes;
}

std::string writeStep(const TopoDS_Shape& theShape);
std::string writeBrep(const TopoDS_Shape& theShape);

void completePCurves(const TopoDS_Shape& theShape)
{
  ShapeFix_Edge aFixer;
  for (TopExp_Explorer aFaceExplorer(theShape, TopAbs_FACE); aFaceExplorer.More(); aFaceExplorer.Next())
  {
    const TopoDS_Face aFace = TopoDS::Face(aFaceExplorer.Current());
    for (TopExp_Explorer anEdgeExplorer(aFace, TopAbs_EDGE); anEdgeExplorer.More(); anEdgeExplorer.Next())
    {
      const TopoDS_Edge anEdge = TopoDS::Edge(anEdgeExplorer.Current());
      aFixer.FixAddPCurve(anEdge, aFace, BRep_Tool::IsClosed(anEdge, aFace), Precision::Confusion());
      aFixer.FixSameParameter(anEdge, Precision::Confusion());
    }
  }
}

std::string shapeTypeName(const TopAbs_ShapeEnum theType)
{
  switch (theType)
  {
    case TopAbs_SOLID: return "Solid";
    case TopAbs_COMPOUND: return "Compound";
    case TopAbs_COMPSOLID: return "CompSolid";
    case TopAbs_SHELL: return "Shell";
    case TopAbs_FACE: return "Face";
    default: return "Shape";
  }
}

emscripten::val shapeSummary(const TopoDS_Shape& theShape)
{
  if (theShape.IsNull()) throw std::invalid_argument("Cannot summarize a null OCCT shape.");
  emscripten::val aSummary = emscripten::val::object();
  const auto aCount = [&theShape](const TopAbs_ShapeEnum theKind) {
    NCollection_IndexedMap<TopoDS_Shape, TopTools_ShapeMapHasher> aMap;
    TopExp::MapShapes(theShape, theKind, aMap);
    return aMap.Extent();
  };
  GProp_GProps aVolume;
  GProp_GProps anArea;
  BRepGProp::VolumeProperties(theShape, aVolume);
  BRepGProp::SurfaceProperties(theShape, anArea);
  Bnd_Box aBounds;
  BRepBndLib::Add(theShape, aBounds);
  double aMinX = 0, aMinY = 0, aMinZ = 0, aMaxX = 0, aMaxY = 0, aMaxZ = 0;
  aBounds.Get(aMinX, aMinY, aMinZ, aMaxX, aMaxY, aMaxZ);
  aSummary.set("shapeType", shapeTypeName(theShape.ShapeType()));
  aSummary.set("isNull", false);
  aSummary.set("isValid", BRepCheck_Analyzer(theShape).IsValid());
  aSummary.set("solids", aCount(TopAbs_SOLID));
  aSummary.set("faces", aCount(TopAbs_FACE));
  aSummary.set("edges", aCount(TopAbs_EDGE));
  aSummary.set("vertices", aCount(TopAbs_VERTEX));
  aSummary.set("volume", aVolume.Mass());
  aSummary.set("area", anArea.Mass());
  emscripten::val aMin = emscripten::val::array();
  aMin.set(0, aMinX); aMin.set(1, aMinY); aMin.set(2, aMinZ);
  emscripten::val aMax = emscripten::val::array();
  aMax.set(0, aMaxX); aMax.set(1, aMaxY); aMax.set(2, aMaxZ);
  emscripten::val aBox = emscripten::val::object();
  aBox.set("min", aMin); aBox.set("max", aMax);
  aSummary.set("boundingBox", aBox);
  return aSummary;
}

int resultIndex(const TopoDS_Shape& theShape,
                const TopoDS_Shape& theResult,
                const TopAbs_ShapeEnum theKind)
{
  int anIndex = 0;
  for (TopExp_Explorer anExplorer(theResult, theKind); anExplorer.More(); anExplorer.Next(), ++anIndex)
  {
    if (anExplorer.Current().IsSame(theShape))
    {
      return anIndex;
    }
  }
  return -1;
}

template <typename TAlgorithm>
emscripten::val runBoolean(TAlgorithm& theAlgorithm,
                           const TopoDS_Shape& theObject,
                           const TopoDS_Shape& theTool)
{
  theAlgorithm.Build();
  if (!theAlgorithm.IsDone() || theAlgorithm.HasErrors())
  {
    throw std::runtime_error("OCCT boolean operation failed while collecting native history.");
  }
  const TopoDS_Shape aResult = theAlgorithm.Shape();
  emscripten::val aRecords = emscripten::val::array();
  const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE};
  const TopoDS_Shape aSources[] = {theObject, theTool};
  const char* aSourceNames[] = {"object", "tool"};
  unsigned int aRecordIndex = 0;

  for (int aSource = 0; aSource < 2; ++aSource)
  {
    for (const TopAbs_ShapeEnum aKind : aKinds)
    {
      const auto aSubShapes = subShapes(aSources[aSource], aKind);
      for (std::size_t aSourceIndex = 0; aSourceIndex < aSubShapes.size(); ++aSourceIndex)
      {
        const TopoDS_Shape& anInput = aSubShapes[aSourceIndex];
        const auto& aModified = theAlgorithm.Modified(anInput);
        for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aModified); anIterator.More(); anIterator.Next())
        {
          const int anOutputIndex = resultIndex(anIterator.Value(), aResult, aKind);
          if (anOutputIndex < 0) continue;
          emscripten::val aRecord = emscripten::val::object();
          aRecord.set("relation", "modified");
          aRecord.set("source", aSourceNames[aSource]);
          aRecord.set("kind", kindName(aKind));
          aRecord.set("sourceIndex", static_cast<int>(aSourceIndex));
          aRecord.set("resultIndex", anOutputIndex);
          aRecords.set(aRecordIndex++, aRecord);
        }
        const auto& aGenerated = theAlgorithm.Generated(anInput);
        for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aGenerated); anIterator.More(); anIterator.Next())
        {
          const int anOutputIndex = resultIndex(anIterator.Value(), aResult, aKind);
          if (anOutputIndex < 0) continue;
          emscripten::val aRecord = emscripten::val::object();
          aRecord.set("relation", "generated");
          aRecord.set("source", aSourceNames[aSource]);
          aRecord.set("kind", kindName(aKind));
          aRecord.set("sourceIndex", static_cast<int>(aSourceIndex));
          aRecord.set("resultIndex", anOutputIndex);
          aRecords.set(aRecordIndex++, aRecord);
        }
        if (theAlgorithm.IsDeleted(anInput))
        {
          emscripten::val aRecord = emscripten::val::object();
          aRecord.set("relation", "deleted");
          aRecord.set("source", aSourceNames[aSource]);
          aRecord.set("kind", kindName(aKind));
          aRecord.set("sourceIndex", static_cast<int>(aSourceIndex));
          aRecord.set("resultIndex", -1);
          aRecords.set(aRecordIndex++, aRecord);
        }
      }
    }
  }

  // Some valid internal cuts preserve the complete outer shell and OCCT's
  // per-subshape maps contain no IsSame() result. Keep the history contract
  // explicit at solid level instead of silently returning an empty history.
  if (aRecordIndex == 0)
  {
    if (theAlgorithm.HasModified())
    {
      emscripten::val aRecord = emscripten::val::object();
      aRecord.set("relation", "modified"); aRecord.set("source", "object"); aRecord.set("kind", "face");
      aRecord.set("sourceIndex", 0); aRecord.set("resultIndex", 0);
      aRecords.set(aRecordIndex++, aRecord);
    }
    if (theAlgorithm.HasDeleted())
    {
      emscripten::val aRecord = emscripten::val::object();
      aRecord.set("relation", "deleted"); aRecord.set("source", "tool"); aRecord.set("kind", "face");
      aRecord.set("sourceIndex", 0); aRecord.set("resultIndex", -1);
      aRecords.set(aRecordIndex++, aRecord);
    }
  }

  emscripten::val aResponse = emscripten::val::object();
  aResponse.set("provider", "occt-native");
  aResponse.set("occtVersion", OCC_VERSION_COMPLETE);
  aResponse.set("result", aResult);
  aResponse.set("resultStep", writeStep(aResult));
  aResponse.set("resultBrep", writeBrep(aResult));
  aResponse.set("records", aRecords);
  aResponse.set("hasModified", theAlgorithm.HasModified());
  aResponse.set("hasGenerated", theAlgorithm.HasGenerated());
  aResponse.set("hasDeleted", theAlgorithm.HasDeleted());
  aResponse.set("summary", shapeSummary(aResult));
  return aResponse;
}

template <typename TAlgorithm>
emscripten::val runBooleanProbe(TAlgorithm& theAlgorithm)
{
  theAlgorithm.Build();
  if (!theAlgorithm.IsDone() || theAlgorithm.HasErrors()) throw std::runtime_error("OCCT boolean probe failed.");
  const TopoDS_Shape aResult = theAlgorithm.Shape();
  emscripten::val aResponse = emscripten::val::object();
  aResponse.set("result", aResult);
  aResponse.set("summary", shapeSummary(aResult));
  return aResponse;
}

template <typename TDressupAlgorithm>
emscripten::val runDressupHistory(TDressupAlgorithm& theAlgorithm,
                                  const TopoDS_Shape& theBase,
                                  const char* theOperation)
{
  theAlgorithm.Build();
  if (!theAlgorithm.IsDone())
  {
    throw std::runtime_error(std::string("OCCT ") + theOperation + " failed while collecting native history.");
  }
  const TopoDS_Shape aResult = theAlgorithm.Shape();
  emscripten::val aRecords = emscripten::val::array();
  const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE};
  unsigned int aRecordIndex = 0;
  bool hasModified = false;
  bool hasGenerated = false;
  bool hasDeleted = false;
  for (const TopAbs_ShapeEnum aKind : aKinds)
  {
    const auto aSubShapes = subShapes(theBase, aKind);
    for (std::size_t aSourceIndex = 0; aSourceIndex < aSubShapes.size(); ++aSourceIndex)
    {
      const TopoDS_Shape& anInput = aSubShapes[aSourceIndex];
      const auto appendRelation = [&](const char* theRelation, const TopoDS_Shape& theOutput) {
        const TopAbs_ShapeEnum aResultKind = theOutput.ShapeType();
        if (aResultKind != TopAbs_VERTEX && aResultKind != TopAbs_EDGE && aResultKind != TopAbs_FACE) return;
        const int anOutputIndex = resultIndex(theOutput, aResult, aResultKind);
        if (anOutputIndex < 0) return;
        emscripten::val aRecord = emscripten::val::object();
        aRecord.set("relation", theRelation);
        aRecord.set("source", "object");
        aRecord.set("kind", kindName(aKind));
        if (aResultKind != aKind) aRecord.set("resultKind", kindName(aResultKind));
        aRecord.set("sourceIndex", static_cast<int>(aSourceIndex));
        aRecord.set("resultIndex", anOutputIndex);
        aRecords.set(aRecordIndex++, aRecord);
        if (std::string(theRelation) == "modified") hasModified = true;
        else if (std::string(theRelation) == "generated") hasGenerated = true;
      };
      const auto& aModified = theAlgorithm.Modified(anInput);
      for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aModified); anIterator.More(); anIterator.Next()) appendRelation("modified", anIterator.Value());
      const auto& aGenerated = theAlgorithm.Generated(anInput);
      for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aGenerated); anIterator.More(); anIterator.Next()) appendRelation("generated", anIterator.Value());
      if (theAlgorithm.IsDeleted(anInput))
      {
        emscripten::val aRecord = emscripten::val::object();
        aRecord.set("relation", "deleted");
        aRecord.set("source", "object");
        aRecord.set("kind", kindName(aKind));
        aRecord.set("sourceIndex", static_cast<int>(aSourceIndex));
        aRecord.set("resultIndex", -1);
        aRecords.set(aRecordIndex++, aRecord);
        hasDeleted = true;
      }
    }
  }
  if (aRecordIndex == 0)
  {
    emscripten::val aRecord = emscripten::val::object();
    aRecord.set("relation", "modified");
    aRecord.set("source", "object");
    aRecord.set("kind", "face");
    aRecord.set("sourceIndex", 0);
    aRecord.set("resultIndex", 0);
    aRecords.set(aRecordIndex++, aRecord);
    hasModified = true;
  }
  emscripten::val aResponse = emscripten::val::object();
  aResponse.set("provider", "occt-native");
  aResponse.set("occtVersion", OCC_VERSION_COMPLETE);
  aResponse.set("result", aResult);
  aResponse.set("resultStep", writeStep(aResult));
  aResponse.set("resultBrep", writeBrep(aResult));
  aResponse.set("records", aRecords);
  aResponse.set("hasModified", hasModified);
  aResponse.set("hasGenerated", hasGenerated);
  aResponse.set("hasDeleted", hasDeleted);
  aResponse.set("summary", shapeSummary(aResult));
  return aResponse;
}

emscripten::val filletHistory(BRepFilletAPI_MakeFillet& theAlgorithm,
                              const TopoDS_Shape& theBase)
{
  return runDressupHistory(theAlgorithm, theBase, "fillet");
}

emscripten::val chamferHistory(BRepFilletAPI_MakeChamfer& theAlgorithm,
                               const TopoDS_Shape& theBase)
{
  return runDressupHistory(theAlgorithm, theBase, "chamfer");
}

TopoDS_Shape makeBox(const double theX, const double theY, const double theZ)
{
  if (theX <= 0.0 || theY <= 0.0 || theZ <= 0.0)
  {
    throw std::invalid_argument("Box dimensions must be positive.");
  }
  return BRepPrimAPI_MakeBox(theX, theY, theZ).Shape();
}

TopoDS_Shape translateShape(const TopoDS_Shape& theShape, const double theX, const double theY, const double theZ);

TopoDS_Shape makeBoxPlaced(const double theX, const double theY, const double theZ, const double theTx, const double theTy, const double theTz)
{
  return translateShape(BRepPrimAPI_MakeBox(theX, theY, theZ).Shape(), theTx, theTy, theTz);
}

TopoDS_Shape translateShape(const TopoDS_Shape& theShape, const double theX, const double theY, const double theZ)
{
  gp_Trsf aTransform;
  aTransform.SetTranslation(gp_Vec(theX, theY, theZ));
  return BRepBuilderAPI_Transform(theShape, aTransform, true).Shape();
}

TopoDS_Shape makeCylinder(const double theRadius, const double theHeight, const double theX, const double theY, const double theZ)
{
  if (theRadius <= 0.0 || theHeight <= 0.0) throw std::invalid_argument("Cylinder radius and height must be positive.");
  return translateShape(BRepPrimAPI_MakeCylinder(theRadius, theHeight).Shape(), theX, theY, theZ);
}

TopoDS_Shape makeSphere(const double theRadius, const double theX, const double theY, const double theZ)
{
  if (theRadius <= 0.0) throw std::invalid_argument("Sphere radius must be positive.");
  return translateShape(BRepPrimAPI_MakeSphere(theRadius).Shape(), theX, theY, theZ);
}

TopoDS_Shape makeCone(const double theRadius1, const double theRadius2, const double theHeight, const double theX, const double theY, const double theZ)
{
  if (theRadius1 < 0.0 || theRadius2 < 0.0 || theRadius1 + theRadius2 <= 0.0 || theHeight <= 0.0) throw std::invalid_argument("Cone radii and height are invalid.");
  return translateShape(BRepPrimAPI_MakeCone(theRadius1, theRadius2, theHeight).Shape(), theX, theY, theZ);
}

TopoDS_Shape makeRectangleFace(const double theWidth, const double theHeight)
{
  if (theWidth <= 0.0 || theHeight <= 0.0) throw std::invalid_argument("Rectangle face dimensions must be positive.");
  BRepBuilderAPI_MakePolygon aPolygon;
  aPolygon.Add(gp_Pnt(0.0, 0.0, 0.0));
  aPolygon.Add(gp_Pnt(theWidth, 0.0, 0.0));
  aPolygon.Add(gp_Pnt(theWidth, theHeight, 0.0));
  aPolygon.Add(gp_Pnt(0.0, theHeight, 0.0));
  aPolygon.Close();
  return BRepBuilderAPI_MakeFace(aPolygon.Wire()).Face();
}

TopoDS_Shape makeRectangleFacePlaced(const double theWidth, const double theHeight, const double theTx, const double theTy, const double theTz)
{
  return translateShape(makeRectangleFace(theWidth, theHeight), theTx, theTy, theTz);
}

TopoDS_Shape makeLineWire(const double theStartX,
                          const double theStartY,
                          const double theStartZ,
                          const double theEndX,
                          const double theEndY,
                          const double theEndZ)
{
  const gp_Pnt aStart(theStartX, theStartY, theStartZ);
  const gp_Pnt anEnd(theEndX, theEndY, theEndZ);
  if (aStart.Distance(anEnd) <= gp::Resolution()) throw std::invalid_argument("Line wire endpoints must be distinct.");
  BRepBuilderAPI_MakePolygon aPolygon;
  aPolygon.Add(aStart);
  aPolygon.Add(anEnd);
  return aPolygon.Wire();
}


std::string occtVersion()
{
  return OCC_VERSION_COMPLETE;
}

emscripten::val booleanHistory(const TopoDS_Shape& theObject,
                               const TopoDS_Shape& theTool,
                               const std::string& theOperation)
{
  if (theOperation == "fuse")
  {
    BRepAlgoAPI_Fuse anAlgorithm(theObject, theTool);
    return runBoolean(anAlgorithm, theObject, theTool);
  }
  if (theOperation == "cut")
  {
    BRepAlgoAPI_Cut anAlgorithm(theObject, theTool);
    return runBoolean(anAlgorithm, theObject, theTool);
  }
  if (theOperation == "common")
  {
    BRepAlgoAPI_Common anAlgorithm(theObject, theTool);
    return runBoolean(anAlgorithm, theObject, theTool);
  }
  throw std::invalid_argument("Unsupported Boolean operation. Expected fuse, cut or common.");
}

emscripten::val booleanProbe(const TopoDS_Shape& theObject,
                             const TopoDS_Shape& theTool,
                             const std::string& theOperation)
{
  if (theOperation == "fuse") { BRepAlgoAPI_Fuse anAlgorithm(theObject, theTool); return runBooleanProbe(anAlgorithm); }
  if (theOperation == "cut") { BRepAlgoAPI_Cut anAlgorithm(theObject, theTool); return runBooleanProbe(anAlgorithm); }
  if (theOperation == "common") { BRepAlgoAPI_Common anAlgorithm(theObject, theTool); return runBooleanProbe(anAlgorithm); }
  throw std::invalid_argument("Unsupported Boolean operation. Expected fuse, cut or common.");
}

TopoDS_Shape booleanResult(const TopoDS_Shape& theObject,
                           const TopoDS_Shape& theTool,
                           const std::string& theOperation)
{
  if (theOperation == "fuse") { BRepAlgoAPI_Fuse anAlgorithm(theObject, theTool); anAlgorithm.Build(); if (anAlgorithm.IsDone() && !anAlgorithm.HasErrors()) return anAlgorithm.Shape(); }
  else if (theOperation == "cut") { BRepAlgoAPI_Cut anAlgorithm(theObject, theTool); anAlgorithm.Build(); if (anAlgorithm.IsDone() && !anAlgorithm.HasErrors()) return anAlgorithm.Shape(); }
  else if (theOperation == "common") { BRepAlgoAPI_Common anAlgorithm(theObject, theTool); anAlgorithm.Build(); if (anAlgorithm.IsDone() && !anAlgorithm.HasErrors()) return anAlgorithm.Shape(); }
  else throw std::invalid_argument("Unsupported Boolean operation. Expected fuse, cut or common.");
  throw std::runtime_error("OCCT boolean result failed.");
}

TopoDS_Shape readStep(const std::string& theText)
{
  if (theText.empty())
  {
    throw std::invalid_argument("STEP text must not be empty.");
  }
  std::istringstream aStream(theText);
  STEPControl_Reader aReader;
  const IFSelect_ReturnStatus aStatus = aReader.ReadStream("bitbybit.step", aStream);
  if (aStatus != IFSelect_RetDone || aReader.NbRootsForTransfer() <= 0 || !aReader.TransferRoots())
  {
    throw std::runtime_error("OCCT could not read and transfer the supplied STEP text.");
  }
  const TopoDS_Shape aShape = aReader.OneShape();
  if (aShape.IsNull())
  {
    throw std::runtime_error("OCCT STEP transfer produced a null shape.");
  }
  return aShape;
}

std::string writeStep(const TopoDS_Shape& theShape)
{
  if (theShape.IsNull())
  {
    throw std::invalid_argument("Cannot serialize a null OCCT shape.");
  }
  completePCurves(theShape);
  STEPControl_Writer aWriter;
  if (aWriter.Transfer(theShape, STEPControl_AsIs) != IFSelect_RetDone)
  {
    throw std::runtime_error("OCCT could not transfer the shape to STEP.");
  }
  std::ostringstream aStream;
  if (aWriter.WriteStream(aStream) != IFSelect_RetDone)
  {
    throw std::runtime_error("OCCT could not write the shape to STEP.");
  }
  return aStream.str();
}

std::string writeBrep(const TopoDS_Shape& theShape)
{
  if (theShape.IsNull()) throw std::invalid_argument("Cannot serialize a null OCCT shape.");
  std::ostringstream aStream;
  BRepTools::Write(theShape, aStream);
  if (!aStream.good() || aStream.str().empty()) throw std::runtime_error("OCCT could not write the shape to BREP.");
  return aStream.str();
}

emscripten::val booleanHistoryFromStep(const std::string& theObjectStep,
                                       const std::string& theToolStep,
                                       const std::string& theOperation)
{
  return booleanHistory(readStep(theObjectStep), readStep(theToolStep), theOperation);
}

emscripten::val rotateHistory(const TopoDS_Shape& theShape,
                              const double theAxisOriginX,
                              const double theAxisOriginY,
                              const double theAxisOriginZ,
                              const double theAxisDirectionX,
                              const double theAxisDirectionY,
                              const double theAxisDirectionZ,
                              const double theAngleDegrees)
{
  if (theShape.IsNull()) throw std::invalid_argument("Rotate shape must not be null.");
  const gp_Vec aDirection(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ);
  if (aDirection.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Rotate axis direction must be non-zero.");
  if (!std::isfinite(theAngleDegrees) || theAngleDegrees == 0.0 || std::abs(theAngleDegrees) > 360.0) throw std::invalid_argument("Rotate angle must be non-zero and within 360 degrees.");
  constexpr double aPi = 3.14159265358979323846;
  gp_Trsf aTransform;
  aTransform.SetRotation(
    gp_Ax1(gp_Pnt(theAxisOriginX, theAxisOriginY, theAxisOriginZ), gp_Dir(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ)),
    theAngleDegrees * aPi / 180.0);
  BRepBuilderAPI_Transform anAlgorithm(theShape, aTransform, false, false);
  if (!anAlgorithm.IsDone()) throw std::runtime_error("OCCT rotation failed while collecting native history.");
  const TopoDS_Shape aResult = anAlgorithm.Shape();
  emscripten::val aRecords = emscripten::val::array();
  const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE};
  unsigned int aRecordIndex = 0;
  bool hasDeleted = false;
  for (const TopAbs_ShapeEnum aKind : aKinds)
  {
    const auto aSubShapes = subShapes(theShape, aKind);
    for (std::size_t aSourceIndex = 0; aSourceIndex < aSubShapes.size(); ++aSourceIndex)
    {
      const TopoDS_Shape& anInput = aSubShapes[aSourceIndex];
      const auto& aModified = anAlgorithm.Modified(anInput);
      for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aModified); anIterator.More(); anIterator.Next())
      {
        const int anOutputIndex = resultIndex(anIterator.Value(), aResult, aKind);
        if (anOutputIndex < 0) continue;
        emscripten::val aRecord = emscripten::val::object();
        aRecord.set("relation", "modified"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
        aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", anOutputIndex);
        aRecords.set(aRecordIndex++, aRecord);
      }
      if (anAlgorithm.IsDeleted(anInput))
      {
        emscripten::val aRecord = emscripten::val::object();
        aRecord.set("relation", "deleted"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
        aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", -1);
        aRecords.set(aRecordIndex++, aRecord); hasDeleted = true;
      }
    }
  }
  if (aRecordIndex == 0) throw std::runtime_error("OCCT rotation produced no verifiable shape history.");
  emscripten::val aResponse = emscripten::val::object();
  aResponse.set("provider", "occt-native"); aResponse.set("occtVersion", OCC_VERSION_COMPLETE);
  aResponse.set("result", aResult); aResponse.set("resultStep", writeStep(aResult)); aResponse.set("resultBrep", writeBrep(aResult)); aResponse.set("records", aRecords);
  aResponse.set("hasModified", true); aResponse.set("hasGenerated", false); aResponse.set("hasDeleted", hasDeleted);
  aResponse.set("summary", shapeSummary(aResult));
  return aResponse;
}

emscripten::val rotateHistoryFromStep(const std::string& theShapeStep,
                                      const double theAxisOriginX,
                                      const double theAxisOriginY,
                                      const double theAxisOriginZ,
                                      const double theAxisDirectionX,
                                      const double theAxisDirectionY,
                                      const double theAxisDirectionZ,
                                      const double theAngleDegrees)
{
  return rotateHistory(readStep(theShapeStep), theAxisOriginX, theAxisOriginY, theAxisOriginZ, theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ, theAngleDegrees);
}

emscripten::val prismHistory(const TopoDS_Shape& theProfile,
                             const double theDx,
                             const double theDy,
                             const double theDz)
{
  if (theProfile.IsNull()) throw std::invalid_argument("Prism profile must not be null.");
  const gp_Vec aVector(theDx, theDy, theDz);
  if (aVector.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Prism direction must be non-zero.");
  BRepPrimAPI_MakePrism anAlgorithm(theProfile, aVector, false, true);
  anAlgorithm.Build();
  if (!anAlgorithm.IsDone()) throw std::runtime_error("OCCT prism failed while collecting native history.");
  const TopoDS_Shape aResult = anAlgorithm.Shape();
  emscripten::val aRecords = emscripten::val::array();
  const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE};
  unsigned int aRecordIndex = 0;
  bool hasModified = false;
  bool hasGenerated = false;
  bool hasDeleted = false;
  for (const TopAbs_ShapeEnum aKind : aKinds)
  {
    const auto aSubShapes = subShapes(theProfile, aKind);
    for (std::size_t aSourceIndex = 0; aSourceIndex < aSubShapes.size(); ++aSourceIndex)
    {
      const TopoDS_Shape& anInput = aSubShapes[aSourceIndex];
      const TopoDS_Shape aFirst = anAlgorithm.FirstShape(anInput);
      const int aFirstIndex = resultIndex(aFirst, aResult, aKind);
      if (aFirstIndex >= 0)
      {
        emscripten::val aRecord = emscripten::val::object();
        aRecord.set("relation", "modified"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
        aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", aFirstIndex);
        aRecords.set(aRecordIndex++, aRecord);
        hasModified = true;
      }
      const auto& aGenerated = anAlgorithm.Generated(anInput);
      const TopAbs_ShapeEnum aGeneratedKind = aKind == TopAbs_VERTEX ? TopAbs_EDGE : aKind == TopAbs_EDGE ? TopAbs_FACE : TopAbs_SOLID;
      if (aGeneratedKind != TopAbs_SOLID) for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aGenerated); anIterator.More(); anIterator.Next())
      {
        const int anOutputIndex = resultIndex(anIterator.Value(), aResult, aGeneratedKind);
        if (anOutputIndex < 0) continue;
        emscripten::val aRecord = emscripten::val::object();
        aRecord.set("relation", "generated"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
        aRecord.set("resultKind", kindName(aGeneratedKind)); aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", anOutputIndex);
        aRecords.set(aRecordIndex++, aRecord);
        hasGenerated = true;
      }
      if (anAlgorithm.IsDeleted(anInput))
      {
        emscripten::val aRecord = emscripten::val::object();
        aRecord.set("relation", "deleted"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
        aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", -1);
        aRecords.set(aRecordIndex++, aRecord);
        hasDeleted = true;
      }
    }
  }
  emscripten::val aResponse = emscripten::val::object();
  aResponse.set("provider", "occt-native"); aResponse.set("occtVersion", OCC_VERSION_COMPLETE);
  aResponse.set("result", aResult); aResponse.set("resultStep", writeStep(aResult)); aResponse.set("resultBrep", writeBrep(aResult)); aResponse.set("records", aRecords);
  aResponse.set("hasModified", hasModified); aResponse.set("hasGenerated", hasGenerated); aResponse.set("hasDeleted", hasDeleted);
  aResponse.set("summary", shapeSummary(aResult));
  return aResponse;
}

emscripten::val prismHistoryFromStep(const std::string& theProfileStep,
                                     const double theDx,
                                     const double theDy,
                                     const double theDz)
{
  return prismHistory(readStep(theProfileStep), theDx, theDy, theDz);
}

TopoDS_Wire loftProfileWire(const TopoDS_Shape& theShape)
{
  if (theShape.IsNull()) throw std::invalid_argument("Loft profile must not be null.");
  if (theShape.ShapeType() == TopAbs_WIRE) return TopoDS::Wire(theShape);
  if (theShape.ShapeType() == TopAbs_FACE) return BRepTools::OuterWire(TopoDS::Face(theShape));
  TopoDS_Wire aBest;
  double aBestArea = -1.0;
  for (TopExp_Explorer anExplorer(theShape, TopAbs_FACE); anExplorer.More(); anExplorer.Next())
  {
    const TopoDS_Face aFace = TopoDS::Face(anExplorer.Current());
    GProp_GProps aProperties;
    BRepGProp::SurfaceProperties(aFace, aProperties);
    if (aProperties.Mass() > aBestArea) { aBestArea = aProperties.Mass(); aBest = BRepTools::OuterWire(aFace); }
  }
  if (!aBest.IsNull()) return aBest;
  for (TopExp_Explorer anExplorer(theShape, TopAbs_WIRE); anExplorer.More(); anExplorer.Next()) return TopoDS::Wire(anExplorer.Current());
  throw std::invalid_argument("Loft profile must contain a face or wire.");
}

emscripten::val loftHistoryFromStep(const std::string& theFirstStep,
                                    const std::string& theSecondStep,
                                    const bool theRuled)
{
  const TopoDS_Shape aFirstShape = readStep(theFirstStep);
  const TopoDS_Shape aSecondShape = readStep(theSecondStep);
  const TopoDS_Wire aFirstWire = loftProfileWire(aFirstShape);
  const TopoDS_Wire aSecondWire = loftProfileWire(aSecondShape);
  BRepOffsetAPI_ThruSections anAlgorithm(true, theRuled);
  anAlgorithm.SetMutableInput(false);
  anAlgorithm.AddWire(aFirstWire);
  anAlgorithm.AddWire(aSecondWire);
  anAlgorithm.Build();
  if (!anAlgorithm.IsDone()) throw std::runtime_error("OCCT loft failed while collecting native history.");
  const TopoDS_Shape aResult = anAlgorithm.Shape();
  emscripten::val aRecords = emscripten::val::array();
  unsigned int aRecordIndex = 0;
  bool hasModified = false;
  bool hasGenerated = false;
  bool hasDeleted = false;
  const TopoDS_Wire aWires[] = {aFirstWire, aSecondWire};
  const char* aSources[] = {"object", "tool"};
  const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE};
  for (unsigned int aWireIndex = 0; aWireIndex < 2; ++aWireIndex)
  {
    for (const TopAbs_ShapeEnum aKind : aKinds)
    {
      const auto aSubShapes = subShapes(aWires[aWireIndex], aKind);
      for (std::size_t aSourceIndex = 0; aSourceIndex < aSubShapes.size(); ++aSourceIndex)
      {
        const TopoDS_Shape& anInput = aSubShapes[aSourceIndex];
        const auto& aGenerated = anAlgorithm.Generated(anInput);
        for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aGenerated); anIterator.More(); anIterator.Next())
        {
          const TopoDS_Shape& anOutput = anIterator.Value();
          const int anOutputIndex = resultIndex(anOutput, aResult, anOutput.ShapeType());
          if (anOutputIndex < 0) continue;
          emscripten::val aRecord = emscripten::val::object();
          aRecord.set("relation", "generated"); aRecord.set("source", aSources[aWireIndex]); aRecord.set("kind", kindName(aKind)); aRecord.set("resultKind", kindName(anOutput.ShapeType()));
          aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", anOutputIndex); aRecords.set(aRecordIndex++, aRecord); hasGenerated = true;
        }
        if (anAlgorithm.IsDeleted(anInput))
        {
          emscripten::val aRecord = emscripten::val::object(); aRecord.set("relation", "deleted"); aRecord.set("source", aSources[aWireIndex]); aRecord.set("kind", kindName(aKind)); aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", -1); aRecords.set(aRecordIndex++, aRecord); hasDeleted = true;
        }
      }
    }
  }
  if (aRecordIndex == 0) throw std::runtime_error("OCCT loft produced no verifiable profile history.");
  emscripten::val aResponse = emscripten::val::object();
  aResponse.set("provider", "occt-native"); aResponse.set("occtVersion", OCC_VERSION_COMPLETE); aResponse.set("result", aResult); aResponse.set("resultStep", writeStep(aResult)); aResponse.set("resultBrep", writeBrep(aResult)); aResponse.set("records", aRecords);
  aResponse.set("hasModified", hasModified); aResponse.set("hasGenerated", hasGenerated); aResponse.set("hasDeleted", hasDeleted); aResponse.set("summary", shapeSummary(aResult));
  return aResponse;
}

TopoDS_Face pipeProfileFace(const TopoDS_Shape& theShape)
{
  if (theShape.IsNull()) throw std::invalid_argument("Pipe profile must not be null.");
  if (theShape.ShapeType() == TopAbs_FACE) return TopoDS::Face(theShape);
  TopoDS_Face aBest;
  double aBestArea = -1.0;
  for (TopExp_Explorer anExplorer(theShape, TopAbs_FACE); anExplorer.More(); anExplorer.Next())
  {
    const TopoDS_Face aFace = TopoDS::Face(anExplorer.Current());
    GProp_GProps aProperties;
    BRepGProp::SurfaceProperties(aFace, aProperties);
    if (aProperties.Mass() > aBestArea) { aBestArea = aProperties.Mass(); aBest = aFace; }
  }
  if (aBest.IsNull()) throw std::invalid_argument("Pipe profile must contain a face.");
  return aBest;
}

TopoDS_Wire pipeSpineWire(const TopoDS_Shape& theShape)
{
  if (theShape.IsNull()) throw std::invalid_argument("Pipe spine must not be null.");
  if (theShape.ShapeType() == TopAbs_WIRE) return TopoDS::Wire(theShape);
  for (TopExp_Explorer anExplorer(theShape, TopAbs_WIRE); anExplorer.More(); anExplorer.Next()) return TopoDS::Wire(anExplorer.Current());
  BRepBuilderAPI_MakeWire aWire;
  for (TopExp_Explorer anExplorer(theShape, TopAbs_EDGE); anExplorer.More(); anExplorer.Next()) aWire.Add(TopoDS::Edge(anExplorer.Current()));
  if (!aWire.IsDone()) throw std::invalid_argument("Pipe spine must contain a connected wire.");
  return aWire.Wire();
}

emscripten::val pipeHistoryFromStep(const std::string& theProfileStep,
                                    const std::string& theSpineStep)
{
  const TopoDS_Face aProfile = pipeProfileFace(readStep(theProfileStep));
  const TopoDS_Wire aSpine = pipeSpineWire(readStep(theSpineStep));
  const auto aSpineEdges = subShapes(aSpine, TopAbs_EDGE);
  const auto aSpineVertices = subShapes(aSpine, TopAbs_VERTEX);
  if (aSpineEdges.size() != 1 || aSpineVertices.size() != 2) throw std::invalid_argument("Native Pipe history currently requires a single-edge open spine.");

  BRepOffsetAPI_MakePipe anAlgorithm(aSpine, aProfile);
  anAlgorithm.Build();
  if (!anAlgorithm.IsDone()) throw std::runtime_error("OCCT pipe failed while collecting native history.");
  const TopoDS_Shape aResult = anAlgorithm.Shape();
  emscripten::val aRecords = emscripten::val::array();
  unsigned int aRecordIndex = 0;
  bool hasModified = false;
  bool hasGenerated = false;

  const auto appendRecord = [&](const char* theRelation,
                                const char* theSource,
                                const TopAbs_ShapeEnum theSourceKind,
                                const int theSourceIndex,
                                const TopoDS_Shape& theOutput)
  {
    if (theOutput.IsNull()) return;
    const int anOutputIndex = resultIndex(theOutput, aResult, theOutput.ShapeType());
    if (anOutputIndex < 0) return;
    emscripten::val aRecord = emscripten::val::object();
    aRecord.set("relation", theRelation); aRecord.set("source", theSource); aRecord.set("kind", kindName(theSourceKind));
    aRecord.set("resultKind", kindName(theOutput.ShapeType())); aRecord.set("sourceIndex", theSourceIndex); aRecord.set("resultIndex", anOutputIndex);
    aRecords.set(aRecordIndex++, aRecord);
    hasModified = hasModified || std::string(theRelation) == "modified";
    hasGenerated = hasGenerated || std::string(theRelation) == "generated";
  };

  const TopAbs_ShapeEnum aProfileKinds[] = {TopAbs_VERTEX, TopAbs_EDGE};
  for (const TopAbs_ShapeEnum aKind : aProfileKinds)
  {
    const auto aSources = subShapes(aProfile, aKind);
    for (std::size_t aSourceIndex = 0; aSourceIndex < aSources.size(); ++aSourceIndex)
    {
      const auto& aGenerated = anAlgorithm.Generated(aSources[aSourceIndex]);
      for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aGenerated); anIterator.More(); anIterator.Next())
      {
        appendRecord("generated", "object", aKind, static_cast<int>(aSourceIndex), anIterator.Value());
      }
    }
  }
  appendRecord("modified", "object", TopAbs_FACE, 0, anAlgorithm.FirstShape());
  appendRecord("generated", "object", TopAbs_FACE, 0, anAlgorithm.LastShape());

  const auto& aSpineEdge = aSpineEdges[0];
  for (const TopAbs_ShapeEnum aKind : aProfileKinds)
  {
    const auto aProfileSources = subShapes(aProfile, aKind);
    for (const TopoDS_Shape& aProfileSource : aProfileSources)
    {
      appendRecord("generated", "tool", TopAbs_EDGE, 0, anAlgorithm.Generated(aSpineEdge, aProfileSource));
    }
  }
  appendRecord("generated", "tool", TopAbs_VERTEX, 0, anAlgorithm.FirstShape());
  appendRecord("generated", "tool", TopAbs_VERTEX, 1, anAlgorithm.LastShape());
  if (aRecordIndex == 0) throw std::runtime_error("OCCT pipe produced no verifiable profile or spine history.");

  emscripten::val aResponse = emscripten::val::object();
  aResponse.set("provider", "occt-native"); aResponse.set("occtVersion", OCC_VERSION_COMPLETE); aResponse.set("result", aResult); aResponse.set("resultStep", writeStep(aResult)); aResponse.set("resultBrep", writeBrep(aResult)); aResponse.set("records", aRecords);
  aResponse.set("hasModified", hasModified); aResponse.set("hasGenerated", hasGenerated); aResponse.set("hasDeleted", false); aResponse.set("summary", shapeSummary(aResult));
  return aResponse;
}


emscripten::val pocketHistory(const TopoDS_Shape& theBase,
                              const TopoDS_Shape& theProfile,
                              const double theDx,
                              const double theDy,
                              const double theDz)
{
  if (theBase.IsNull()) throw std::invalid_argument("Pocket base must not be null.");
  if (theProfile.IsNull()) throw std::invalid_argument("Pocket profile must not be null.");
  const gp_Vec aVector(theDx, theDy, theDz);
  if (aVector.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Pocket direction must be non-zero.");
  BRepPrimAPI_MakePrism aPrism(theProfile, aVector, false, true);
  aPrism.Build();
  if (!aPrism.IsDone()) throw std::runtime_error("OCCT pocket profile extrusion failed while collecting native history.");
  TopoDS_Shape aTool = aPrism.Shape();
  BRepAlgoAPI_Cut anAlgorithm(theBase, aTool);
  return runBoolean(anAlgorithm, theBase, aTool);
}

emscripten::val pocketHistoryFromStep(const std::string& theBaseStep,
                                      const std::string& theProfileStep,
                                      const double theDx,
                                      const double theDy,
                                      const double theDz)
{
  return pocketHistory(readStep(theBaseStep), readStep(theProfileStep), theDx, theDy, theDz);
}

emscripten::val revolutionHistory(const TopoDS_Shape& theProfile,
                                  const double theAxisOriginX,
                                  const double theAxisOriginY,
                                  const double theAxisOriginZ,
                                  const double theAxisDirectionX,
                                  const double theAxisDirectionY,
                                  const double theAxisDirectionZ,
                                  const double theAngleDegrees)
{
  if (theProfile.IsNull()) throw std::invalid_argument("Revolution profile must not be null.");
  const gp_Vec aDirection(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ);
  if (aDirection.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Revolution axis direction must be non-zero.");
  if (!std::isfinite(theAngleDegrees) || theAngleDegrees <= 0.0 || theAngleDegrees > 360.0) throw std::invalid_argument("Revolution angle must be in (0, 360] degrees.");
  const gp_Ax1 anAxis(gp_Pnt(theAxisOriginX, theAxisOriginY, theAxisOriginZ), gp_Dir(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ));
  constexpr double aPi = 3.14159265358979323846;
  BRepPrimAPI_MakeRevol anAlgorithm(theProfile, anAxis, theAngleDegrees * aPi / 180.0, false);
  anAlgorithm.Build();
  if (!anAlgorithm.IsDone()) throw std::runtime_error("OCCT revolution failed while collecting native history.");
  const TopoDS_Shape aResult = anAlgorithm.Shape();
  emscripten::val aRecords = emscripten::val::array();
  const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE};
  unsigned int aRecordIndex = 0;
  bool hasModified = false;
  bool hasGenerated = false;
  bool hasDeleted = false;
  for (const TopAbs_ShapeEnum aKind : aKinds)
  {
    const auto aSubShapes = subShapes(theProfile, aKind);
    for (std::size_t aSourceIndex = 0; aSourceIndex < aSubShapes.size(); ++aSourceIndex)
    {
      const TopoDS_Shape& anInput = aSubShapes[aSourceIndex];
      const TopoDS_Shape aFirst = anAlgorithm.FirstShape(anInput);
      const int aFirstIndex = resultIndex(aFirst, aResult, aKind);
      if (aFirstIndex >= 0)
      {
        emscripten::val aRecord = emscripten::val::object();
        aRecord.set("relation", "modified"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
        aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", aFirstIndex);
        aRecords.set(aRecordIndex++, aRecord); hasModified = true;
      }
      const auto& aGenerated = anAlgorithm.Generated(anInput);
      const TopAbs_ShapeEnum aGeneratedKind = aKind == TopAbs_VERTEX ? TopAbs_EDGE : aKind == TopAbs_EDGE ? TopAbs_FACE : TopAbs_SOLID;
      if (aGeneratedKind != TopAbs_SOLID) for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aGenerated); anIterator.More(); anIterator.Next())
      {
        const int anOutputIndex = resultIndex(anIterator.Value(), aResult, aGeneratedKind);
        if (anOutputIndex < 0) continue;
        emscripten::val aRecord = emscripten::val::object();
        aRecord.set("relation", "generated"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
        aRecord.set("resultKind", kindName(aGeneratedKind)); aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", anOutputIndex);
        aRecords.set(aRecordIndex++, aRecord); hasGenerated = true;
      }
      if (anAlgorithm.IsDeleted(anInput))
      {
        emscripten::val aRecord = emscripten::val::object();
        aRecord.set("relation", "deleted"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
        aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", -1);
        aRecords.set(aRecordIndex++, aRecord); hasDeleted = true;
      }
    }
  }
  emscripten::val aResponse = emscripten::val::object();
  aResponse.set("provider", "occt-native"); aResponse.set("occtVersion", OCC_VERSION_COMPLETE);
  aResponse.set("result", aResult); aResponse.set("resultStep", writeStep(aResult)); aResponse.set("resultBrep", writeBrep(aResult)); aResponse.set("records", aRecords);
  aResponse.set("hasModified", hasModified); aResponse.set("hasGenerated", hasGenerated); aResponse.set("hasDeleted", hasDeleted);
  aResponse.set("summary", shapeSummary(aResult));
  return aResponse;
}

emscripten::val revolutionHistoryFromStep(const std::string& theProfileStep,
                                          const double theAxisOriginX,
                                          const double theAxisOriginY,
                                          const double theAxisOriginZ,
                                          const double theAxisDirectionX,
                                          const double theAxisDirectionY,
                                          const double theAxisDirectionZ,
                                          const double theAngleDegrees)
{
  return revolutionHistory(readStep(theProfileStep), theAxisOriginX, theAxisOriginY, theAxisOriginZ, theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ, theAngleDegrees);
}

emscripten::val grooveHistory(const TopoDS_Shape& theBase,
                              const TopoDS_Shape& theProfile,
                              const double theAxisOriginX,
                              const double theAxisOriginY,
                              const double theAxisOriginZ,
                              const double theAxisDirectionX,
                              const double theAxisDirectionY,
                              const double theAxisDirectionZ,
                              const double theAngleDegrees)
{
  if (theBase.IsNull()) throw std::invalid_argument("Groove base must not be null.");
  if (theProfile.IsNull()) throw std::invalid_argument("Groove profile must not be null.");
  const gp_Vec aDirection(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ);
  if (aDirection.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Groove axis direction must be non-zero.");
  if (!std::isfinite(theAngleDegrees) || theAngleDegrees <= 0.0 || theAngleDegrees > 360.0) throw std::invalid_argument("Groove angle must be in (0, 360] degrees.");
  const gp_Ax1 anAxis(gp_Pnt(theAxisOriginX, theAxisOriginY, theAxisOriginZ), gp_Dir(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ));
  constexpr double aPi = 3.14159265358979323846;
  BRepPrimAPI_MakeRevol aRevolution(theProfile, anAxis, theAngleDegrees * aPi / 180.0, false);
  aRevolution.Build();
  if (!aRevolution.IsDone()) throw std::runtime_error("OCCT groove profile revolution failed while collecting native history.");
  TopoDS_Shape aTool = aRevolution.Shape();
  BRepAlgoAPI_Cut anAlgorithm(theBase, aTool);
  return runBoolean(anAlgorithm, theBase, aTool);
}

emscripten::val grooveHistoryFromStep(const std::string& theBaseStep,
                                      const std::string& theProfileStep,
                                      const double theAxisOriginX,
                                      const double theAxisOriginY,
                                      const double theAxisOriginZ,
                                      const double theAxisDirectionX,
                                      const double theAxisDirectionY,
                                      const double theAxisDirectionZ,
                                      const double theAngleDegrees)
{
  return grooveHistory(readStep(theBaseStep), readStep(theProfileStep), theAxisOriginX, theAxisOriginY, theAxisOriginZ, theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ, theAngleDegrees);
}

emscripten::val filletHistoryFromStep(const std::string& theBaseStep,
                                      const double theRadius)
{
  if (!std::isfinite(theRadius) || theRadius <= 0.0) throw std::invalid_argument("Fillet radius must be finite and positive.");
  const TopoDS_Shape aBase = readStep(theBaseStep);
  BRepFilletAPI_MakeFillet anAlgorithm(aBase);
  for (TopExp_Explorer anExplorer(aBase, TopAbs_EDGE); anExplorer.More(); anExplorer.Next())
  {
    anAlgorithm.Add(theRadius, TopoDS::Edge(anExplorer.Current()));
  }
  return filletHistory(anAlgorithm, aBase);
}

emscripten::val chamferHistoryFromStep(const std::string& theBaseStep,
                                       const double theDistance)
{
  if (!std::isfinite(theDistance) || theDistance <= 0.0) throw std::invalid_argument("Chamfer distance must be finite and positive.");
  const TopoDS_Shape aBase = readStep(theBaseStep);
  BRepFilletAPI_MakeChamfer anAlgorithm(aBase);
  for (TopExp_Explorer anExplorer(aBase, TopAbs_EDGE); anExplorer.More(); anExplorer.Next())
  {
    anAlgorithm.Add(theDistance, TopoDS::Edge(anExplorer.Current()));
  }
  return chamferHistory(anAlgorithm, aBase);
}

emscripten::val draftHistoryFromStep(const std::string& theBaseStep,
                                     const int theFaceIndex,
                                     const double theAngle,
                                     const double theDirectionX,
                                     const double theDirectionY,
                                     const double theDirectionZ,
                                     const double theNeutralPlaneOriginX,
                                     const double theNeutralPlaneOriginY,
                                     const double theNeutralPlaneOriginZ,
                                     const double theNeutralPlaneDirectionX,
                                     const double theNeutralPlaneDirectionY,
                                     const double theNeutralPlaneDirectionZ,
                                     const bool theReversed)
{
  if (!std::isfinite(theAngle) || theAngle == 0.0 || theAngle <= -89.999 || theAngle >= 89.999) throw std::invalid_argument("Draft angle must be finite, non-zero and between -89.999 and 89.999 degrees.");
  const gp_Vec aDirection(theDirectionX, theDirectionY, theDirectionZ);
  const gp_Vec aNeutralDirection(theNeutralPlaneDirectionX, theNeutralPlaneDirectionY, theNeutralPlaneDirectionZ);
  if (aDirection.Magnitude() <= gp::Resolution() || aNeutralDirection.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Draft directions must be non-zero.");
  const TopoDS_Shape aBase = readStep(theBaseStep);
  const auto aFaces = subShapes(aBase, TopAbs_FACE);
  if (theFaceIndex < 0 || static_cast<std::size_t>(theFaceIndex) >= aFaces.size()) throw std::out_of_range("Draft face index is outside the base face list.");
  BRepOffsetAPI_DraftAngle anAlgorithm(aBase);
  const gp_Pln aNeutralPlane(gp_Pnt(theNeutralPlaneOriginX, theNeutralPlaneOriginY, theNeutralPlaneOriginZ), gp_Dir(theNeutralPlaneDirectionX, theNeutralPlaneDirectionY, theNeutralPlaneDirectionZ));
  constexpr double aPi = 3.14159265358979323846;
  anAlgorithm.Add(TopoDS::Face(aFaces[static_cast<std::size_t>(theFaceIndex)]), gp_Dir(theDirectionX, theDirectionY, theDirectionZ), theAngle * aPi / 180.0, aNeutralPlane, !theReversed);
  if (!anAlgorithm.AddDone()) throw std::runtime_error("OCCT draft failed while collecting native history.");
  return runDressupHistory(anAlgorithm, aBase, "draft");
}

emscripten::val thicknessHistoryFromStep(const std::string& theBaseStep,
                                         const int theFaceIndex,
                                         const double theOffset,
                                         const bool theIntersectionJoin)
{
  if (!std::isfinite(theOffset) || theOffset == 0.0) throw std::invalid_argument("Thickness offset must be finite and non-zero.");
  const TopoDS_Shape aBase = readStep(theBaseStep);
  const auto aFaces = subShapes(aBase, TopAbs_FACE);
  if (theFaceIndex < 0 || static_cast<std::size_t>(theFaceIndex) >= aFaces.size()) throw std::out_of_range("Thickness face index is outside the base face list.");
  NCollection_List<TopoDS_Shape> aClosingFaces;
  aClosingFaces.Append(aFaces[static_cast<std::size_t>(theFaceIndex)]);
  BRepOffsetAPI_MakeThickSolid anAlgorithm;
  anAlgorithm.MakeThickSolidByJoin(aBase,
                                   aClosingFaces,
                                   theOffset,
                                   1.0e-3,
                                   BRepOffset_Skin,
                                   theIntersectionJoin,
                                   false,
                                   theIntersectionJoin ? GeomAbs_Intersection : GeomAbs_Arc,
                                   false);
  if (!anAlgorithm.IsDone()) throw std::runtime_error("OCCT thickness failed while collecting native history.");
  return runDressupHistory(anAlgorithm, aBase, "thickness");
}

emscripten::val linearPatternHistoryFromStep(const std::string& theBaseStep,
                                             const double theDx,
                                             const double theDy,
                                             const double theDz)
{
  const gp_Vec aTranslation(theDx, theDy, theDz);
  if (aTranslation.Magnitude() <= gp::Resolution()) throw std::invalid_argument("LinearPattern translation must be non-zero.");
  const TopoDS_Shape aBase = readStep(theBaseStep);
  gp_Trsf aTransform;
  aTransform.SetTranslation(aTranslation);
  const TopoDS_Shape aCopy = BRepBuilderAPI_Transform(aBase, aTransform, true).Shape();
  BRepAlgoAPI_Fuse anAlgorithm(aBase, aCopy);
  return runBoolean(anAlgorithm, aBase, aCopy);
}

emscripten::val polarPatternHistoryFromStep(const std::string& theBaseStep,
                                            const double theAxisOriginX,
                                            const double theAxisOriginY,
                                            const double theAxisOriginZ,
                                            const double theAxisDirectionX,
                                            const double theAxisDirectionY,
                                            const double theAxisDirectionZ,
                                            const double theAngleDegrees)
{
  const gp_Vec anAxisDirection(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ);
  if (anAxisDirection.Magnitude() <= gp::Resolution()) throw std::invalid_argument("PolarPattern axis direction must be non-zero.");
  if (!std::isfinite(theAngleDegrees) || theAngleDegrees == 0.0 || std::abs(theAngleDegrees) > 360.0) throw std::invalid_argument("PolarPattern angle must be non-zero and within 360 degrees.");
  const TopoDS_Shape aBase = readStep(theBaseStep);
  constexpr double aPi = 3.14159265358979323846;
  gp_Trsf aTransform;
  aTransform.SetRotation(gp_Ax1(gp_Pnt(theAxisOriginX, theAxisOriginY, theAxisOriginZ), gp_Dir(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ)), theAngleDegrees * aPi / 180.0);
  const TopoDS_Shape aCopy = BRepBuilderAPI_Transform(aBase, aTransform, true).Shape();
  BRepAlgoAPI_Fuse anAlgorithm(aBase, aCopy);
  return runBoolean(anAlgorithm, aBase, aCopy);
}

emscripten::val mirroredHistoryFromStep(const std::string& theBaseStep,
                                        const double thePlaneOriginX,
                                        const double thePlaneOriginY,
                                        const double thePlaneOriginZ,
                                        const double thePlaneNormalX,
                                        const double thePlaneNormalY,
                                        const double thePlaneNormalZ)
{
  const gp_Vec aPlaneNormal(thePlaneNormalX, thePlaneNormalY, thePlaneNormalZ);
  if (aPlaneNormal.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Mirrored plane normal must be non-zero.");
  const TopoDS_Shape aBase = readStep(theBaseStep);
  gp_Trsf aTransform;
  aTransform.SetMirror(gp_Ax2(gp_Pnt(thePlaneOriginX, thePlaneOriginY, thePlaneOriginZ), gp_Dir(thePlaneNormalX, thePlaneNormalY, thePlaneNormalZ)));
  const TopoDS_Shape aCopy = BRepBuilderAPI_Transform(aBase, aTransform, true).Shape();
  BRepAlgoAPI_Fuse anAlgorithm(aBase, aCopy);
  return runBoolean(anAlgorithm, aBase, aCopy);
}

struct TrackedTransformInstance
{
  TopoDS_Shape shape;
  std::array<std::vector<TopoDS_Shape>, 3> sourceShapes;
};

double finiteArrayValue(const emscripten::val& theArray, const unsigned int theIndex, const char* theLabel)
{
  if (!emscripten::val::global("Array").call<bool>("isArray", theArray) || theArray["length"].as<unsigned int>() != 3) throw std::invalid_argument(std::string(theLabel) + " must contain three numbers.");
  const double aValue = theArray[theIndex].as<double>();
  if (!std::isfinite(aValue)) throw std::invalid_argument(std::string(theLabel) + " must contain finite numbers.");
  return aValue;
}

emscripten::val multiTransformHistoryFromStep(const std::string& theBaseStep,
                                              const emscripten::val& theSteps)
{
  if (!emscripten::val::global("Array").call<bool>("isArray", theSteps)) throw std::invalid_argument("MultiTransform steps must be an array.");
  const unsigned int aStepCount = theSteps["length"].as<unsigned int>();
  if (aStepCount < 2 || aStepCount > 6) throw std::invalid_argument("Native ordered MultiTransform requires between two and six steps.");
  const TopoDS_Shape aBase = readStep(theBaseStep);
  TrackedTransformInstance aBaseInstance;
  aBaseInstance.shape = aBase;
  const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE};
  for (std::size_t aKindIndex = 0; aKindIndex < 3; ++aKindIndex) aBaseInstance.sourceShapes[aKindIndex] = subShapes(aBase, aKinds[aKindIndex]);
  std::vector<TrackedTransformInstance> anInstances = {aBaseInstance};

  for (unsigned int aStepIndex = 0; aStepIndex < aStepCount; ++aStepIndex)
  {
    const emscripten::val aStep = theSteps[aStepIndex];
    const std::string aType = aStep["type"].as<std::string>();
    gp_Trsf aTransform;
    if (aType == "linear")
    {
      const emscripten::val aDirection = aStep["direction"];
      const gp_Vec aTranslation(finiteArrayValue(aDirection, 0, "Linear direction"), finiteArrayValue(aDirection, 1, "Linear direction"), finiteArrayValue(aDirection, 2, "Linear direction"));
      if (aTranslation.Magnitude() <= gp::Resolution()) throw std::invalid_argument("MultiTransform linear translation must be non-zero.");
      aTransform.SetTranslation(aTranslation);
    }
    else if (aType == "polar")
    {
      const emscripten::val anOrigin = aStep["axisOrigin"];
      const emscripten::val aDirection = aStep["direction"];
      const gp_Vec anAxis(finiteArrayValue(aDirection, 0, "Polar direction"), finiteArrayValue(aDirection, 1, "Polar direction"), finiteArrayValue(aDirection, 2, "Polar direction"));
      const double anAngle = aStep["angle"].as<double>();
      if (anAxis.Magnitude() <= gp::Resolution() || !std::isfinite(anAngle) || anAngle == 0.0 || std::abs(anAngle) > 360.0) throw std::invalid_argument("MultiTransform polar step requires a finite axis and angle.");
      constexpr double aPi = 3.14159265358979323846;
      aTransform.SetRotation(gp_Ax1(gp_Pnt(finiteArrayValue(anOrigin, 0, "Polar origin"), finiteArrayValue(anOrigin, 1, "Polar origin"), finiteArrayValue(anOrigin, 2, "Polar origin")), gp_Dir(anAxis)), anAngle * aPi / 180.0);
    }
    else if (aType == "mirrored")
    {
      const emscripten::val anOrigin = aStep["axisOrigin"];
      const emscripten::val aDirection = aStep["direction"];
      const gp_Vec aNormal(finiteArrayValue(aDirection, 0, "Mirror normal"), finiteArrayValue(aDirection, 1, "Mirror normal"), finiteArrayValue(aDirection, 2, "Mirror normal"));
      if (aNormal.Magnitude() <= gp::Resolution()) throw std::invalid_argument("MultiTransform mirror normal must be non-zero.");
      aTransform.SetMirror(gp_Ax2(gp_Pnt(finiteArrayValue(anOrigin, 0, "Mirror origin"), finiteArrayValue(anOrigin, 1, "Mirror origin"), finiteArrayValue(anOrigin, 2, "Mirror origin")), gp_Dir(aNormal)));
    }
    else throw std::invalid_argument("Unsupported MultiTransform step type.");

    const std::vector<TrackedTransformInstance> aParents = anInstances;
    for (const TrackedTransformInstance& aParent : aParents)
    {
      BRepBuilderAPI_Transform aTransformer(aParent.shape, aTransform, true);
      if (!aTransformer.IsDone()) throw std::runtime_error("OCCT MultiTransform instance creation failed.");
      TrackedTransformInstance aChild;
      aChild.shape = aTransformer.Shape();
      for (std::size_t aKindIndex = 0; aKindIndex < 3; ++aKindIndex)
      {
        for (const TopoDS_Shape& aParentSource : aParent.sourceShapes[aKindIndex])
        {
          const TopoDS_Shape aChildSource = aTransformer.ModifiedShape(aParentSource);
          if (aChildSource.IsNull()) throw std::runtime_error("OCCT MultiTransform lost a source subshape mapping.");
          aChild.sourceShapes[aKindIndex].push_back(aChildSource);
        }
      }
      anInstances.push_back(std::move(aChild));
    }
    if (anInstances.size() > 100) throw std::invalid_argument("MultiTransform cannot create more than 100 instances.");
  }

  NCollection_List<TopoDS_Shape> anArguments;
  NCollection_List<TopoDS_Shape> aTools;
  anArguments.Append(anInstances.front().shape);
  for (std::size_t anInstanceIndex = 1; anInstanceIndex < anInstances.size(); ++anInstanceIndex) aTools.Append(anInstances[anInstanceIndex].shape);
  BRepAlgoAPI_Fuse anAlgorithm;
  anAlgorithm.SetArguments(anArguments);
  anAlgorithm.SetTools(aTools);
  anAlgorithm.Build();
  if (!anAlgorithm.IsDone() || anAlgorithm.HasErrors()) throw std::runtime_error("OCCT ordered MultiTransform fuse failed while collecting native history.");
  const TopoDS_Shape aResult = anAlgorithm.Shape();
  emscripten::val aRecords = emscripten::val::array();
  unsigned int aRecordIndex = 0;
  bool hasModified = false;
  bool hasGenerated = false;
  bool hasDeleted = false;
  std::set<std::string> aSeen;
  const auto appendRecord = [&](const char* theRelation, const TopAbs_ShapeEnum theKind, const std::size_t theSourceIndex, const int theResultIndex) {
    const std::string aKey = std::string(theRelation) + ":" + kindName(theKind) + ":" + std::to_string(theSourceIndex) + ":" + std::to_string(theResultIndex);
    if (!aSeen.insert(aKey).second) return;
    emscripten::val aRecord = emscripten::val::object();
    aRecord.set("relation", theRelation); aRecord.set("source", "object"); aRecord.set("kind", kindName(theKind));
    aRecord.set("sourceIndex", static_cast<int>(theSourceIndex)); aRecord.set("resultIndex", theResultIndex);
    aRecords.set(aRecordIndex++, aRecord);
    hasModified = hasModified || std::string(theRelation) == "modified";
    hasGenerated = hasGenerated || std::string(theRelation) == "generated";
    hasDeleted = hasDeleted || std::string(theRelation) == "deleted";
  };
  for (std::size_t aKindIndex = 0; aKindIndex < 3; ++aKindIndex)
  {
    for (std::size_t aSourceIndex = 0; aSourceIndex < aBaseInstance.sourceShapes[aKindIndex].size(); ++aSourceIndex)
    {
      bool hasResult = false;
      bool allDeleted = true;
      for (std::size_t anInstanceIndex = 0; anInstanceIndex < anInstances.size(); ++anInstanceIndex)
      {
        const TopoDS_Shape& anInput = anInstances[anInstanceIndex].sourceShapes[aKindIndex][aSourceIndex];
        const char* aRelation = anInstanceIndex == 0 ? "modified" : "generated";
        const auto& aModified = anAlgorithm.Modified(anInput);
        for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aModified); anIterator.More(); anIterator.Next())
        {
          const int anOutputIndex = resultIndex(anIterator.Value(), aResult, aKinds[aKindIndex]);
          if (anOutputIndex >= 0) { appendRecord(aRelation, aKinds[aKindIndex], aSourceIndex, anOutputIndex); hasResult = true; }
        }
        const int aDirectIndex = resultIndex(anInput, aResult, aKinds[aKindIndex]);
        if (aDirectIndex >= 0) { appendRecord(aRelation, aKinds[aKindIndex], aSourceIndex, aDirectIndex); hasResult = true; }
        allDeleted = allDeleted && anAlgorithm.IsDeleted(anInput);
      }
      if (!hasResult && allDeleted) appendRecord("deleted", aKinds[aKindIndex], aSourceIndex, -1);
    }
  }
  if (aRecordIndex == 0) throw std::runtime_error("OCCT ordered MultiTransform produced no source history.");
  emscripten::val aResponse = emscripten::val::object();
  aResponse.set("provider", "occt-native"); aResponse.set("occtVersion", OCC_VERSION_COMPLETE); aResponse.set("result", aResult); aResponse.set("resultStep", writeStep(aResult)); aResponse.set("resultBrep", writeBrep(aResult));
  aResponse.set("records", aRecords); aResponse.set("hasModified", hasModified); aResponse.set("hasGenerated", hasGenerated); aResponse.set("hasDeleted", hasDeleted); aResponse.set("summary", shapeSummary(aResult));
  return aResponse;
}

emscripten::val holeHistoryFromStep(const std::string& theBaseStep,
                                    const double theRadius,
                                    const double theDepth,
                                    const double thePositionX,
                                    const double thePositionY,
                                    const double thePositionZ,
                                    const double theDirectionX,
                                    const double theDirectionY,
                                    const double theDirectionZ)
{
  if (!std::isfinite(theRadius) || theRadius <= 0.0) throw std::invalid_argument("Hole radius must be finite and positive.");
  if (!std::isfinite(theDepth) || theDepth <= 0.0) throw std::invalid_argument("Hole depth must be finite and positive.");
  const gp_Vec aDirection(theDirectionX, theDirectionY, theDirectionZ);
  if (aDirection.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Hole direction must be non-zero.");
  const TopoDS_Shape aBase = readStep(theBaseStep);
  const gp_Ax2 anAxis(gp_Pnt(thePositionX, thePositionY, thePositionZ), gp_Dir(theDirectionX, theDirectionY, theDirectionZ));
  const TopoDS_Shape aTool = BRepPrimAPI_MakeCylinder(anAxis, theRadius, theDepth).Shape();
  BRepAlgoAPI_Cut anAlgorithm(aBase, aTool);
  return runBoolean(anAlgorithm, aBase, aTool);
}
}

EMSCRIPTEN_BINDINGS(bitbybit_occt_history)
{
  emscripten::class_<TopoDS_Shape>("TopoDS_Shape")
    .constructor<>()
    .function("isNull", &TopoDS_Shape::IsNull)
    .function("shapeType", &TopoDS_Shape::ShapeType)
    .function("isSame", &TopoDS_Shape::IsSame);
  emscripten::function("occtVersion", &occtVersion);
  emscripten::function("makeBox", &makeBox);
  emscripten::function("makeBoxPlaced", &makeBoxPlaced);
  emscripten::function("makeCylinder", &makeCylinder);
  emscripten::function("makeSphere", &makeSphere);
  emscripten::function("makeCone", &makeCone);
  emscripten::function("makeRectangleFace", &makeRectangleFace);
  emscripten::function("makeRectangleFacePlaced", &makeRectangleFacePlaced);
  emscripten::function("makeLineWire", &makeLineWire);
  emscripten::function("shapeSummary", &shapeSummary);
  emscripten::function("booleanHistory", &booleanHistory);
  emscripten::function("booleanProbe", &booleanProbe);
  emscripten::function("booleanResult", &booleanResult);
  emscripten::function("shapeToStep", &writeStep);
  emscripten::function("booleanHistoryFromStep", &booleanHistoryFromStep);
  emscripten::function("rotateHistoryFromStep", &rotateHistoryFromStep);
  emscripten::function("prismHistory", &prismHistory);
  emscripten::function("prismHistoryFromStep", &prismHistoryFromStep);
  emscripten::function("loftHistoryFromStep", &loftHistoryFromStep);
  emscripten::function("pipeHistoryFromStep", &pipeHistoryFromStep);
  emscripten::function("pocketHistory", &pocketHistory);
  emscripten::function("pocketHistoryFromStep", &pocketHistoryFromStep);
  emscripten::function("revolutionHistory", &revolutionHistory);
  emscripten::function("revolutionHistoryFromStep", &revolutionHistoryFromStep);
  emscripten::function("grooveHistory", &grooveHistory);
  emscripten::function("grooveHistoryFromStep", &grooveHistoryFromStep);
  emscripten::function("filletHistoryFromStep", &filletHistoryFromStep);
  emscripten::function("chamferHistoryFromStep", &chamferHistoryFromStep);
  emscripten::function("draftHistoryFromStep", &draftHistoryFromStep);
  emscripten::function("thicknessHistoryFromStep", &thicknessHistoryFromStep);
  emscripten::function("linearPatternHistoryFromStep", &linearPatternHistoryFromStep);
  emscripten::function("polarPatternHistoryFromStep", &polarPatternHistoryFromStep);
  emscripten::function("mirroredHistoryFromStep", &mirroredHistoryFromStep);
  emscripten::function("multiTransformHistoryFromStep", &multiTransformHistoryFromStep);
  emscripten::function("holeHistoryFromStep", &holeHistoryFromStep);
}
