//
//  Copyright (C) 2020-2025 Greg Landrum and other RDKit contributors
//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//

#include <cstdlib>
#include <optional>
#include <ranges>

#include <catch2/catch_all.hpp>

#include <boost/noncopyable.hpp>

#include <GraphMol/RDKitBase.h>
#include <GraphMol/StereoGroup.h>
#include <GraphMol/Chirality.h>
#include <GraphMol/MolOps.h>
#include <GraphMol/new_canon.h>
#include <GraphMol/test_fixtures.h>

#include <GraphMol/FileParsers/FileParsers.h>
#include <GraphMol/FileParsers/MolFileStereochem.h>
#include <GraphMol/FileParsers/MolSupplier.h>
#include <GraphMol/SmilesParse/SmilesParse.h>
#include <GraphMol/SmilesParse/SmilesWrite.h>
#include <GraphMol/CIPLabeler/CIPLabeler.h>

using namespace RDKit;

unsigned count_wedged_bonds(const ROMol &mol) {
  unsigned nWedged = 0;
  for (const auto bond : mol.bonds()) {
    if (bond->getBondDir() != Bond::BondDir::NONE) {
      ++nWedged;
    }
  }
  return nWedged;
}

TEST_CASE("bond StereoInfo", "[unittest]") {
  SECTION("basics") {
    {
      auto mol = "CC=C(C#C)C=C"_smiles;
      REQUIRE(mol);
      auto sinfo = Chirality::detail::getStereoInfo(mol->getBondWithIdx(1));
      CHECK(sinfo.type == Chirality::StereoType::Bond_Double);
      CHECK(sinfo.centeredOn == 1);
      REQUIRE(sinfo.controllingAtoms.size() == 4);
      CHECK(sinfo.controllingAtoms[0] == 0);
      CHECK(sinfo.controllingAtoms[1] == Atom::NOATOM);
      CHECK(sinfo.controllingAtoms[2] == 3);
      CHECK(sinfo.controllingAtoms[3] == 5);
      CHECK(sinfo.specified == Chirality::StereoSpecified::Unspecified);
      CHECK(sinfo.descriptor == Chirality::StereoDescriptor::None);
    }
    {
      auto mol = "CC=NC=N"_smiles;
      REQUIRE(mol);
      auto sinfo = Chirality::detail::getStereoInfo(mol->getBondWithIdx(1));
      CHECK(sinfo.type == Chirality::StereoType::Bond_Double);
      CHECK(sinfo.centeredOn == 1);
      REQUIRE(sinfo.controllingAtoms.size() == 4);
      CHECK(sinfo.controllingAtoms[0] == 0);
      CHECK(sinfo.controllingAtoms[1] == Atom::NOATOM);
      CHECK(sinfo.controllingAtoms[2] == 3);
      CHECK(sinfo.controllingAtoms[3] == Atom::NOATOM);
    }
  }
  SECTION("stereo") {
    auto useLegacy = GENERATE(true, false);
    CAPTURE(useLegacy);
    UseLegacyStereoPerceptionFixture fx(useLegacy);

    {
      auto mol = "C/C=C(/C#C)C"_smiles;
      REQUIRE(mol);

      CHECK(mol->getBondWithIdx(1)->getStereoAtoms().size() == 2);
      CHECK(mol->getBondWithIdx(1)->getStereoAtoms()[0] == 0);
      CHECK(mol->getBondWithIdx(1)->getStereoAtoms()[1] == 3);

      auto sinfo = Chirality::detail::getStereoInfo(mol->getBondWithIdx(1));
      CHECK(sinfo.type == Chirality::StereoType::Bond_Double);
      CHECK(sinfo.centeredOn == 1);
      REQUIRE(sinfo.controllingAtoms.size() == 4);
      CHECK(sinfo.controllingAtoms[0] == 0);
      CHECK(sinfo.controllingAtoms[1] == Atom::NOATOM);
      CHECK(sinfo.controllingAtoms[2] == 3);
      CHECK(sinfo.controllingAtoms[3] == 5);
      CHECK(sinfo.specified == Chirality::StereoSpecified::Specified);
      CHECK(sinfo.descriptor == Chirality::StereoDescriptor::Bond_Trans);
    }
    {  // check an example where one of the stereo atoms isn't the first
       // neighbor (only true with legacy chirality)
      auto mol = "C/C=C(/C)C#C"_smiles;
      REQUIRE(mol);

      CHECK(mol->getBondWithIdx(1)->getStereoAtoms().size() == 2);
      CHECK(mol->getBondWithIdx(1)->getStereoAtoms()[0] == 0);
      CHECK(mol->getBondWithIdx(1)->getStereoAtoms()[1] == (useLegacy ? 4 : 3));

      auto sinfo = Chirality::detail::getStereoInfo(mol->getBondWithIdx(1));
      CHECK(sinfo.type == Chirality::StereoType::Bond_Double);
      CHECK(sinfo.centeredOn == 1);
      REQUIRE(sinfo.controllingAtoms.size() == 4);
      CHECK(sinfo.controllingAtoms[0] == 0);
      CHECK(sinfo.controllingAtoms[1] == Atom::NOATOM);
      CHECK(sinfo.controllingAtoms[2] == 3);
      CHECK(sinfo.controllingAtoms[3] == 4);
      CHECK(sinfo.specified == Chirality::StereoSpecified::Specified);
      CHECK(sinfo.descriptor == Chirality::StereoDescriptor::Bond_Trans);
    }
    {
      auto mol = "C/C=C(\\C#C)C"_smiles;
      REQUIRE(mol);

      CHECK(mol->getBondWithIdx(1)->getStereoAtoms().size() == 2);
      CHECK(mol->getBondWithIdx(1)->getStereoAtoms()[0] == 0);
      CHECK(mol->getBondWithIdx(1)->getStereoAtoms()[1] == 3);

      auto sinfo = Chirality::detail::getStereoInfo(mol->getBondWithIdx(1));
      CHECK(sinfo.type == Chirality::StereoType::Bond_Double);
      CHECK(sinfo.centeredOn == 1);
      REQUIRE(sinfo.controllingAtoms.size() == 4);
      CHECK(sinfo.controllingAtoms[0] == 0);
      CHECK(sinfo.controllingAtoms[1] == Atom::NOATOM);
      CHECK(sinfo.controllingAtoms[2] == 3);
      CHECK(sinfo.controllingAtoms[3] == 5);
      CHECK(sinfo.specified == Chirality::StereoSpecified::Specified);
      CHECK(sinfo.descriptor == Chirality::StereoDescriptor::Bond_Cis);
    }
    {  // any bonds
      auto mol = "CC=C(C#C)C"_smiles;
      REQUIRE(mol);

      mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);

      auto sinfo = Chirality::detail::getStereoInfo(mol->getBondWithIdx(1));
      CHECK(sinfo.type == Chirality::StereoType::Bond_Double);
      CHECK(sinfo.centeredOn == 1);
      REQUIRE(sinfo.controllingAtoms.size() == 4);
      CHECK(sinfo.controllingAtoms[0] == 0);
      CHECK(sinfo.controllingAtoms[1] == Atom::NOATOM);
      CHECK(sinfo.controllingAtoms[2] == 3);
      CHECK(sinfo.controllingAtoms[3] == 5);
      CHECK(sinfo.specified == Chirality::StereoSpecified::Unknown);
      CHECK(sinfo.descriptor == Chirality::StereoDescriptor::None);
    }
  }
}
TEST_CASE("isBondPotentialStereoBond", "[unittest]") {
  SECTION("basics") {
    {
      auto mol = "CC=C(C#C)C=C"_smiles;
      REQUIRE(mol);
      CHECK(
          Chirality::detail::isBondPotentialStereoBond(mol->getBondWithIdx(1)));
      CHECK(!Chirality::detail::isBondPotentialStereoBond(
          mol->getBondWithIdx(5)));
      CHECK(!Chirality::detail::isBondPotentialStereoBond(
          mol->getBondWithIdx(3)));
      CHECK(!Chirality::detail::isBondPotentialStereoBond(
          mol->getBondWithIdx(4)));
    }
    {
      auto mol = "CC=NC=N"_smiles;
      REQUIRE(mol);
      CHECK(
          Chirality::detail::isBondPotentialStereoBond(mol->getBondWithIdx(1)));
      CHECK(
          Chirality::detail::isBondPotentialStereoBond(mol->getBondWithIdx(3)));
    }
    {
      SmilesParserParams ps;
      ps.removeHs = false;
      std::unique_ptr<ROMol> mol{SmilesToMol("[H]C=CC=C([H])[H]", ps)};
      REQUIRE(mol);
      CHECK(!Chirality::detail::isBondPotentialStereoBond(
          mol->getBondWithIdx(1)));
      CHECK(!Chirality::detail::isBondPotentialStereoBond(
          mol->getBondWithIdx(3)));
    }
  }
  SECTION("ring size") {
    {
      auto m = "C1=CCCCC1"_smiles;
      REQUIRE(m);
      CHECK(
          !Chirality::detail::isBondPotentialStereoBond(m->getBondWithIdx(0)));
    }
    {
      auto m = "C1=CCCCCC1"_smiles;
      REQUIRE(m);
      CHECK(
          !Chirality::detail::isBondPotentialStereoBond(m->getBondWithIdx(0)));
    }
    {
      auto m = "C12=C(CCCC2)CCCCCC1"_smiles;
      REQUIRE(m);
      CHECK(
          !Chirality::detail::isBondPotentialStereoBond(m->getBondWithIdx(0)));
    }
  }
}

TEST_CASE("atom StereoInfo", "[unittest]") {
  SECTION("basics") {
    {
      auto mol = "CC(F)(Cl)CNC(C)C"_smiles;
      REQUIRE(mol);
      auto sinfo = Chirality::detail::getStereoInfo(mol->getAtomWithIdx(1));
      CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(sinfo.centeredOn == 1);
      REQUIRE(sinfo.controllingAtoms.size() == 4);
      CHECK(sinfo.controllingAtoms[0] == 0);
      CHECK(sinfo.controllingAtoms[1] == 2);
      CHECK(sinfo.controllingAtoms[2] == 3);
      CHECK(sinfo.controllingAtoms[3] == 4);
      CHECK(sinfo.specified == Chirality::StereoSpecified::Unspecified);
      CHECK(sinfo.descriptor == Chirality::StereoDescriptor::None);

      sinfo = Chirality::detail::getStereoInfo(mol->getAtomWithIdx(6));
      CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(sinfo.centeredOn == 6);
      REQUIRE(sinfo.controllingAtoms.size() == 3);
      CHECK(sinfo.controllingAtoms[0] == 5);
      CHECK(sinfo.controllingAtoms[1] == 7);
      CHECK(sinfo.controllingAtoms[2] == 8);
      CHECK(sinfo.specified == Chirality::StereoSpecified::Unspecified);
      CHECK(sinfo.descriptor == Chirality::StereoDescriptor::None);
    }

    {
      auto mol = "C[C@](F)(Cl)CNC(C)C"_smiles;
      REQUIRE(mol);
      auto sinfo = Chirality::detail::getStereoInfo(mol->getAtomWithIdx(1));
      CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(sinfo.centeredOn == 1);
      REQUIRE(sinfo.controllingAtoms.size() == 4);
      CHECK(sinfo.controllingAtoms[0] == 0);
      CHECK(sinfo.controllingAtoms[1] == 2);
      CHECK(sinfo.controllingAtoms[2] == 3);
      CHECK(sinfo.controllingAtoms[3] == 4);
      CHECK(sinfo.specified == Chirality::StereoSpecified::Specified);
      CHECK(sinfo.descriptor == Chirality::StereoDescriptor::Tet_CCW);
    }

    {
      auto mol = "CN1CC1N(F)C"_smiles;
      REQUIRE(mol);
      auto sinfo = Chirality::detail::getStereoInfo(mol->getAtomWithIdx(1));
      CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(sinfo.centeredOn == 1);
      REQUIRE(sinfo.controllingAtoms.size() == 3);
      CHECK(sinfo.controllingAtoms[0] == 0);
      CHECK(sinfo.controllingAtoms[1] == 2);
      CHECK(sinfo.controllingAtoms[2] == 3);
    }

    {
      auto mol = "O[As](F)C[As]C[As]"_smiles;
      REQUIRE(mol);

      auto sinfo = Chirality::detail::getStereoInfo(mol->getAtomWithIdx(1));
      CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(sinfo.centeredOn == 1);
      REQUIRE(sinfo.controllingAtoms.size() == 3);
      CHECK(sinfo.controllingAtoms[0] == 0);
      CHECK(sinfo.controllingAtoms[1] == 2);
      CHECK(sinfo.controllingAtoms[2] == 3);

      sinfo = Chirality::detail::getStereoInfo(mol->getAtomWithIdx(4));
      CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(sinfo.centeredOn == 4);
      REQUIRE(sinfo.controllingAtoms.size() == 2);
      CHECK(sinfo.controllingAtoms[0] == 3);
      CHECK(sinfo.controllingAtoms[1] == 5);
    }
  }
}
TEST_CASE("isAtomPotentialTetrahedralCenter", "[unittest]") {
  SECTION("basics") {
    {
      auto mol = "CC(F)(Cl)CNC(C)(C)C"_smiles;
      REQUIRE(mol);
      CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(1)));
      CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(0)));
      CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(4)));
      CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(6)));
    }
    {
      auto mol = "CN1CC1N(F)C"_smiles;
      REQUIRE(mol);
      CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(1)));
      CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(4)));
    }
    {
      auto mol = "O=S(F)CC[S+]([O-])CS=O"_smiles;
      REQUIRE(mol);
      CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(1)));
      CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(5)));
      CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(8)));
    }
    {
      auto mol = "O=[Se](F)CC[Se+]([O-])C[Se]=O"_smiles;
      REQUIRE(mol);
      CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(1)));
      CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(5)));
      CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(8)));
    }
    {
      auto mol = "OP(F)CPCP"_smiles;
      REQUIRE(mol);
      CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(1)));
      CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(4)));
      CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(6)));
    }
    {
      auto mol = "O[As](F)C[As]C[As]"_smiles;
      REQUIRE(mol);
      CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(1)));
      CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(4)));
      CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(6)));
    }
    {
      auto mol = "O[P]([O-])(=O)OC"_smiles;
      REQUIRE(mol);
      CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
          mol->getAtomWithIdx(1)));
    }
  }
}
TEST_CASE("isAtomPotentialStereoAtom", "[unittest]") {
  SECTION("basics") {
    {
      auto mol = "CC(F)(Cl)CNC(C)(C)C"_smiles;
      REQUIRE(mol);
      for (const auto atom : mol->atoms()) {
        CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(atom) ==
              Chirality::detail::isAtomPotentialStereoAtom(atom));
      }
    }
    {
      auto mol = "CN1CC1N(F)C"_smiles;
      REQUIRE(mol);
      for (const auto atom : mol->atoms()) {
        CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(atom) ==
              Chirality::detail::isAtomPotentialStereoAtom(atom));
      }
    }
    {
      auto mol = "O=S(F)CC[S+]([O-])CS=O"_smiles;
      REQUIRE(mol);
      for (const auto atom : mol->atoms()) {
        CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(atom) ==
              Chirality::detail::isAtomPotentialStereoAtom(atom));
      }
    }
  }
}

TEST_CASE("possible stereochemistry on atoms", "[chirality]") {
  SECTION("specified") {
    {
      auto mol = "CC(C)(O)[C@](Cl)(F)I"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
      CHECK(stereoInfo[0].centeredOn == 4);
      std::vector<unsigned> catoms = {1, 5, 6, 7};
      CHECK(stereoInfo[0].controllingAtoms == catoms);
    }
    {
      auto mol = "C[C@@H](O)[C@H](C)[C@H](C)O"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 3);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
      CHECK(stereoInfo[0].centeredOn == 1);

      CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
      CHECK(stereoInfo[1].centeredOn == 3);

      CHECK(stereoInfo[2].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[2].specified == Chirality::StereoSpecified::Specified);
      CHECK(stereoInfo[2].centeredOn == 5);
    }

    {
      auto mol = "FC(F)(F)[C@@H](O)[C@H](C)[C@H](C(F)(F)F)O"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 3);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
      CHECK(stereoInfo[0].centeredOn == 4);

      CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
      CHECK(stereoInfo[1].centeredOn == 6);

      CHECK(stereoInfo[2].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[2].specified == Chirality::StereoSpecified::Specified);
      CHECK(stereoInfo[2].centeredOn == 8);
    }
  }
  SECTION("simple unspecified") {
    {
      auto mol = "CC(C)(O)C(Cl)(F)I"_smiles;
      REQUIRE(mol);

      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
      CHECK(stereoInfo[0].centeredOn == 4);
      std::vector<unsigned> catoms = {1, 5, 6, 7};
      CHECK(stereoInfo[0].controllingAtoms == catoms);
    }
  }
  SECTION("atoms with unknown set, real") {
    auto mol = "FC(O)C"_smiles;
    REQUIRE(mol);
    mol->getBondBetweenAtoms(0, 1)->setBondDir(Bond::BondDir::UNKNOWN);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 1);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
    CHECK(stereoInfo[0].centeredOn == 1);
  }
  SECTION("atoms with unknown set, not real") {
    auto mol = "CC(O)C"_smiles;
    REQUIRE(mol);
    mol->getBondBetweenAtoms(0, 1)->setBondDir(Bond::BondDir::UNKNOWN);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    CHECK(stereoInfo.size() == 0);
  }
  SECTION("Isotopes") {
    {
      auto mol = "O[C@H](F)[18OH]"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
      CHECK(stereoInfo[0].centeredOn == 1);
      std::vector<unsigned> catoms = {0, 2, 3};
      CHECK(stereoInfo[0].controllingAtoms == catoms);
    }
  }
}

TEST_CASE("possible stereochemistry on bonds", "[chirality]") {
  SECTION("simplest") {
    {
      auto mol = "CC=CC"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
      CHECK(stereoInfo[0].centeredOn == 1);
      std::vector<unsigned> catoms = {0, Atom::NOATOM, 3, Atom::NOATOM};
      CHECK(stereoInfo[0].controllingAtoms == catoms);
    }
    {
      auto mol = "CC=C(C)C"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      CHECK(stereoInfo.size() == 0);
    }
    {
      auto mol = "CC=C"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      CHECK(stereoInfo.size() == 0);
    }
    {
      auto mol = "CC(F)=C(Cl)C"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
      CHECK(stereoInfo[0].centeredOn == 2);
      std::vector<unsigned> catoms = {2, 0, 4, 5};
      CHECK(stereoInfo[0].controllingAtoms == catoms);
    }
    {
      auto mol = "CC=C(Cl)C"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
      CHECK(stereoInfo[0].centeredOn == 1);
      std::vector<unsigned> catoms = {0, Atom::NOATOM, 3, 4};
      CHECK(stereoInfo[0].controllingAtoms == catoms);
    }
  }
  SECTION("bond with unknown set, real") {
    auto mol = "CC=C(C)F"_smiles;
    REQUIRE(mol);
    mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 1);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
    CHECK(stereoInfo[0].centeredOn == 1);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
  }
  SECTION("bond with unknown set, not real") {
    auto mol = "CC=C(C)C"_smiles;
    REQUIRE(mol);
    mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    CHECK(stereoInfo.size() == 0);
  }
}

TEST_CASE("para-stereocenters and assignStereochemistry", "[chirality]") {
  SECTION("simplest") {
    auto mol = "CC(F)C(C)C(C)F"_smiles;
    REQUIRE(mol);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 3);

    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 1);
    CHECK(stereoInfo[0].controllingAtoms.size() == 3);
    CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[1].centeredOn == 3);
    CHECK(stereoInfo[1].controllingAtoms.size() == 3);
    CHECK(stereoInfo[2].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[2].centeredOn == 5);
    CHECK(stereoInfo[2].controllingAtoms.size() == 3);
  }

  SECTION("including bonds") {
    // thanks to Salome Rieder for this nasty example
    auto mol = "CC=CC(C=CC)C(C)C(C=CC)C=CC"_smiles;
    REQUIRE(mol);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    CHECK(stereoInfo.size() == 7);

    std::sort(stereoInfo.begin(), stereoInfo.end(),
              [](const Chirality::StereoInfo &a,
                 const Chirality::StereoInfo &b) -> bool {
                return (a.type < b.type) && (a.centeredOn < b.centeredOn) &&
                       (a.specified < b.specified) &&
                       (a.descriptor < b.descriptor) &&
                       (a.controllingAtoms < b.controllingAtoms);
              });
    REQUIRE(stereoInfo.size() == 7);

    CHECK(stereoInfo[6].type == Chirality::StereoType::Bond_Double);
    CHECK(stereoInfo[6].centeredOn == 13);
    CHECK(stereoInfo[5].type == Chirality::StereoType::Bond_Double);
    CHECK(stereoInfo[5].centeredOn == 10);
    CHECK(stereoInfo[4].type == Chirality::StereoType::Bond_Double);
    CHECK(stereoInfo[4].centeredOn == 4);
    CHECK(stereoInfo[3].type == Chirality::StereoType::Bond_Double);
    CHECK(stereoInfo[3].centeredOn == 1);

    CHECK(stereoInfo[2].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[2].centeredOn == 9);
    CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[1].centeredOn == 7);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 3);
  }

  SECTION("sugar fun") {
    auto mol = "C1(O)C(O)C(O)C(O)C(O)C1O"_smiles;
    REQUIRE(mol);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 6);
    for (const auto &si : stereoInfo) {
      CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(si.centeredOn % 2 == 0);
      CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
    }
  }
}

TEST_CASE("ring stereochemistry", "[chirality]") {
  SECTION("partially specified") {
    SmilesParserParams ps;
    ps.sanitize = false;
    std::unique_ptr<RWMol> mol{SmilesToMol("C[C@H]1CCC(C)CC1", ps)};
    REQUIRE(mol);
    // std::cerr << "------------ 3 -------------" << std::endl;
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 1);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);

    CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[1].centeredOn == 4);
    CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
  }
  SECTION("specified") {
    auto mol = "C[C@H]1CC[C@@H](C)CC1"_smiles;
    REQUIRE(mol);
    // std::cerr << "------------ 1 -------------" << std::endl;

    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 1);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
    CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[1].centeredOn == 4);
    CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
  }
  SECTION("unspecified") {
    auto mol = "CC1CCC(C)CC1"_smiles;
    REQUIRE(mol);
    // std::cerr << "------------ 2 -------------" << std::endl;
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 1);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);

    CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[1].centeredOn == 4);
    CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
  }
  SECTION("four ring") {
    auto mol = "C[C@H]1C[C@@H](C)C1"_smiles;
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 1);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);

    CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[1].centeredOn == 3);
    CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
  }
  SECTION("four ring unspecified") {
    auto mol = "CC1CC(C)C1"_smiles;
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 1);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);

    CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[1].centeredOn == 3);
    CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
  }
}

TEST_CASE("tricky recursive example from Dan Nealschneider", "[chirality]") {
  SECTION("adapted") {
    auto mol = "CC=C1CCC(O)CC1"_smiles;
    REQUIRE(mol);
    mol->updatePropertyCache();
    MolOps::setBondStereoFromDirections(*mol);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 5);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
    CHECK(stereoInfo[1].type == Chirality::StereoType::Bond_Double);
    CHECK(stereoInfo[1].centeredOn == 1);
    CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
  }
  SECTION("simplified") {
    // can't sanitize this because the current (2020.03) assignStereochemistry
    // code doesn't recognize the stereo here and removes it
    SmilesParserParams ps;
    ps.sanitize = false;
    ps.removeHs = false;
    std::unique_ptr<ROMol> mol(SmilesToMol("C/C=C1/C[C@H](O)C1", ps));
    REQUIRE(mol);
    mol->updatePropertyCache();
    MolOps::setBondStereoFromDirections(*mol);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 4);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
    CHECK(stereoInfo[1].type == Chirality::StereoType::Bond_Double);
    CHECK(stereoInfo[1].centeredOn == 1);
    CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
  }
  // FIX this still isn't working
  SECTION("unspecified") {
    auto mol = "CC=C1C[CH](O)C1"_smiles;
    REQUIRE(mol);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 4);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
    CHECK(stereoInfo[1].type == Chirality::StereoType::Bond_Double);
    CHECK(stereoInfo[1].centeredOn == 1);
    CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
  }
}

TEST_CASE("unknown stereo", "[chirality]") {
  SECTION("atoms") {
    auto mol = "CC(O)C[C@@H](O)F"_smiles;
    REQUIRE(mol);
    REQUIRE(mol->getBondBetweenAtoms(0, 1));
    mol->getBondBetweenAtoms(0, 1)->setBondDir(Bond::BondDir::UNKNOWN);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 1);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
    CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[1].centeredOn == 4);
    CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
  }
  SECTION("atoms2") {
    // artificial situation: "squiggly bond" overrides the specified atomic
    // stereo
    auto mol = "C[C@H](O)C[C@@H](O)F"_smiles;
    REQUIRE(mol);
    REQUIRE(mol->getBondBetweenAtoms(0, 1));
    mol->getBondBetweenAtoms(0, 1)->setBondDir(Bond::BondDir::UNKNOWN);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    REQUIRE(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 1);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
    CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[1].centeredOn == 4);
    CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
  }
  SECTION("bonds") {
    {
      auto mol = "CC=CC"_smiles;
      REQUIRE(mol);
      REQUIRE(mol->getBondBetweenAtoms(1, 2));
      mol->getBondBetweenAtoms(1, 2)->setBondDir(Bond::BondDir::EITHERDOUBLE);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
      CHECK(stereoInfo[0].centeredOn == 1);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
    }
    {
      auto mol = "CC=CC=C"_smiles;
      REQUIRE(mol);
      REQUIRE(mol->getBondBetweenAtoms(1, 2));
      mol->getBondBetweenAtoms(1, 2)->setBondDir(Bond::BondDir::EITHERDOUBLE);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
      CHECK(stereoInfo[0].centeredOn == 1);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
    }
  }
  SECTION("bonds with squiggle bonds") {
    {  // to begin atom
      auto mol = "CC=CC"_smiles;
      REQUIRE(mol);
      REQUIRE(mol->getBondBetweenAtoms(0, 1));
      mol->getBondBetweenAtoms(0, 1)->setBondDir(Bond::BondDir::UNKNOWN);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
      CHECK(stereoInfo[0].centeredOn == 1);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
    }
    {  // to end atom
      auto mol = "CC=CC"_smiles;
      REQUIRE(mol);
      REQUIRE(mol->getBondBetweenAtoms(2, 3));
      mol->getBondBetweenAtoms(2, 3)->setBondDir(Bond::BondDir::UNKNOWN);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
      CHECK(stereoInfo[0].centeredOn == 1);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
    }
  }
}

TEST_CASE("cleaning chirality", "[chirality]") {
  SECTION("atoms") {
    auto mol = "CC(O)C"_smiles;
    REQUIRE(mol);
    mol->getAtomWithIdx(1)->setChiralTag(Atom::ChiralType::CHI_TETRAHEDRAL_CW);
    {
      // by default we don't clean up, so the chiral center survives even though
      // we don't get any results:
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      CHECK(stereoInfo.size() == 0);
      CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_TETRAHEDRAL_CW);
    }
    {
      bool cleanIt = true;
      auto stereoInfo = Chirality::findPotentialStereo(*mol, cleanIt);
      CHECK(stereoInfo.size() == 0);
      CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("bonds") {
    auto mol = "CC=C(C)C"_smiles;
    REQUIRE(mol);
    mol->getBondWithIdx(1)->setStereoAtoms(0, 3);
    mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOCIS);
    {
      // by default we don't clean up, so the stereo bond survives even though
      // we don't get any results:
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      CHECK(stereoInfo.size() == 0);
      CHECK(mol->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOCIS);
    }
    {
      bool cleanIt = true;
      auto stereoInfo = Chirality::findPotentialStereo(*mol, cleanIt);
      CHECK(stereoInfo.size() == 0);
      CHECK(mol->getBondWithIdx(1)->getStereo() ==
            Bond::BondStereo::STEREONONE);
    }
  }
}

TEST_CASE("flagPossible", "[chirality]") {
  SECTION("atoms") {
    auto mol = "CC(O)[C@H](F)O"_smiles;
    REQUIRE(mol);
    {
      // by default we do use flagPossible:
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 2);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].centeredOn == 1);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
      CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[1].centeredOn == 3);
      CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
    }
    {
      bool cleanIt = false;
      bool flagPossible = false;
      auto stereoInfo =
          Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
      REQUIRE(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].centeredOn == 3);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
    }
  }
  SECTION("bonds") {
    auto mol = "CC=C/C=C/C"_smiles;
    REQUIRE(mol);
    {
      // by default we do use flagPossible
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 2);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
      CHECK(stereoInfo[0].centeredOn == 1);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
      CHECK(stereoInfo[1].type == Chirality::StereoType::Bond_Double);
      CHECK(stereoInfo[1].centeredOn == 3);
      CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
    }
    {
      bool cleanIt = true;
      bool flagPossible = false;
      auto stereoInfo =
          Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
      REQUIRE(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
      CHECK(stereoInfo[0].centeredOn == 3);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
    }
  }
}

TEST_CASE("cleanup after removing possible centers", "[chirality]") {
  SECTION("atoms1") {
    auto mol = "FC(Cl)(F)C(C(Cl)(F)F)I"_smiles;
    REQUIRE(mol);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    CHECK(stereoInfo.empty());
  }
  SECTION("bonds1") {
    auto mol = "FC(Cl)(F)C(C(Cl)(F)F)=CF"_smiles;
    REQUIRE(mol);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    CHECK(stereoInfo.empty());
  }
  SECTION("atoms2") {
    auto mol = "ClC(F)(F)C(=CC(F)C=C(C(F)(F)Cl)C(F)(F)Cl)C(Cl)(F)F"_smiles;
    REQUIRE(mol);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    CHECK(stereoInfo.empty());
  }
}

TEST_CASE("findPotentialStereo problems related to #3490", "[chirality][bug]") {
  SECTION("example 1") {
    auto mol = "CC1CC(O)C1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 1);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
    CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[1].centeredOn == 3);
    CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
  }
  SECTION("example 2a") {
    auto mol = "C(C(C)C1)C12CCN2"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
  }
  SECTION("example 2b") {
    auto mol = "CC(C1)CC12CCN2"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
  }
  SECTION("example 2c") {
    auto mol = "C([C@H](C)C1)[C@]12CCN2"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
  }
  SECTION("example 2d") {
    auto mol = "C[C@H](C1)C[C@]12CCN2"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
  }
  SECTION("example 3") {
    auto mol = "C(C(C)C1)C12CN(C3)CCCCC23"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 4);  // [1, 4, 6, 12]
    CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[0].centeredOn == 1);
    CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
    CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[1].centeredOn == 4);
    CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
    CHECK(stereoInfo[2].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[2].centeredOn == 6);
    CHECK(stereoInfo[2].specified == Chirality::StereoSpecified::Unspecified);
    CHECK(stereoInfo[3].type == Chirality::StereoType::Atom_Tetrahedral);
    CHECK(stereoInfo[3].centeredOn == 12);
    CHECK(stereoInfo[3].specified == Chirality::StereoSpecified::Unspecified);
  }
}
TEST_CASE("ring stereo finding is overly aggressive", "[chirality][bug]") {
  SECTION("Finding too much 1a") {
    auto mol = "CC1CCCCC1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 0);
  }
  SECTION("Finding too much 1b") {
    auto mol = "CC1CCC(C)CC1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
  }
  SECTION("Finding too much 1c") {
    auto mol = "C[C@H]1CCC(C)CC1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
  }
  SECTION("Finding too much 1d") {
    auto mol = "CC1(C)CCCCC1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 0);
  }
  SECTION("Finding too much 1e") {
    auto mol = "CC1(C)CCC(C)CC1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 0);
  }
  SECTION("Finding too much 1f") {
    auto mol = "C2CC2C1(C2CC2)CCCCC1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 0);
  }
  SECTION("Finding too much 1g") {
    auto mol = "CC1CC2(CCC2)C1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 0);
  }
  SECTION("Finding too much 1h") {
    auto mol = "CC1CC2(CC(C)C2)C1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 3);
  }

  SECTION("Finding too much 2a") {
    auto mol = "CC1CCNCC1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 0);
  }
  SECTION("Finding too much 2b") {
    auto mol = "CC1CCN(C)CC1"_smiles;  // 3-coordinate N is not stereogenic
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 0);
  }
  SECTION("Finding too much 3a") {
    auto mol = "CC1CCC1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 0);
  }

  SECTION("Finding too much 3b") {
    auto mol = "CC1CC(C)C1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
  }
  SECTION("fused rings 1") {
    auto mol = "C1CCC2CCCCC2C1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
  }

  SECTION("fused rings 2") {
    auto mol = "C1CC2CCCC2C1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
  }

  SECTION("cages 1") {
    auto mol = "CC1CN2CCC1CC2"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 3);
    CHECK(stereoInfo[0].centeredOn == 1);
    CHECK(stereoInfo[1].centeredOn == 3);
    CHECK(stereoInfo[2].centeredOn == 6);
  }
  SECTION("cages 1b") {
    auto mol = "O1CN2CCC1CC2"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].centeredOn == 2);
    CHECK(stereoInfo[1].centeredOn == 5);
  }
  SECTION("cages 2") {
    auto mol = "C1CC2(O)CCC1(C)CC2"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].centeredOn == 2);
    CHECK(stereoInfo[1].centeredOn == 6);
  }
  SECTION("cages 3") {
    auto mol = "C1CC2(O)CCC1CC2"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
    CHECK(stereoInfo[0].centeredOn == 2);
    CHECK(stereoInfo[1].centeredOn == 6);
  }
  SECTION("adamantyl") {
    auto mol = "CC12CC3CC(CC(C3)C1)C2"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 4);
  }
  SECTION("bug 1a") {
    // example that came up during testing
    auto mol = "C(=O)C(C(C)N2C=C2)C(=O)"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    REQUIRE(stereoInfo.size() == 1);
    CHECK(stereoInfo[0].centeredOn == 3);
  }
  SECTION("bug 1b") {
    // example that came up during testing
    auto mol = "C(=O)C(C(CC)c2ccc(Cl)cc2)C(=O)"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    REQUIRE(stereoInfo.size() == 1);
    CHECK(stereoInfo[0].centeredOn == 3);
  }

  SECTION("bug 1c") {
    // example that came up during testing
    auto mol = "O=CC(C=O)C(C)n2cccc2"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    REQUIRE(stereoInfo.size() == 1);
    CHECK(stereoInfo[0].centeredOn == 5);
  }

  SECTION("bug 1c") {
    // example that came up during testing
    auto mol = "C(=O)C(C(C)n2cccc2)C(=O)"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    REQUIRE(stereoInfo.size() == 1);
    CHECK(stereoInfo[0].centeredOn == 3);
  }

  SECTION("bug 1d") {
    // example that came up during testing
    auto mol = "C(O)C(C(C)n2cccc2)C(O)"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    REQUIRE(stereoInfo.size() == 1);
    CHECK(stereoInfo[0].centeredOn == 3);
  }
  SECTION("just a bug") {
    // example that came up during testing

    auto mol = "CC1=CN(C2OC(CNC(=O)C3c4ccccc4Sc4ccccc43)CC2)C(=O)NC1=O"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 2);
  }
  SECTION("bad ring chirality due to pair being in 2 rings") {
    // from canonicalization watch test.

    auto mol = "[O-]C1=C2C[C@H](C2)[C@@]12CC2"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    auto stereoInfo =
        Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
    CHECK(stereoInfo.size() == 0);
  }
  SECTION(
      "Removal of stereoatoms requires removing CIS/TRANS when using legacy stereo") {
    UseLegacyStereoPerceptionFixture reset_stereo_perception(false);

    {
      auto mol = "N/C=C/C"_smiles;
      CHECK(mol->getBondWithIdx(1)->getStereo() ==
            Bond::BondStereo::STEREOTRANS);
      auto rwmol = dynamic_cast<RWMol *>(mol.get());
      rwmol->removeBond(0, 1);
      CHECK(mol->getBondWithIdx(0)->getStereo() ==
            Bond::BondStereo::STEREONONE);
    }
    {
      auto mol = "N/C=C/C"_smiles;
      CHECK(mol->getBondWithIdx(1)->getStereo() ==
            Bond::BondStereo::STEREOTRANS);
      auto rwmol = dynamic_cast<RWMol *>(mol.get());
      rwmol->removeBond(2, 3);
      CHECK(mol->getBondWithIdx(1)->getStereo() ==
            Bond::BondStereo::STEREONONE);
    }
  }
}

TEST_CASE(
    "github #3631: Ring stereochemistry not properly removed from N atoms",
    "[chirality][bug]") {
  SECTION("basics") {
    SmilesParserParams ps;
    ps.sanitize = false;
    ps.removeHs = false;
    std::unique_ptr<RWMol> mol{SmilesToMol("C[N@]1C[C@@](F)(Cl)C1", ps)};
    REQUIRE(mol);
    MolOps::sanitizeMol(*mol);

    CHECK(mol->getAtomWithIdx(1)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(mol->getAtomWithIdx(3)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
    bool cleanIt = true;
    bool flagPossible = true;
    bool force = true;
    {
      RWMol mol2(*mol);
      auto stereoInfo =
          Chirality::findPotentialStereo(mol2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 0);
    }
    {
      RWMol mol2(*mol);
      MolOps::assignStereochemistry(mol2, cleanIt, force, flagPossible);
      CHECK(mol2.getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
      CHECK(mol2.getAtomWithIdx(3)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("default behavior") {
    auto mol = "C[N@]1C[C@@](F)(Cl)C1"_smiles;
    REQUIRE(mol);
    auto smiles = MolToSmiles(*mol);
    CHECK(smiles == "CN1CC(F)(Cl)C1");
    bool cleanIt = true;
    bool flagPossible = true;
    bool force = true;
    CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(mol->getAtomWithIdx(3)->getChiralTag() ==
          Atom::ChiralType::CHI_UNSPECIFIED);
    {
      RWMol mol2(*mol);
      auto stereoInfo =
          Chirality::findPotentialStereo(mol2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 0);
    }
    {
      RWMol mol2(*mol);
      MolOps::assignStereochemistry(mol2, cleanIt, force, flagPossible);
      CHECK(mol2.getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
      CHECK(mol2.getAtomWithIdx(3)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("don't overcorrect") {
    auto mol = "C[N@]1O[C@@](F)(Cl)C1"_smiles;
    REQUIRE(mol);
    bool cleanIt = true;
    bool flagPossible = true;
    bool force = true;
    {
      RWMol mol2(*mol);
      auto stereoInfo =
          Chirality::findPotentialStereo(mol2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 1);
      CHECK(stereoInfo[0].centeredOn == 3);
    }
    {
      RWMol mol2(*mol);
      MolOps::assignStereochemistry(mol2, cleanIt, force, flagPossible);
      CHECK(mol2.getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
      CHECK(mol2.getAtomWithIdx(3)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
}

TEST_CASE("N Chirality in rings") {
  SECTION("basics 4 coordinate") {
    {
      auto mol = "CC1CC2CC[N@@+]1(C)OC2"_smiles;
      REQUIRE(mol);
      CHECK(mol->getAtomWithIdx(6)->getAtomicNum() == 7);
      CHECK(mol->getAtomWithIdx(6)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
    {
      auto mol = "C[N@@+](F)(Cl)O"_smiles;
      REQUIRE(mol);
      CHECK(mol->getAtomWithIdx(1)->getAtomicNum() == 7);
      CHECK(mol->getAtomWithIdx(1)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("basics 3 coordinate") {
    {
      auto mol = "CC1CC2CC[N@@]1OC2"_smiles;
      REQUIRE(mol);
      CHECK(mol->getAtomWithIdx(6)->getAtomicNum() == 7);
      CHECK(mol->getAtomWithIdx(6)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
    {
      auto mol = "C1CC[N@]2OCCCC2C1"_smiles;
      REQUIRE(mol);
      CHECK(mol->getAtomWithIdx(3)->getAtomicNum() == 7);
      CHECK(mol->getAtomWithIdx(3)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("ring stereo") {
    {  // real chirality
      auto mol = "C[C@H]1CC[N@@+](C)(O)OC1"_smiles;
      REQUIRE(mol);
      CHECK(mol->getAtomWithIdx(4)->getAtomicNum() == 7);
      CHECK(mol->getAtomWithIdx(4)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
      CHECK(mol->getAtomWithIdx(1)->getAtomicNum() == 6);
      CHECK(mol->getAtomWithIdx(1)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
    {  // ring stereo
      auto mol = "C[C@H]1CC[N@@+](C)(O)CC1"_smiles;
      REQUIRE(mol);
      CHECK(mol->getAtomWithIdx(4)->getAtomicNum() == 7);
      CHECK(mol->getAtomWithIdx(4)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
      CHECK(mol->getAtomWithIdx(1)->getAtomicNum() == 6);
      CHECK(mol->getAtomWithIdx(1)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
    {  // three-ring degree-three ring stereo
      auto mol = "C[C@H]1[C@@H](C)[N@]1C"_smiles;
      REQUIRE(mol);
      CHECK(mol->getAtomWithIdx(4)->getAtomicNum() == 7);
      CHECK(mol->getAtomWithIdx(4)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
    {  // CHEMBL79374
      auto mol = "Cn1ncc([C@]23CC[N@](CC2)C3)n1"_smiles;
      REQUIRE(mol);
      CHECK(mol->getAtomWithIdx(8)->getAtomicNum() == 7);
      CHECK(mol->getAtomWithIdx(8)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
    {  // derived from CHEMBL79374
      auto mol = "Cn1ncc([C@]23CC[C@](CC2)C3)n1"_smiles;
      REQUIRE(mol);
      CHECK(mol->getAtomWithIdx(8)->getAtomicNum() == 6);
      CHECK(mol->getAtomWithIdx(8)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
}

TEST_CASE(
    "Github #4115: RemoveStereochemistry should also remove stereogroups") {
  SECTION("basics") {
    auto mol = "C[C@H](O)[C@@H](C)F |o1:1,3,r|"_smiles;
    REQUIRE(mol);
    CHECK(mol->getAtomWithIdx(1)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(mol->getAtomWithIdx(3)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(mol->getStereoGroups().size() == 1);
    MolOps::removeStereochemistry(*mol);
    CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(mol->getAtomWithIdx(3)->getChiralTag() ==
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(mol->getStereoGroups().empty());
  }
}

TEST_CASE(
    "Github #4155: Problem finding stereocenters in bridged bicyclics with "
    "4-rings") {
  SECTION("specified") {
    std::vector<std::string> smis = {
        "C[C@H]1CC[C@H](CC1)C(N)=O", "C[C@]12CC[C@](CC1)(C2)C(N)=O",
        "C[C@H]1C[C@H](C1)C(N)=O", "C[C@]12C[C@](C1)(CC2)C(N)=O"};
    for (const auto &smi : smis) {
      std::unique_ptr<ROMol> mol(SmilesToMol(smi));
      REQUIRE(mol);
      bool cleanIt = true;
      bool flagPossible = true;
      auto stereoInfo =
          Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
      REQUIRE(stereoInfo.size() == 2);
      CHECK(stereoInfo[0].centeredOn == 1);
    }
  }
  SECTION("unspecified") {
    std::vector<std::string> smis = {
        "CC1CCC(CC1)C(N)=O", "CC12CCC(CC1)(C2)C(N)=O", "CC1CC(C1)C(N)=O",
        "CC12CC(C1)(CC2)C(N)=O"};
    for (const auto &smi : smis) {
      std::unique_ptr<ROMol> mol(SmilesToMol(smi));
      REQUIRE(mol);
      bool cleanIt = true;
      bool flagPossible = true;
      auto stereoInfo =
          Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
      REQUIRE(stereoInfo.size() == 2);
      CHECK(stereoInfo[0].centeredOn == 1);
    }
  }
}

TEST_CASE("pickBondsToWedge() should avoid double bonds") {
  SECTION("simplest") {
    auto mol = "OC=C[C@H](C1CC1)C2CCC2"_smiles;
    REQUIRE(mol);
    auto wedgedBonds = Chirality::pickBondsToWedge(*mol);
    REQUIRE(wedgedBonds.size() == 1);
    auto head = wedgedBonds.begin();
    CHECK(head->first == 3);
    CHECK(head->second->getIdx() == 3);
  }
  SECTION("simplest, specified double bond") {
    auto mol = "OC=C[C@H](C1CC1)C2CCC2"_smiles;
    REQUIRE(mol);
    mol->getBondBetweenAtoms(1, 2)->setStereoAtoms(0, 3);
    mol->getBondBetweenAtoms(1, 2)->setStereo(Bond::BondStereo::STEREOCIS);
    auto wedgedBonds = Chirality::pickBondsToWedge(*mol);
    REQUIRE(wedgedBonds.size() == 1);
    auto head = wedgedBonds.begin();
    CHECK(head->first == 3);
    CHECK(head->second->getIdx() == 3);
  }
  SECTION("prefer unspecified bond stereo") {
    auto mol = "OC=C[C@H](C=CF)(C=CC)"_smiles;
    REQUIRE(mol);
    mol->getBondBetweenAtoms(1, 2)->setStereoAtoms(0, 3);
    mol->getBondBetweenAtoms(1, 2)->setStereo(Bond::BondStereo::STEREOCIS);
    mol->getBondBetweenAtoms(4, 5)->setStereoAtoms(3, 6);
    mol->getBondBetweenAtoms(4, 5)->setStereo(Bond::BondStereo::STEREOANY);
    auto wedgedBonds = Chirality::pickBondsToWedge(*mol);
    REQUIRE(wedgedBonds.size() == 1);
    auto head = wedgedBonds.begin();
    CHECK(head->first == 6);
    CHECK(head->second->getIdx() == 3);
  }
}

TEST_CASE("addWavyBondsForStereoAny()") {
  SECTION("simplest") {
    auto mol = "CC=CC"_smiles;
    REQUIRE(mol);
    mol->getBondWithIdx(1)->setStereoAtoms(0, 3);
    mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);
    addWavyBondsForStereoAny(*mol);
    CHECK(mol->getBondWithIdx(0)->getBondDir() == Bond::BondDir::UNKNOWN);
    CHECK(mol->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREONONE);
  }
  SECTION("don't reset flags") {
    auto mol = "CC=CC"_smiles;
    REQUIRE(mol);
    mol->getBondWithIdx(1)->setStereoAtoms(0, 3);
    mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);
    bool clearFlags = false;
    addWavyBondsForStereoAny(*mol, clearFlags);
    CHECK(mol->getBondWithIdx(0)->getBondDir() == Bond::BondDir::UNKNOWN);
    CHECK(mol->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOANY);
  }
  SECTION("avoid double bonds") {
    auto mol = "CC=CC(CC)=CC"_smiles;
    REQUIRE(mol);
    mol->getBondWithIdx(5)->setStereoAtoms(2, 7);
    mol->getBondWithIdx(5)->setStereo(Bond::BondStereo::STEREOANY);
    addWavyBondsForStereoAny(*mol);
    CHECK(mol->getBondWithIdx(6)->getBondDir() == Bond::BondDir::UNKNOWN);
    CHECK(mol->getBondWithIdx(5)->getStereo() == Bond::BondStereo::STEREONONE);
  }
  SECTION("avoid chiral atoms") {
    auto mol = "C[C@](F)(Cl)C(C)=CC"_smiles;
    REQUIRE(mol);
    mol->getBondWithIdx(5)->setStereoAtoms(1, 7);
    mol->getBondWithIdx(5)->setStereo(Bond::BondStereo::STEREOANY);
    addWavyBondsForStereoAny(*mol);
    CHECK(mol->getBondWithIdx(4)->getBondDir() == Bond::BondDir::UNKNOWN);
    CHECK(mol->getBondWithIdx(5)->getStereo() == Bond::BondStereo::STEREONONE);
  }
  SECTION("prefer atoms with less neighbors") {
    auto mol = "CC(F)(Cl)C(CF)=CC"_smiles;
    REQUIRE(mol);
    mol->getBondWithIdx(6)->setStereoAtoms(1, 8);
    mol->getBondWithIdx(6)->setStereo(Bond::BondStereo::STEREOANY);
    addWavyBondsForStereoAny(*mol);
    CHECK(mol->getBondWithIdx(7)->getBondDir() == Bond::BondDir::UNKNOWN);
    CHECK(mol->getBondWithIdx(6)->getStereo() == Bond::BondStereo::STEREONONE);
  }
  SECTION("more complex") {
    auto mol = "CC=CC(C=CO)=CC"_smiles;
    REQUIRE(mol);
    mol->getBondWithIdx(6)->setStereoAtoms(2, 8);
    mol->getBondWithIdx(6)->setStereo(Bond::BondStereo::STEREOANY);
    addWavyBondsForStereoAny(*mol);
    CHECK(mol->getBondWithIdx(7)->getBondDir() == Bond::BondDir::UNKNOWN);
    CHECK(mol->getBondWithIdx(6)->getStereo() == Bond::BondStereo::STEREONONE);
  }
  SECTION("no solution without changing threshold") {
    auto mol = "CC=CC=CC=CC"_smiles;
    REQUIRE(mol);
    mol->getBondWithIdx(1)->setStereoAtoms(0, 3);
    mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOCIS);
    mol->getBondWithIdx(3)->setStereoAtoms(2, 5);
    mol->getBondWithIdx(3)->setStereo(Bond::BondStereo::STEREOANY);
    mol->getBondWithIdx(5)->setStereoAtoms(4, 7);
    mol->getBondWithIdx(5)->setStereo(Bond::BondStereo::STEREOCIS);
    addWavyBondsForStereoAny(*mol);
    // we didn't actually do anything:
    CHECK(mol->getBondWithIdx(2)->getBondDir() == Bond::BondDir::NONE);
    CHECK(mol->getBondWithIdx(3)->getStereo() == Bond::BondStereo::STEREOANY);

    bool clearDoubleBondFlags = true;
    addWavyBondsForStereoAny(*mol, clearDoubleBondFlags,
                             StereoBondThresholds::DBL_BOND_SPECIFIED_STEREO);
    CHECK(mol->getBondWithIdx(2)->getBondDir() == Bond::BondDir::UNKNOWN);
    CHECK(mol->getBondWithIdx(3)->getStereo() == Bond::BondStereo::STEREONONE);
  }
  SECTION("multiple bonds to wedge") {
    auto mol = "CCC(C)=CC=C(CC)C=CC(C)=CC"_smiles;
    REQUIRE(mol);
    mol->getBondWithIdx(3)->setStereoAtoms(3, 5);
    mol->getBondWithIdx(3)->setStereo(Bond::BondStereo::STEREOCIS);
    mol->getBondWithIdx(9)->setStereoAtoms(6, 11);
    mol->getBondWithIdx(9)->setStereo(Bond::BondStereo::STEREOANY);
    mol->getBondWithIdx(5)->setStereoAtoms(4, 7);
    mol->getBondWithIdx(5)->setStereo(Bond::BondStereo::STEREOANY);
    addWavyBondsForStereoAny(*mol);
    CHECK(mol->getBondWithIdx(9)->getStereo() == Bond::BondStereo::STEREONONE);
    CHECK(mol->getBondWithIdx(5)->getStereo() == Bond::BondStereo::STEREONONE);
    CHECK(mol->getBondWithIdx(8)->getBondDir() == Bond::BondDir::UNKNOWN);
    for (const auto bond : mol->bonds()) {
      if (bond->getBondType() == Bond::BondType::SINGLE &&
          bond->getIdx() != 8) {
        CHECK(bond->getBondDir() == Bond::BondDir::NONE);
      }
    }
  }
}

TEST_CASE("Github #4215: Ring stereo being discarded in spiro systems") {
  // Note: this bug is still there when using the legacy stereochemistry
  // assignment. It's "non-trivial" to fix there and we've opted not to
  UseLegacyStereoPerceptionFixture reset_stereo_perception(false);

  SECTION("original failing example") {
    auto m = "C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2"_smiles;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
  }
  SECTION("original failing example, findPossible") {
    // UseLegacyStereoPerceptionFixture inner_reset_stereo_perception(true);
    SmilesParserParams ps2;
    ps2.sanitize = false;
    std::unique_ptr<RWMol> m{
        SmilesToMol("C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2", ps2)};
    REQUIRE(m);
    MolOps::sanitizeMol(*m);
    auto stereoInfo = Chirality::findPotentialStereo(*m, true, true);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(4)->getChiralTag() == Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(11)->getChiralTag() == Atom::CHI_UNSPECIFIED);
    REQUIRE(stereoInfo.size() == 4);
    for (const auto &si : stereoInfo) {
      CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
      if (si.centeredOn != 9) {
        CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
        CHECK(si.descriptor == Chirality::StereoDescriptor::None);
      } else {
        CHECK(si.specified == Chirality::StereoSpecified::Specified);
        CHECK(si.descriptor != Chirality::StereoDescriptor::None);
      }
    }
  }
  SECTION("original passing example") {
    auto m = "C[C@H]1CCC2(CC1)CC[C@H](C)CC2"_smiles;
    REQUIRE(m);
    // if the middle is unspecified, the two ends can't be specified
    CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(9)->getChiralTag() == Atom::CHI_UNSPECIFIED);

    {
      bool cleanIt = true;
      bool flagPossible = true;
      RWMol m2(*m);
      auto stereoInfo =
          Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 3);
      for (const auto &si : stereoInfo) {
        CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
        CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
        CHECK(si.descriptor == Chirality::StereoDescriptor::None);
      }
    }
    {
      bool cleanIt = true;
      bool flagPossible = false;
      RWMol m2(*m);
      auto stereoInfo =
          Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
      CHECK(stereoInfo.empty());
    }
  }
  SECTION("specified chirality on spiro atom") {
    auto m = "C[C@H]1CC[C@@]2(CC[C@H](C)CC2)CC1"_smiles;
    REQUIRE(m);
    // now the middle is specified, so the two ends are as well
    CHECK(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(7)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    {
      bool cleanIt = true;
      bool flagPossible = true;
      RWMol m2(*m);
      auto stereoInfo =
          Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 3);
      for (const auto &si : stereoInfo) {
        CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
        CHECK(si.specified == Chirality::StereoSpecified::Specified);
        CHECK(si.descriptor != Chirality::StereoDescriptor::None);
      }
    }
    {
      bool cleanIt = true;
      bool flagPossible = false;
      RWMol m2(*m);
      auto stereoInfo =
          Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 3);
      for (const auto &si : stereoInfo) {
        CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
        CHECK(si.specified == Chirality::StereoSpecified::Specified);
        CHECK(si.descriptor != Chirality::StereoDescriptor::None);
      }
    }
  }
  SECTION("three spiro rings, unspecified spiro links") {
    auto m = "C[C@H]1CCC2(CC1)CCC1(CC[C@H](C)CC1)CC2"_smiles;
    REQUIRE(m);
    {
      bool cleanIt = true;
      bool flagPossible = true;
      RWMol m2(*m);
      auto stereoInfo =
          Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 4);
      for (const auto &si : stereoInfo) {
        CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
        CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
        CHECK(si.descriptor == Chirality::StereoDescriptor::None);
      }
    }
  }
  SECTION("three spiro rings, specified spiro links") {
    auto m = "C[C@H]1CC[C@@]2(CC1)CC[C@]1(CC[C@H](C)CC1)CC2"_smiles;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(12)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    {
      bool cleanIt = true;
      bool flagPossible = true;
      RWMol m2(*m);
      auto stereoInfo =
          Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 4);
      for (const auto &si : stereoInfo) {
        CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
        CHECK(si.specified == Chirality::StereoSpecified::Specified);
        CHECK(si.descriptor != Chirality::StereoDescriptor::None);
      }
    }
  }
}

TEST_CASE(
    "Github #4215: Ring stereo being discarded in spiro systems, using deprecated useLegacyStereo option") {
  // Note: this bug is still there when using the legacy stereochemistry
  // assignment. It's "non-trivial" to fix there and we've opted not to
  UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
  SECTION("original failing example") {
    std::unique_ptr<RWMol> m{SmilesToMol("C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2")};
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
  }
  SECTION("original failing example, findPossible") {
    SmilesParserParams ps2;
    ps2.sanitize = false;
    std::unique_ptr<RWMol> m{
        SmilesToMol("C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2", ps2)};
    REQUIRE(m);
    MolOps::sanitizeMol(*m);
    auto stereoInfo = Chirality::findPotentialStereo(*m, true, true);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(4)->getChiralTag() == Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(11)->getChiralTag() == Atom::CHI_UNSPECIFIED);
    REQUIRE(stereoInfo.size() == 4);
    for (const auto &si : stereoInfo) {
      CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
      if (si.centeredOn != 9) {
        CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
        CHECK(si.descriptor == Chirality::StereoDescriptor::None);
      } else {
        CHECK(si.specified == Chirality::StereoSpecified::Specified);
        CHECK(si.descriptor != Chirality::StereoDescriptor::None);
      }
    }
  }
  SECTION("original passing example") {
    std::unique_ptr<RWMol> m{SmilesToMol("C[C@H]1CCC2(CC1)CC[C@H](C)CC2")};
    REQUIRE(m);
    // if the middle is unspecified, the two ends can't be specified
    CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(9)->getChiralTag() == Atom::CHI_UNSPECIFIED);

    {
      bool cleanIt = true;
      bool flagPossible = true;
      RWMol m2(*m);
      auto stereoInfo =
          Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 3);
      for (const auto &si : stereoInfo) {
        CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
        CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
        CHECK(si.descriptor == Chirality::StereoDescriptor::None);
      }
    }
    {
      bool cleanIt = true;
      bool flagPossible = false;
      RWMol m2(*m);
      auto stereoInfo =
          Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
      CHECK(stereoInfo.empty());
    }
  }
  SECTION("specified chirality on spiro atom") {
    std::unique_ptr<RWMol> m{SmilesToMol("C[C@H]1CC[C@@]2(CC[C@H](C)CC2)CC1")};
    REQUIRE(m);
    // now the middle is specified, so the two ends are as well
    CHECK(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(7)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    {
      bool cleanIt = true;
      bool flagPossible = true;
      RWMol m2(*m);
      auto stereoInfo =
          Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 3);
      for (const auto &si : stereoInfo) {
        CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
        CHECK(si.specified == Chirality::StereoSpecified::Specified);
        CHECK(si.descriptor != Chirality::StereoDescriptor::None);
      }
    }
    {
      bool cleanIt = true;
      bool flagPossible = false;
      RWMol m2(*m);
      auto stereoInfo =
          Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 3);
      for (const auto &si : stereoInfo) {
        CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
        CHECK(si.specified == Chirality::StereoSpecified::Specified);
        CHECK(si.descriptor != Chirality::StereoDescriptor::None);
      }
    }
  }
  SECTION("three spiro rings, unspecified spiro links") {
    std::unique_ptr<RWMol> m{
        SmilesToMol("C[C@H]1CCC2(CC1)CCC1(CC[C@H](C)CC1)CC2")};
    REQUIRE(m);
    {
      bool cleanIt = true;
      bool flagPossible = true;
      RWMol m2(*m);
      auto stereoInfo =
          Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 4);
      for (const auto &si : stereoInfo) {
        CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
        CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
        CHECK(si.descriptor == Chirality::StereoDescriptor::None);
      }
    }
  }
  SECTION("three spiro rings, specified spiro links") {
    std::unique_ptr<RWMol> m{
        SmilesToMol("C[C@H]1CC[C@@]2(CC1)CC[C@]1(CC[C@H](C)CC1)CC2")};
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(12)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    {
      bool cleanIt = true;
      bool flagPossible = true;
      RWMol m2(*m);
      auto stereoInfo =
          Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
      CHECK(stereoInfo.size() == 4);
      for (const auto &si : stereoInfo) {
        CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
        CHECK(si.specified == Chirality::StereoSpecified::Specified);
        CHECK(si.descriptor != Chirality::StereoDescriptor::None);
      }
    }
  }
}

TEST_CASE(
    "Github #4279: FindPotentialStereo() doesn't find *marked* ring stereo "
    "when flagPossible=False") {
  SECTION("base") {
    std::unique_ptr<RWMol> m{SmilesToMol("C[C@H]1CC[C@@H](C)CC1")};
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    bool cleanIt = true;
    bool flagPossible = false;
    auto stereoInfo = Chirality::findPotentialStereo(*m, cleanIt, flagPossible);
    for (const auto &si : stereoInfo) {
      CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(si.specified == Chirality::StereoSpecified::Specified);
      CHECK(si.descriptor != Chirality::StereoDescriptor::None);
    }
    CHECK(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED);
  }
}

TEST_CASE("StereoInfo comparisons") {
  Chirality::StereoInfo si1;
  si1.centeredOn = 3;
  CHECK(si1.type == Chirality::StereoType::Unspecified);
  si1.type = Chirality::StereoType::Atom_Tetrahedral;
  Chirality::StereoInfo si2;
  si2.centeredOn = 3;
  si2.type = Chirality::StereoType::Atom_Tetrahedral;
  CHECK(si1 == si2);
  si2.descriptor = Chirality::StereoDescriptor::Tet_CCW;
  CHECK(si1 != si2);
}

TEST_CASE("StereoGroup Testing") {
  SECTION("basics") {
    auto mol = "C[C@H](O)[C@@H](C)[C@H](F)Cl |o1:1,3,&2:5,r|"_smiles;
    REQUIRE(mol);
    CHECK(mol->getStereoGroups().size() == 2);
    StereoGroup cp(mol->getStereoGroups()[0]);
    CHECK(cp == mol->getStereoGroups()[0]);
    CHECK(cp != mol->getStereoGroups()[1]);

    std::vector<Atom *> toRemove{mol->getAtomWithIdx(1)};
    std::vector<StereoGroup> &sgs =
        const_cast<std::vector<StereoGroup> &>(mol->getStereoGroups());
    removeGroupsWithAtoms(toRemove, sgs);
    CHECK(mol->getStereoGroups().size() == 1);
  }
}

TEST_CASE("Removing stereogroups from unspecified atoms") {
  SECTION("basics") {
    auto mol = "C[C@](O)(Cl)F |o1:1|"_smiles;
    REQUIRE(mol);
    CHECK(mol->getStereoGroups().size() == 1);
    mol->getAtomWithIdx(1)->setChiralTag(Atom::ChiralType::CHI_UNSPECIFIED);
    Chirality::cleanupStereoGroups(*mol);
    CHECK(mol->getStereoGroups().empty());
  }
  SECTION("parsing") {
    auto mol = "C[C@](C)(Cl)F |o1:1|"_smiles;
    REQUIRE(mol);
    CHECK(mol->getStereoGroups().empty());
  }
  SECTION("partial group removal") {
    auto mol = "C[C@](C)(Cl)[C@H](F)Cl |o1:1,4|"_smiles;
    REQUIRE(mol);
    CHECK(mol->getStereoGroups().size() == 1);
    CHECK(mol->getStereoGroups()[0].getAtoms().size() == 1);
    CHECK(mol->getStereoGroups()[0].getAtoms()[0]->getIdx() == 4);
  }
}

TEST_CASE("replaceAtom and StereoGroups") {
  SECTION("basics") {
    auto mol = "C[C@](O)(Cl)[C@H](F)Cl |o1:1,4|"_smiles;
    REQUIRE(mol);
    CHECK(mol->getStereoGroups().size() == 1);
    CHECK(mol->getStereoGroups()[0].getAtoms().size() == 2);
    CHECK(mol->getStereoGroups()[0].getAtoms()[0] == mol->getAtomWithIdx(1));

    Atom acp(*mol->getAtomWithIdx(1));
    mol->replaceAtom(1, &acp);
    CHECK(mol->getStereoGroups().size() == 1);
    CHECK(mol->getStereoGroups()[0].getAtoms().size() == 2);
    CHECK(mol->getStereoGroups()[0].getAtoms()[0] == mol->getAtomWithIdx(1));
  }
}

TEST_CASE(
    "Github #5200: FindPotentialStereo does not clean stereoflags from atoms "
    "which cannot be stereocenters") {
  auto m = "CCF"_smiles;
  REQUIRE(m);
  m->getAtomWithIdx(1)->setChiralTag(Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
  bool cleanIt = true;
  auto sinfo = Chirality::findPotentialStereo(*m, cleanIt);
  CHECK(sinfo.empty());
  CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
        Atom::ChiralType::CHI_UNSPECIFIED);
}

TEST_CASE(
    "Github #5196: Zero & coordinate bonds are being taken into account for "
    "chirality") {
  RDLog::LogStateSetter setter;  // disable irritating warning messages
  auto mol = R"CTAB(
     RDKit          3D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 15 18 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C -0.136359 0.025241 -0.986870 0
M  V30 2 C 0.211183 -0.810922 0.138318 0
M  V30 3 C -0.446638 -0.713741 1.305561 0
M  V30 4 C -1.141107 0.914647 -0.916429 0
M  V30 5 R -1.628248 -0.983190 -0.411960 0
M  V30 6 H 0.392055 -0.106505 -1.920607 0
M  V30 7 H 0.974038 -1.568492 0.017171 0
M  V30 8 H -0.209921 -1.406535 2.084966 0
M  V30 9 H -1.378909 1.482059 -1.807349 0
M  V30 10 C -1.544607 0.306162 1.588191 0
M  V30 11 C -1.946856 1.186683 0.358271 0
M  V30 12 H -1.207983 0.944410 2.407927 0
M  V30 13 H -2.419549 -0.225146 1.965589 0
M  V30 14 H -3.006492 1.040978 0.144313 0
M  V30 15 H -1.830875 2.240146 0.620809 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 2 1
M  V30 2 2 3 2
M  V30 3 2 4 1
M  V30 4 0 1 5
M  V30 5 0 2 5
M  V30 6 0 3 5
M  V30 7 0 4 5
M  V30 8 1 1 6
M  V30 9 1 2 7
M  V30 10 1 3 8
M  V30 11 1 4 9
M  V30 12 1 10 3
M  V30 13 1 11 4
M  V30 14 1 11 10
M  V30 15 1 12 10
M  V30 16 1 13 10
M  V30 17 1 14 11
M  V30 18 1 15 11
M  V30 END BOND
M  V30 END CTAB
M  END)CTAB"_ctab;
  REQUIRE(mol);
  SECTION("as reported") {
    MolOps::assignStereochemistryFrom3D(*mol);
    for (auto aidx : {0, 1, 2, 3}) {
      CHECK(mol->getAtomWithIdx(aidx)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("as reported - ZOBs") {
    for (auto bidx : {3, 4, 5, 6}) {
      mol->getBondWithIdx(bidx)->setBondType(Bond::BondType::ZERO);
    }
    MolOps::assignStereochemistryFrom3D(*mol);
    for (auto idx : {0, 1, 2, 3}) {
      CHECK(mol->getAtomWithIdx(idx)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("as reported - datives") {
    for (auto bidx : {3, 4, 5, 6}) {
      mol->getBondWithIdx(bidx)->setBondType(Bond::BondType::DATIVE);
    }
    MolOps::assignStereochemistryFrom3D(*mol);
    for (auto idx : {0, 1, 2, 3}) {
      CHECK(mol->getAtomWithIdx(idx)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("as reported - reversed datives") {
    // structure is bogus, but we want to test
    for (auto bidx : {3, 4, 5, 6}) {
      auto bond = mol->getBondWithIdx(bidx);
      bond->setEndAtomIdx(bond->getBeginAtomIdx());
      bond->setBeginAtomIdx(4);
      bond->setBondType(Bond::BondType::DATIVE);
    }
    MolOps::assignStereochemistryFrom3D(*mol);
    for (auto idx : {0, 1, 2, 3}) {
      CHECK(mol->getAtomWithIdx(idx)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("as reported - singles") {
    // structure is bogus, but we want to test
    for (auto bidx : {3, 4, 5, 6}) {
      mol->getBondWithIdx(bidx)->setBondType(Bond::BondType::SINGLE);
    }
    MolOps::assignStereochemistryFrom3D(*mol);
    for (auto idx : {0, 1, 2, 3}) {
      CHECK(mol->getAtomWithIdx(idx)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("assignStereochemistry") {
    auto mol = "[Fe]C(=C)O |C:1.0|"_smiles;
    REQUIRE(mol);
    for (auto bt : {Bond::BondType::DATIVE, Bond::BondType::ZERO,
                    Bond::BondType::UNSPECIFIED}) {
      mol->getAtomWithIdx(1)->setChiralTag(
          Atom::ChiralType::CHI_TETRAHEDRAL_CW);
      mol->getBondWithIdx(0)->setBondType(bt);
      bool cleanit = true;
      bool force = true;
      MolOps::assignStereochemistry(*mol, cleanit, force);
      CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("isAtomPotentialTetrahedralCenter() and getStereoInfo()") {
    auto mol = "[Fe]C(=C)O |C:1.0|"_smiles;
    REQUIRE(mol);
    for (auto bt : {Bond::BondType::DATIVE, Bond::BondType::ZERO,
                    Bond::BondType::UNSPECIFIED}) {
      mol->getAtomWithIdx(1)->setChiralTag(
          Atom::ChiralType::CHI_TETRAHEDRAL_CW);
      mol->getBondWithIdx(0)->setBondType(bt);
      CHECK(!Chirality::detail::isAtomPotentialStereoAtom(
          mol->getAtomWithIdx(1)));
      bool cleanit = true;
      auto sinfo = Chirality::findPotentialStereo(*mol, cleanit);
      CHECK(sinfo.empty());
      CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
}

TEST_CASE(
    "Github #5239: Precondition violation on chiral Atoms with zero order "
    "bonds") {
  RDLog::LogStateSetter setter;  // disable irritating warning messages
  auto molblock = R"CTAB(
     RDKit          3D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 5 4 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C -0.446600 -0.713700 1.305600 0
M  V30 2 Fe -1.628200 -0.983200 -0.412000 0
M  V30 3 Cl -0.049300 -1.876700 2.613900 0
M  V30 4 C -1.544600 0.306200 1.588200 0
M  V30 5 F 0.673700 0.029200 0.993700 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 3
M  V30 2 1 1 4 CFG=1
M  V30 3 1 1 5
M  V30 4 0 2 1
M  V30 END BOND
M  V30 END CTAB
M  END)CTAB";
  bool sanitize = false;
  std::unique_ptr<ROMol> mol(MolBlockToMol(molblock, sanitize));
  REQUIRE(mol);
  MolOps::assignStereochemistryFrom3D(*mol);

  CHECK(mol->getAtomWithIdx(0)->getChiralTag() !=
        Atom::ChiralType::CHI_UNSPECIFIED);
}
TEST_CASE("nontetrahedral stereo from 3D", "[nontetrahedral]") {
  std::string pathName = getenv("RDBASE");
  pathName += "/Code/GraphMol/test_data/nontetrahedral_3d.sdf";
  SECTION("basics") {
    SDMolSupplier suppl(pathName);
    while (!suppl.atEnd()) {
      std::unique_ptr<ROMol> m{suppl.next()};
      REQUIRE(m);
      MolOps::assignChiralTypesFrom3D(*m);
      auto ct = m->getProp<std::string>("ChiralType");
      auto cp = m->getProp<unsigned>("ChiralPermutation");
      auto atom = m->getAtomWithIdx(0);

      if (ct == "SP") {
        CHECK(atom->getChiralTag() == Atom::ChiralType::CHI_SQUAREPLANAR);
      } else if (ct == "TB") {
        CHECK(atom->getChiralTag() ==
              Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL);
      } else if (ct == "TH") {
        CHECK(atom->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL);
      } else if (ct == "OH") {
        CHECK(atom->getChiralTag() == Atom::ChiralType::CHI_OCTAHEDRAL);
      }

      CHECK(atom->getProp<unsigned>(common_properties::_chiralPermutation) ==
            cp);
    }
  }
  SECTION("disable nontetrahedral stereo") {
    AllowNontetrahedralChiralityFixture reset_non_tetrahedral_allowed;
    Chirality::setAllowNontetrahedralChirality(false);
    SDMolSupplier suppl(pathName);
    while (!suppl.atEnd()) {
      std::unique_ptr<ROMol> m{suppl.next()};
      REQUIRE(m);
      MolOps::assignChiralTypesFrom3D(*m);
      auto ct = m->getProp<std::string>("ChiralType");
      auto atom = m->getAtomWithIdx(0);

      if (ct == "TH") {
        CHECK(atom->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL);
      } else {
        CHECK(atom->getChiralTag() == Atom::ChiralType::CHI_UNSPECIFIED);
      }
    }
  }
}

TEST_CASE("assignStereochemistry shouldn't remove nontetrahedral stereo",
          "[nontetrahedral]") {
  SECTION("basics") {
    SmilesParserParams parseps;
    parseps.sanitize = false;
    parseps.removeHs = false;
    std::unique_ptr<RWMol> m{SmilesToMol("F[Pt@TB1](O)(Br)(N)Cl", parseps)};
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL);
    bool cleanIt = true;
    bool force = true;
    MolOps::assignStereochemistry(*m, cleanIt, force);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL);
  }
  SECTION("standard SMILES parsing") {
    std::unique_ptr<RWMol> m{SmilesToMol("F[Pt@TB1](O)(Br)(N)Cl")};
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL);
  }
  SECTION("SMILES parsing w/o sanitization") {
    SmilesParserParams parseps;
    // we need to skip stereo assignment
    parseps.sanitize = false;
    std::unique_ptr<RWMol> m{SmilesToMol("F[Pt@TB1](O)(Br)(N)Cl", parseps)};
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL);
  }
}

TEST_CASE("remove hs and non-tetrahedral stereo", "[nontetrahedral]") {
  SmilesParserParams parseps;
  parseps.sanitize = false;
  parseps.removeHs = false;
  std::vector<std::string> smiles = {"F[Pt@TB1]([H])(Br)(N)Cl",
                                     "F[Pt@TB]([H])(Br)(N)Cl"};
  for (const auto &smi : smiles) {
    std::unique_ptr<RWMol> m{SmilesToMol(smi, parseps)};
    REQUIRE(m);
    CHECK(m->getNumAtoms(6));
    {
      // the default is to not remove Hs to non-tetrahedral stereocenters
      RWMol molcp(*m);
      MolOps::removeHs(molcp);
      CHECK(molcp.getNumAtoms() == 6);
    }
    {
      // but we can enable it
      RWMol molcp(*m);
      MolOps::RemoveHsParameters ps;
      ps.removeNontetrahedralNeighbors = true;
      MolOps::removeHs(molcp, ps);
      CHECK(molcp.getNumAtoms() == 5);
    }
    {
      // but we can enable it
      RWMol molcp(*m);
      MolOps::removeAllHs(molcp);
      CHECK(molcp.getNumAtoms() == 5);
    }
  }
}

TEST_CASE("getIdealAngle", "[nontetrahedral]") {
  SECTION("TB1") {
    auto m = "S[As@TB1](F)(Cl)(Br)N"_smiles;
    REQUIRE(m);
    CHECK(Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
                                                    m->getAtomWithIdx(0)));
    CHECK(Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
                                                    m->getAtomWithIdx(5)));
    CHECK(!Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
                                                     m->getAtomWithIdx(2)));
    CHECK(!Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
                                                     m->getAtomWithIdx(3)));
    CHECK(!Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
                                                     m->getAtomWithIdx(4)));
    CHECK(Chirality::getTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1))
              ->getIdx() == 0);
    CHECK(Chirality::getTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1), -1)
              ->getIdx() == 5);

    CHECK_THAT(
        Chirality::getIdealAngleBetweenLigands(
            m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(2)),
        Catch::Matchers::WithinAbs(90, 0.001));
    CHECK_THAT(
        Chirality::getIdealAngleBetweenLigands(
            m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(3)),
        Catch::Matchers::WithinAbs(90, 0.001));
    CHECK_THAT(
        Chirality::getIdealAngleBetweenLigands(
            m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(4)),
        Catch::Matchers::WithinAbs(90, 0.001));
    CHECK_THAT(
        Chirality::getIdealAngleBetweenLigands(
            m->getAtomWithIdx(1), m->getAtomWithIdx(2), m->getAtomWithIdx(3)),
        Catch::Matchers::WithinAbs(120, 0.001));
    CHECK_THAT(
        Chirality::getIdealAngleBetweenLigands(
            m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(5)),
        Catch::Matchers::WithinAbs(180, 0.001));
    CHECK_THAT(
        Chirality::getIdealAngleBetweenLigands(
            m->getAtomWithIdx(1), m->getAtomWithIdx(5), m->getAtomWithIdx(2)),
        Catch::Matchers::WithinAbs(90, 0.001));
    CHECK_THAT(
        Chirality::getIdealAngleBetweenLigands(
            m->getAtomWithIdx(1), m->getAtomWithIdx(5), m->getAtomWithIdx(3)),
        Catch::Matchers::WithinAbs(90, 0.001));
    CHECK_THAT(
        Chirality::getIdealAngleBetweenLigands(
            m->getAtomWithIdx(1), m->getAtomWithIdx(5), m->getAtomWithIdx(4)),
        Catch::Matchers::WithinAbs(90, 0.001));
  }
  SECTION("TB1 missing 1") {
    // S[As@TB1](F)(Cl)(Br)* => S[As@TB7](*)(F)(Cl)Br
    auto m = "S[As@TB7](F)(Cl)Br"_smiles;
    REQUIRE(m);

    CHECK(Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
                                                    m->getAtomWithIdx(0)));
    CHECK(!Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
                                                     m->getAtomWithIdx(2)));
    CHECK(!Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
                                                     m->getAtomWithIdx(3)));
    CHECK(!Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
                                                     m->getAtomWithIdx(4)));
    CHECK(Chirality::getTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1))
              ->getIdx() == 0);
    CHECK(Chirality::getTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
                                                     -1) == nullptr);

    CHECK_THAT(
        Chirality::getIdealAngleBetweenLigands(
            m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(2)),
        Catch::Matchers::WithinAbs(90, 0.001));
    CHECK_THAT(
        Chirality::getIdealAngleBetweenLigands(
            m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(3)),
        Catch::Matchers::WithinAbs(90, 0.001));
    CHECK_THAT(
        Chirality::getIdealAngleBetweenLigands(
            m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(4)),
        Catch::Matchers::WithinAbs(90, 0.001));
    CHECK_THAT(
        Chirality::getIdealAngleBetweenLigands(
            m->getAtomWithIdx(1), m->getAtomWithIdx(2), m->getAtomWithIdx(3)),
        Catch::Matchers::WithinAbs(120, 0.001));
  }
}

TEST_CASE("getChiralPermutation", "[nontetrahedral]") {
  SECTION("TB1") {
    // clang-format off
    std::vector<std::pair<std::list<int>, unsigned int>> data = {
        {{2, 3, 4, 5, 6}, 1},
        {{2, 3, 5, 4, 6}, 2},

        {{2, 3, 4, 6, 5}, 3},
        {{2, 3, 5, 6, 4}, 4},

        {{2, 3, 6, 4, 5}, 5},
        {{2, 3, 6, 5, 4}, 6},

        {{2, 6, 3, 4, 5}, 7},
        {{2, 6, 3, 5, 4}, 8},

        {{3, 2, 4, 5, 6}, 9},
        {{3, 2, 5, 4, 6}, 11},

        {{3, 2, 4, 6, 5}, 10},
        {{3, 2, 5, 6, 4}, 12},

        {{3, 2, 6, 4, 5}, 13},
        {{3, 2, 6, 5, 4}, 14},

        {{3, 4, 2, 5, 6}, 15},
        {{3, 5, 2, 4, 6}, 20},

        {{3, 4, 2, 6, 5}, 16},
        {{3, 5, 2, 6, 4}, 19},

        {{3, 4, 5, 2, 6}, 17},
        {{3, 5, 4, 2, 6}, 18},


    };
    // clang-format on
    auto m = "CCS[As@TB1](F)(Cl)(Br)N"_smiles;
    REQUIRE(m);
    const auto atm = m->getAtomWithIdx(3);
    for (const auto &pr : data) {
      // std::cerr << "---- " << pr.second << std::endl;
      CHECK(Chirality::getChiralPermutation(atm, pr.first) == pr.second);
    }
  }

  SECTION("SP1") {
    // clang-format off
    std::vector<std::pair<std::list<int>, unsigned int>> data = {
        {{2, 3, 4, 5}, 1},
        {{2, 4, 3, 5}, 2},
        {{2, 3, 5, 4}, 3},
    };
    // clang-format on
    auto m = "CCC[Pt@SP1](F)(Cl)N"_smiles;
    REQUIRE(m);
    const auto atm = m->getAtomWithIdx(3);
    for (const auto &pr : data) {
      // std::cerr << "---- " << pr.second << std::endl;
      CHECK(Chirality::getChiralPermutation(atm, pr.first) == pr.second);
    }
  }
  SECTION("OH1") {
    // clang-format off
    std::vector<std::pair<std::list<int>, unsigned int>> data = {
        {{2, 3, 4, 5, 6, 7}, 1},
        {{2, 3, 6, 5, 4, 7}, 2},

        {{2, 3, 4, 5, 7, 6}, 3},
        {{2, 3, 6, 5, 7, 4}, 16},

        {{2, 3, 4, 7, 5, 6}, 6},
        {{2, 3, 6, 7, 5, 4}, 18},

        {{2, 3, 7, 4, 5, 6}, 19},
        {{2, 3, 7, 6, 5, 4}, 24},

        {{2, 7, 3, 4, 5, 6}, 25},
        {{2, 7, 3, 6, 5, 4}, 30},

        {{2, 3, 4, 6, 5, 7}, 4},
        {{2, 3, 6, 4, 5, 7}, 14},

        {{2, 3, 4, 6, 7, 5}, 5},
        {{2, 3, 6, 4, 7, 5}, 15},

        {{2, 3, 4, 7, 6, 5}, 7},
        {{2, 3, 6, 7, 4, 5}, 17},

        {{2, 3, 7, 4, 6, 5}, 20},
        {{2, 3, 7, 6, 4, 5}, 23},

        {{2, 7, 3, 4, 6, 5}, 26},
        {{2, 7, 3, 6, 4, 5}, 29},

        {{2, 3, 5, 6, 4, 7}, 10},
        {{2, 3, 5, 4, 6, 7}, 8},

        {{2, 3, 5, 6, 7, 4}, 11},
        {{2, 3, 5, 4, 7, 6}, 9},

        {{2, 3, 5, 7, 6, 4}, 13},
        {{2, 3, 5, 7, 4, 6}, 12},

        {{2, 3, 7, 5, 6, 4}, 22},
        {{2, 3, 7, 5, 4, 6}, 21},

        {{2, 7, 3, 5, 6, 4}, 28},
        {{2, 7, 3, 5, 4, 6}, 27},

    };
    // clang-format on
    auto m = "CCO[Co@OH1](Cl)(C)(N)(F)P"_smiles;
    REQUIRE(m);
    const auto atm = m->getAtomWithIdx(3);
    for (const auto &pr : data) {
      // std::cerr << "---- " << pr.second << std::endl;
      CHECK(Chirality::getChiralPermutation(atm, pr.first) == pr.second);
    }
  }
}

TEST_CASE("isAtomPotentialNontetrahedralCenter", "[nontetrahedral]") {
  SECTION("basics") {
    {
      auto mol = "C[S+](O)F"_smiles;
      REQUIRE(mol);
      CHECK(!Chirality::detail::isAtomPotentialNontetrahedralCenter(
          mol->getAtomWithIdx(1)));
    }
    {
      auto mol = "C[SH](O)F"_smiles;
      REQUIRE(mol);
      CHECK(Chirality::detail::isAtomPotentialNontetrahedralCenter(
          mol->getAtomWithIdx(1)));
    }
    {
      auto mol = "C[S@SP](O)F"_smiles;
      REQUIRE(mol);
      CHECK(Chirality::detail::isAtomPotentialNontetrahedralCenter(
          mol->getAtomWithIdx(1)));
    }
  }
}
TEST_CASE("nontetrahedral StereoInfo", "[nontetrahedral]") {
  SECTION("SP") {
    auto m = "C[Pt@SP1](F)(Cl)O"_smiles;
    REQUIRE(m);
    auto sinfo = Chirality::findPotentialStereo(*m);
    REQUIRE(sinfo.size() == 1);
    CHECK(sinfo[0].centeredOn == 1);
    CHECK(sinfo[0].type == Chirality::StereoType::Atom_SquarePlanar);
    CHECK(sinfo[0].descriptor == Chirality::StereoDescriptor::None);
    CHECK(sinfo[0].permutation == 1);
    CHECK(sinfo[0].controllingAtoms == std::vector<unsigned int>{0, 2, 3, 4});
  }
  SECTION("TB") {
    auto m = "C[Fe@TB4](F)(Cl)(O)N"_smiles;
    REQUIRE(m);
    auto sinfo = Chirality::findPotentialStereo(*m);
    REQUIRE(sinfo.size() == 1);
    CHECK(sinfo[0].centeredOn == 1);
    CHECK(sinfo[0].type == Chirality::StereoType::Atom_TrigonalBipyramidal);
    CHECK(sinfo[0].descriptor == Chirality::StereoDescriptor::None);
    CHECK(sinfo[0].permutation == 4);
    CHECK(sinfo[0].controllingAtoms ==
          std::vector<unsigned int>{0, 2, 3, 4, 5});
  }
  SECTION("TB0") {
    auto m = "C[Fe@TB](F)(Cl)(O)N"_smiles;
    REQUIRE(m);
    auto sinfo = Chirality::findPotentialStereo(*m);
    REQUIRE(sinfo.size() == 1);
    CHECK(sinfo[0].centeredOn == 1);
    CHECK(sinfo[0].specified == Chirality::StereoSpecified::Unknown);
    CHECK(sinfo[0].type == Chirality::StereoType::Atom_TrigonalBipyramidal);
    CHECK(sinfo[0].descriptor == Chirality::StereoDescriptor::None);
    CHECK(sinfo[0].permutation == 0);
    CHECK(sinfo[0].controllingAtoms ==
          std::vector<unsigned int>{0, 2, 3, 4, 5});
  }
  SECTION("perceived as possible") {
    auto m = "C[Fe](F)(Cl)(O)N"_smiles;
    REQUIRE(m);
    auto sinfo = Chirality::findPotentialStereo(*m);
    REQUIRE(sinfo.size() == 1);
    CHECK(sinfo[0].centeredOn == 1);
    CHECK(sinfo[0].specified == Chirality::StereoSpecified::Unspecified);
    CHECK(sinfo[0].type == Chirality::StereoType::Atom_TrigonalBipyramidal);
    CHECK(sinfo[0].descriptor == Chirality::StereoDescriptor::None);
    CHECK(sinfo[0].permutation == 0);
    CHECK(sinfo[0].controllingAtoms ==
          std::vector<unsigned int>{0, 2, 3, 4, 5});
  }

  SECTION("OH") {
    auto m = "C[Fe@OH9](F)(Cl)(O)(N)Br"_smiles;
    REQUIRE(m);
    auto sinfo = Chirality::findPotentialStereo(*m);
    REQUIRE(sinfo.size() == 1);
    CHECK(sinfo[0].centeredOn == 1);
    CHECK(sinfo[0].type == Chirality::StereoType::Atom_Octahedral);
    CHECK(sinfo[0].descriptor == Chirality::StereoDescriptor::None);
    CHECK(sinfo[0].permutation == 9);
    CHECK(sinfo[0].controllingAtoms ==
          std::vector<unsigned int>{0, 2, 3, 4, 5, 6});
  }
  SECTION("OH missing ligand") {
    // C[Fe@OH9](F)(Cl)(O)(N)* => C[Fe@OH4](*)(F)(Cl)(O)N
    auto m = "C[Fe@OH4](F)(Cl)(O)N"_smiles;
    REQUIRE(m);
    auto sinfo = Chirality::findPotentialStereo(*m);
    REQUIRE(sinfo.size() == 1);
    CHECK(sinfo[0].centeredOn == 1);
    CHECK(sinfo[0].type == Chirality::StereoType::Atom_Octahedral);
    CHECK(sinfo[0].descriptor == Chirality::StereoDescriptor::None);
    CHECK(sinfo[0].permutation == 9);
    CHECK(sinfo[0].controllingAtoms ==
          std::vector<unsigned int>{0, 2, 3, 4, 5});
  }
}

TEST_CASE("github #5328: assignChiralTypesFrom3D() ignores wiggly bonds") {
  SECTION("basics") {
    auto m = R"CTAB(
     RDKit          3D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 5 4 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C 0.900794 -0.086835 0.009340 0
M  V30 2 C -0.552652 0.319534 0.077502 0
M  V30 3 F -0.861497 0.413307 1.437370 0
M  V30 4 Cl -0.784572 1.925710 -0.672698 0
M  V30 5 O -1.402227 -0.583223 -0.509512 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 1 2 4 CFG=2
M  V30 4 1 2 5
M  V30 END BOND
M  V30 END CTAB
M  END)CTAB"_ctab;
    REQUIRE(m);
    MolOps::assignChiralTypesFrom3D(*m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_UNSPECIFIED);
  }
  SECTION("non-tetrahedral") {
    auto m = R"CTAB(
  Mrv2108 05252216313D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 6 5 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C -1.7191 0.2488 -3.5085 0
M  V30 2 As -1.0558 1.9209 -2.6345 0
M  V30 3 F -0.4636 3.422 -1.7567 0
M  V30 4 O -2.808 2.4243 -2.1757 0
M  V30 5 Cl -0.1145 2.6609 -4.5048 0
M  V30 6 Br 0.2255 0.6458 -1.079 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 1 2 4
M  V30 4 1 2 5 CFG=2
M  V30 5 1 2 6
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
    REQUIRE(m);
    MolOps::assignChiralTypesFrom3D(*m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_UNSPECIFIED);
  }
}

TEST_CASE("useLegacyStereoPerception feature flag") {
  UseLegacyStereoPerceptionFixture reset_stereo_perception(true);

  SECTION("original failing example") {
    Chirality::setUseLegacyStereoPerception(true);
    auto m = "C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2"_smiles;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
  }
  SECTION("use new code") {
    Chirality::setUseLegacyStereoPerception(false);
    auto m = "C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2"_smiles;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
  }
  std::string molblock = R"CTAB(
  Mrv2108 05202206352D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 14 15 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C -4.5417 3.165 0 0
M  V30 2 C -5.8753 2.395 0 0
M  V30 3 C -5.8753 0.855 0 0
M  V30 4 C -4.5417 0.085 0 0 CFG=1
M  V30 5 C -3.208 0.855 0 0
M  V30 6 C -3.208 2.395 0 0
M  V30 7 C -4.5417 -1.455 0 0
M  V30 8 C -1.8743 0.085 0 0
M  V30 9 C -4.5417 6.2451 0 0 CFG=2
M  V30 10 C -5.8753 5.4751 0 0
M  V30 11 C -5.8753 3.9351 0 0
M  V30 12 C -3.208 3.9351 0 0
M  V30 13 C -3.208 5.4751 0 0
M  V30 14 C -4.5417 7.7851 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 1 3 4
M  V30 4 1 4 5
M  V30 5 1 5 6
M  V30 6 1 1 6
M  V30 7 1 4 7 CFG=1
M  V30 8 1 5 8
M  V30 9 1 9 10
M  V30 10 1 10 11
M  V30 11 1 12 13
M  V30 12 1 9 13
M  V30 13 1 11 1
M  V30 14 1 1 12
M  V30 15 1 9 14 CFG=1
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB";
  SECTION("original example, from mol block") {
    Chirality::setUseLegacyStereoPerception(true);
    std::unique_ptr<RWMol> m{MolBlockToMol(molblock)};
    CHECK(m->getAtomWithIdx(3)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(8)->getChiralTag() == Atom::CHI_UNSPECIFIED);
  }
  SECTION("original example, from mol block, new code") {
    Chirality::setUseLegacyStereoPerception(false);
    std::unique_ptr<RWMol> m{MolBlockToMol(molblock)};
    CHECK(m->getAtomWithIdx(3)->getChiralTag() != Atom::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(8)->getChiralTag() == Atom::CHI_UNSPECIFIED);
  }
}

TEST_CASE("wedgeMolBonds to aromatic rings") {
  auto m = R"CTAB(
     RDKit          2D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 10 11 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C 2.948889 -2.986305 0.000000 0
M  V30 2 C 2.560660 -4.435194 0.000000 0
M  V30 3 N 1.111771 -4.046965 0.000000 0
M  V30 4 C 1.500000 -2.598076 0.000000 0
M  V30 5 C 0.750000 -1.299038 0.000000 0
M  V30 6 C 1.500000 0.000000 0.000000 0
M  V30 7 C 0.750000 1.299038 0.000000 0
M  V30 8 C -0.750000 1.299038 0.000000 0
M  V30 9 C -1.500000 0.000000 0.000000 0
M  V30 10 C -0.750000 -1.299038 0.000000 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 1 3 4
M  V30 4 1 4 5 CFG=1
M  V30 5 2 5 6
M  V30 6 1 6 7
M  V30 7 2 7 8
M  V30 8 1 8 9
M  V30 9 2 9 10
M  V30 10 1 4 1
M  V30 11 1 10 5
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
  REQUIRE(m);
  CHECK(m->getAtomWithIdx(3)->getChiralTag() !=
        Atom::ChiralType::CHI_UNSPECIFIED);
  CHECK(m->getBondWithIdx(2)->getBondDir() == Bond::BondDir::NONE);
  CHECK(m->getBondWithIdx(3)->getBondDir() == Bond::BondDir::NONE);

  SECTION("generating mol blocks") {
    auto mb = MolToV3KMolBlock(*m);
    CHECK(mb.find("M  V30 10 1 4 1 CFG=1") == std::string::npos);
    CHECK(mb.find("M  V30 4 1 4 5 CFG=1") != std::string::npos);
  }

  SECTION("details: pickBondsWedge()") {
    // this is with aromatic bonds
    auto wedgedBonds = Chirality::pickBondsToWedge(*m);
    CHECK(wedgedBonds.at(3)->getIdx() == 3);
    RWMol cp(*m);

    // now try kekulized:
    MolOps::Kekulize(cp);
    wedgedBonds = Chirality::pickBondsToWedge(cp);
    CHECK(wedgedBonds.at(3)->getIdx() == 3);
  }
}

TEST_CASE("github 5307: AssignAtomChiralTagsFromStructure ignores Hydrogens") {
  std::string mb = R"CTAB(
     RDKit          3D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 5 4 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C -0.022097 0.003215 0.016520 0
M  V30 2 H -0.669009 0.889360 -0.100909 0
M  V30 3 H -0.377788 -0.857752 -0.588296 0
M  V30 4 H 0.096421 -0.315125 1.063781 0
M  V30 5 H 0.972473 0.280302 -0.391096 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 1 3
M  V30 3 1 1 4
M  V30 4 1 1 5
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB";
  bool sanitize = true;
  bool removeHs = false;
  std::unique_ptr<RWMol> m{MolBlockToMol(mb, sanitize, removeHs)};
  REQUIRE(m);
  MolOps::assignChiralTypesFrom3D(*m);
  CHECK(m->getAtomWithIdx(0)->getChiralTag() !=
        Atom::ChiralType::CHI_UNSPECIFIED);
  // assignStereochemistryFrom3D() actually checks:
  MolOps::assignStereochemistryFrom3D(*m);
  CHECK(m->getAtomWithIdx(0)->getChiralTag() ==
        Atom::ChiralType::CHI_UNSPECIFIED);
}

TEST_CASE(
    "github 5403: Incorrect perception of pseudoasymmetric centers on "
    "non-canonical molecules") {
  SECTION("basics") {
    auto mol1 = "N[C@@]12CC[C@@](CC1)(C2)C(F)F"_smiles;
    REQUIRE(mol1);

    bool clean = true;

    auto stereoInfo1 = Chirality::findPotentialStereo(*mol1, clean);
    REQUIRE(stereoInfo1.size() == 2);
    REQUIRE(stereoInfo1[0].centeredOn == 1);
    REQUIRE(stereoInfo1[1].centeredOn == 4);
    CHECK(mol1->getAtomWithIdx(1)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(mol1->getAtomWithIdx(4)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);

    auto mol2 = "C1C[C@]2(C(F)F)CC[C@@]1(N)C2"_smiles;
    REQUIRE(mol2);

    auto stereoInfo2 = Chirality::findPotentialStereo(*mol2, clean);
    REQUIRE(stereoInfo2.size() == 2);
    CHECK(stereoInfo2[0].centeredOn == 2);
    CHECK(stereoInfo2[1].centeredOn == 8);
    {
      RWMol cp(*mol2);
      Chirality::findPotentialStereo(cp, true, false);
      CHECK(cp.getAtomWithIdx(2)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
      CHECK(cp.getAtomWithIdx(8)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("new stereo") {
    UseLegacyStereoPerceptionFixture reset_stereo_perception(false);

    auto mol1 = "N[C@@]12CC[C@@](CC1)(C2)C(F)F"_smiles;
    REQUIRE(mol1);
    CHECK(mol1->getAtomWithIdx(1)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(mol1->getAtomWithIdx(4)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
  }
}
TEST_CASE("assignStereochemistry sets bond stereo with new stereo perception") {
  SECTION("basics") {
    UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
    {
      auto m = "C/C=C/C"_smiles;
      REQUIRE(m);
      CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOTRANS);
      CHECK(m->getBondWithIdx(1)->getStereoAtoms() == std::vector<int>{0, 3});
    }
    {
      auto m = "C/C=C\\C"_smiles;
      REQUIRE(m);
      CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOCIS);
      CHECK(m->getBondWithIdx(1)->getStereoAtoms() == std::vector<int>{0, 3});
    }
    {
      auto m = "C(/C)=C/C"_smiles;
      REQUIRE(m);
      CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOCIS);
      CHECK(m->getBondWithIdx(1)->getStereoAtoms() == std::vector<int>{1, 3});
    }
    {
      auto m = "FC(/C)=C/C"_smiles;
      REQUIRE(m);
      CHECK(m->getBondWithIdx(2)->getStereo() == Bond::BondStereo::STEREOTRANS);
      CHECK(m->getBondWithIdx(2)->getStereoAtoms() == std::vector<int>{0, 4});
    }
    {
      auto m = "FC(/C)=C(F)/C"_smiles;
      REQUIRE(m);
      CHECK(m->getBondWithIdx(2)->getStereo() == Bond::BondStereo::STEREOCIS);
      CHECK(m->getBondWithIdx(2)->getStereoAtoms() == std::vector<int>{0, 4});
    }
  }
}

TEST_CASE("chiral duplicates") {
  UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
  SECTION("atom basics") {
    auto mol = "C[C@](F)([C@H](F)Cl)[C@H](F)Cl"_smiles;
    REQUIRE(mol);
    CHECK(mol->getAtomWithIdx(3)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    CHECK(mol->getAtomWithIdx(6)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_UNSPECIFIED);
  }
  SECTION("double bonds and atoms") {
    auto mol = "C/C(O)=C([C@H](F)Cl)/[C@H](F)Cl"_smiles;
    REQUIRE(mol);
    CHECK(mol->getAtomWithIdx(4)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    CHECK(mol->getAtomWithIdx(7)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    CHECK(mol->getBondWithIdx(2)->getStereo() == Bond::BondStereo::STEREONONE);
  }
  SECTION("double bonds and atoms 2") {
    auto mol = "C/C(O)=C([C@H](F)Cl)/[C@@H](F)Cl"_smiles;
    REQUIRE(mol);
    CHECK(mol->getAtomWithIdx(4)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    CHECK(mol->getAtomWithIdx(7)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CW);
    CHECK(mol->getBondWithIdx(2)->getStereo() != Bond::BondStereo::STEREONONE);
  }
  SECTION("double bonds and double bonds") {
    auto mol = "C/C(O)=C(/C=C/C)/C=C/C"_smiles;
    REQUIRE(mol);
    CHECK(mol->getBondWithIdx(4)->getStereo() == Bond::BondStereo::STEREOTRANS);
    CHECK(mol->getBondWithIdx(7)->getStereo() == Bond::BondStereo::STEREOTRANS);
    CHECK(mol->getBondWithIdx(2)->getStereo() == Bond::BondStereo::STEREONONE);
  }
  SECTION("double bonds and double bonds 2") {
    auto mol = "C/C(O)=C(/C=C/C)/C=C\\C"_smiles;
    REQUIRE(mol);
    CHECK(mol->getBondWithIdx(4)->getStereo() == Bond::BondStereo::STEREOTRANS);
    CHECK(mol->getBondWithIdx(7)->getStereo() == Bond::BondStereo::STEREOCIS);
    CHECK(mol->getBondWithIdx(2)->getStereo() != Bond::BondStereo::STEREONONE);
  }
}

TEST_CASE("more findPotential") {
  UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
  SECTION("basics") {
    {
      auto m = "O[C@H](C)CC(C)C[C@@H](C)O"_smiles;
      REQUIRE(m);
      auto si = Chirality::findPotentialStereo(*m, true, true);
      CHECK(si.size() == 2);
    }
    {
      auto m = "O[C@H](C)CC(C)C[C@H](C)O"_smiles;
      REQUIRE(m);
      auto si = Chirality::findPotentialStereo(*m, true, true);
      CHECK(si.size() == 3);
    }
    {
      auto m = "O[CH](C)C[C@H](C)C[CH](C)O"_smiles;
      REQUIRE(m);
      auto si = Chirality::findPotentialStereo(*m, true, true);
      CHECK(si.size() == 3);
    }
    {
      auto m = "O[CH](C)C[CH](C)C[CH](C)O"_smiles;
      REQUIRE(m);
      auto si = Chirality::findPotentialStereo(*m, true, true);
      CHECK(si.size() == 3);
    }
  }
  SECTION("double bond impact on atoms") {
    {
      auto m = "C[CH](/C=C/C)/C=C\\C"_smiles;
      REQUIRE(m);
      auto si = Chirality::findPotentialStereo(*m, true, true);
      CHECK(si.size() == 3);
    }
    {
      auto m = "C[CH](/C=C/C)/C=C/C"_smiles;
      REQUIRE(m);
      auto si = Chirality::findPotentialStereo(*m, true, true);
      CHECK(si.size() == 2);
    }
    {
      auto m = "C[CH](C=CC)C=CC"_smiles;
      REQUIRE(m);
      auto si = Chirality::findPotentialStereo(*m, true, true);
      CHECK(si.size() == 3);
    }
    {
      auto m = "C[CH](C=CC)C=CC"_smiles;
      REQUIRE(m);
      m->getBondWithIdx(2)->setStereo(Bond::BondStereo::STEREOANY);
      m->getBondWithIdx(5)->setStereo(Bond::BondStereo::STEREOANY);
      auto si = Chirality::findPotentialStereo(*m, true, true);
      CHECK(si.size() == 3);
    }
  }
  SECTION("atom impact on double bonds") {
    {
      auto m = "CC=C([C@H](F)Cl)[C@@H](F)Cl"_smiles;
      REQUIRE(m);
      auto si = Chirality::findPotentialStereo(*m, true, true);
      CHECK(si.size() == 3);
    }
    {
      auto m = "CC=C([C@H](F)Cl)[C@H](F)Cl"_smiles;
      REQUIRE(m);
      auto si = Chirality::findPotentialStereo(*m, true, true);
      CHECK(si.size() == 2);
    }
    {
      auto m = "CC=C([CH](F)Cl)[CH](F)Cl"_smiles;
      REQUIRE(m);
      auto si = Chirality::findPotentialStereo(*m, true, true);
      CHECK(si.size() == 3);
    }
    {
      auto m = "CC=C([CH](F)Cl)[CH](F)Cl"_smiles;
      REQUIRE(m);
      m->getBondBetweenAtoms(2, 3)->setBondDir(Bond::UNKNOWN);
      m->getBondBetweenAtoms(2, 6)->setBondDir(Bond::UNKNOWN);
      auto si = Chirality::findPotentialStereo(*m, true, true);
      CHECK(si.size() == 3);
    }
  }
}
TEST_CASE("more findPotential and ring stereo") {
  UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
  SECTION("simple") {
    {
      auto m = "CC1CCC(C)CC1"_smiles;
      REQUIRE(m);
      auto stereoInfo = Chirality::findPotentialStereo(*m, true, true);
      REQUIRE(stereoInfo.size() == 2);
      CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].centeredOn == 1);
      CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);

      CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[1].centeredOn == 4);
      CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
    }
  }
}

TEST_CASE(
    "github 2984: RDKit misplaces stereochemistry/chirality information for "
    "small ring") {
  using namespace RDKit::Chirality;

  // UseLegacyStereoPerceptionFixture reset_stereo_perception(true);

  SECTION("The mol with the issue") {
    for (auto use_legacy : {false, true}) {
      Chirality::setUseLegacyStereoPerception(use_legacy);

      // With Legacy stereo, parsing the SMILES string will discard the
      // problematic stereo features, so findPotentialStereo (which uses
      // the new algorithm) will still find them, but they will be unspecified.
      // Parsing with the new algorithm will preserve the features, so they
      // can be correctly resolved.

      auto specified_status = use_legacy ? StereoSpecified::Unspecified
                                         : StereoSpecified::Specified;

      auto mol = R"SMI(CC/C=C1\C[C@H](O)C1)SMI"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 2);
      CHECK(stereoInfo[0].centeredOn == 5);
      CHECK(stereoInfo[0].type == StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].specified == specified_status);
      CHECK(stereoInfo[1].centeredOn == 2);
      CHECK(stereoInfo[1].type == StereoType::Bond_Double);
      CHECK(stereoInfo[1].specified == specified_status);
    }
  }

  // The other sections should yield the same results independently
  // of which stereo algoritm is used, new or legacy

  SECTION("Unspecified, but still potentially stereo") {
    for (auto use_legacy : {false, true}) {
      Chirality::setUseLegacyStereoPerception(use_legacy);

      auto mol = R"SMI(CCC=C1CC(O)C1)SMI"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 2);
      CHECK(stereoInfo[0].centeredOn == 5);
      CHECK(stereoInfo[0].type == StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].specified == StereoSpecified::Unspecified);
      CHECK(stereoInfo[1].centeredOn == 2);
      CHECK(stereoInfo[1].type == StereoType::Bond_Double);
      CHECK(stereoInfo[1].specified == StereoSpecified::Unspecified);
    }
  }

  SECTION("Specified") {
    for (auto use_legacy : {false, true}) {
      Chirality::setUseLegacyStereoPerception(use_legacy);

      auto mol = R"SMI(CC/C=C1\CC[C@H]1O)SMI"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 2);
      CHECK(stereoInfo[0].centeredOn == 6);
      CHECK(stereoInfo[0].type == StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].specified == StereoSpecified::Specified);
      CHECK(stereoInfo[1].centeredOn == 2);
      CHECK(stereoInfo[1].type == StereoType::Bond_Double);
      CHECK(stereoInfo[1].specified == StereoSpecified::Specified);
    }
  }

  SECTION("More than one ring (1)") {
    for (auto use_legacy : {false, true}) {
      Chirality::setUseLegacyStereoPerception(use_legacy);

      auto mol = R"SMI(CC\C=C/1C2CCC1[C@H]2O)SMI"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 4);
      CHECK(stereoInfo[0].centeredOn == 4);
      CHECK(stereoInfo[0].type == StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].specified == StereoSpecified::Unspecified);
      CHECK(stereoInfo[1].centeredOn == 7);
      CHECK(stereoInfo[1].type == StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[1].specified == StereoSpecified::Unspecified);
      CHECK(stereoInfo[2].centeredOn == 8);
      CHECK(stereoInfo[2].type == StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[2].specified == (use_legacy
                                            ? StereoSpecified::Unspecified
                                            : StereoSpecified::Specified));
      CHECK(stereoInfo[3].centeredOn == 2);
      CHECK(stereoInfo[3].type == StereoType::Bond_Double);
      CHECK(stereoInfo[3].specified == (use_legacy
                                            ? StereoSpecified::Unspecified
                                            : StereoSpecified::Specified));
    }
  }

  SECTION("More than one ring (2)") {
    for (auto use_legacy : {false, true}) {
      Chirality::setUseLegacyStereoPerception(use_legacy);

      // This is the same structure as previos section, but
      // the rings are defined in the opposite order
      auto mol = R"SMI(CC\C=C/1C2[C@H](O)C1CC2)SMI"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      REQUIRE(stereoInfo.size() == 4);
      CHECK(stereoInfo[0].centeredOn == 4);
      CHECK(stereoInfo[0].type == StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[0].specified == StereoSpecified::Unspecified);

      CHECK(stereoInfo[1].centeredOn == 5);
      CHECK(stereoInfo[1].type == StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[1].specified == (use_legacy
                                            ? StereoSpecified::Unspecified
                                            : StereoSpecified::Specified));

      CHECK(stereoInfo[2].centeredOn == 7);
      CHECK(stereoInfo[2].type == StereoType::Atom_Tetrahedral);
      CHECK(stereoInfo[2].specified == StereoSpecified::Unspecified);

      CHECK(stereoInfo[3].centeredOn == 2);
      CHECK(stereoInfo[3].type == StereoType::Bond_Double);
      CHECK(stereoInfo[3].specified == (use_legacy
                                            ? StereoSpecified::Unspecified
                                            : StereoSpecified::Specified));
    }
  }

  SECTION("Stereo not possible: symmetric opposite atom") {
    for (auto use_legacy : {false, true}) {
      Chirality::setUseLegacyStereoPerception(use_legacy);

      auto mol = R"SMI(CCC=C1CC(O)(O)C1)SMI"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      CHECK(stereoInfo.empty());
    }
  }

  SECTION("Stereo not possible: odd-sized ring") {
    for (auto use_legacy : {false, true}) {
      Chirality::setUseLegacyStereoPerception(use_legacy);

      auto mol = R"SMI(CCC=C1CCCC1)SMI"_smiles;
      REQUIRE(mol);
      auto stereoInfo = Chirality::findPotentialStereo(*mol);
      CHECK(stereoInfo.empty());
    }
  }
}

TEST_CASE("double bond stereo with new chirality perception") {
  UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
  SECTION("chain bonds") {
    {
      auto m = "C/C=C/C"_smiles;
      REQUIRE(m);
      CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOTRANS);
      CHECK(m->getBondWithIdx(1)->getStereoAtoms() == std::vector<int>{0, 3});
    }
  }
  SECTION("ring bonds") {
    {
      auto m = "C1/C=C/CCCCCCC1"_smiles;
      REQUIRE(m);
      CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOTRANS);
      CHECK(m->getBondWithIdx(1)->getStereoAtoms() == std::vector<int>{0, 3});
    }
  }
}

TEST_CASE("false positives from new stereo code") {
  SECTION("elements") {
    std::vector<std::string> examples{"P", "PC", "S", "SC", "S(F)C"};
    for (auto &smi : examples) {
      INFO(smi);
      std::unique_ptr<RWMol> m{SmilesToMol(smi)};
      REQUIRE(m);
      auto si = Chirality::findPotentialStereo(*m);
      CHECK(si.empty());
    }
  }
  SECTION("non-tetrahedral and implicit Hs") {
    std::vector<std::string> examples{
        "[SiH4]",         "[SiH3]C",      "[SH4]",     "[PH5]",
        "[PH4]C",         "[SH6]",        "[SH5]C",  //"[SiH2](C)C",
        "[PH3](C)C",      "[PH2](C)(C)C", "[SH4](C)C", "[SH3](C)(C)C",
        "[SH2](C)(C)(C)C"};
    {
      AllowNontetrahedralChiralityFixture reset_non_tetrahedral_allowed;
      Chirality::setAllowNontetrahedralChirality(false);
      for (auto &smi : examples) {
        INFO(smi);
        std::unique_ptr<RWMol> m{SmilesToMol(smi)};
        REQUIRE(m);
        auto si = Chirality::findPotentialStereo(*m);
        CHECK(si.empty());
      }
    }
    {
      AllowNontetrahedralChiralityFixture reset_non_tetrahedral_allowed;
      Chirality::setAllowNontetrahedralChirality(true);
      for (auto &smi : examples) {
        INFO(smi);
        std::unique_ptr<RWMol> m{SmilesToMol(smi)};
        REQUIRE(m);
        auto si = Chirality::findPotentialStereo(*m);
        CHECK(si.size() == 1);
      }
    }
  }
}

TEST_CASE(
    "Github #6049: Cyclobutyl group in a macrocycle triggers a stereo center") {
  SECTION("as reported") {
    auto mol = "O=S1(=O)C=CC=C2CCCCC3CC(C3)N21"_smiles;
    REQUIRE(mol);
    auto stereoInfo = Chirality::findPotentialStereo(*mol);
    for (const auto &sg : stereoInfo) {
      CHECK(sg.centeredOn != 15);
    }
  }
}

void testStereoValidationFromMol(std::string molBlock,
                                 std::string expectedSmiles, bool legacyFlag,
                                 bool canonicalFlag = false) {
  RDKit::Chirality::setUseLegacyStereoPerception(legacyFlag);

  std::unique_ptr<RWMol> mol(MolBlockToMol(molBlock, true, false, false));
  REQUIRE(mol);

  // CHECK(CIPLabeler::validateStereochem(*mol, validationFlags));

  RDKit::SmilesWriteParams smilesWriteParams;
  smilesWriteParams.doIsomericSmiles = true;
  smilesWriteParams.doKekule = false;
  smilesWriteParams.canonical = canonicalFlag;

  unsigned int flags = 0 |
                       RDKit::SmilesWrite::CXSmilesFields::CX_MOLFILE_VALUES |
                       RDKit::SmilesWrite::CXSmilesFields::CX_ATOM_PROPS |
                       RDKit::SmilesWrite::CXSmilesFields::CX_BOND_CFG |
                       RDKit::SmilesWrite::CXSmilesFields::CX_ENHANCEDSTEREO |
                       RDKit::SmilesWrite::CXSmilesFields::CX_SGROUPS |
                       RDKit::SmilesWrite::CXSmilesFields::CX_POLYMER;

  auto outSmiles = MolToCXSmiles(*mol, smilesWriteParams, flags);
  RDKit::Chirality::setUseLegacyStereoPerception(false);

  CHECK(outSmiles == expectedSmiles);
}

std::string validateStereoMolBlockSpiro = R"(
  Mrv2308 06232316112D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 13 14 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C -4.2921 0.9158 0 0
M  V30 2 C -4.2921 -0.6244 0 0
M  V30 3 C -2.9583 -1.3942 0 0 CFG=1
M  V30 4 C -1.6246 -0.6244 0 0
M  V30 5 C -1.6246 0.9158 0 0
M  V30 6 C -2.9583 4.7658 0 0 CFG=2
M  V30 7 C -4.2921 3.9958 0 0
M  V30 8 C -4.2921 2.4556 0 0
M  V30 9 C -2.9583 1.6858 0 0 CFG=2
M  V30 10 C -1.6246 2.4556 0 0
M  V30 11 C -1.6246 3.9958 0 0
M  V30 12 C -2.9583 6.3058 0 0
M  V30 13 Cl -2.9583 -2.9342 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 1 3 4
M  V30 4 1 4 5
M  V30 5 1 6 7
M  V30 6 1 6 11
M  V30 7 1 7 8
M  V30 8 1 9 8 CFG=1
M  V30 9 1 9 10
M  V30 10 1 10 11
M  V30 11 1 9 1
M  V30 12 1 9 5
M  V30 13 1 6 12 CFG=1
M  V30 14 1 3 13 CFG=1
M  V30 END BOND
M  V30 END CTAB
M  END

  )";

std::string validateStereoMolBlockDoubleBondNoStereo = R"(
  Mrv2308 06232316392D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 9 9 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C 0.8498 2.3564 0 0
M  V30 2 C 2.1835 1.5864 0 0
M  V30 3 C 2.1835 0.0464 0 0
M  V30 4 C -0.4839 1.5864 0 0
M  V30 5 O -1.8176 2.3564 0 0
M  V30 6 C -1.8176 3.8964 0 0
M  V30 7 C -3.3342 3.629 0 0
M  V30 8 O -0.4839 4.6664 0 0
M  V30 9 C 0.8498 3.8964 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 2 1 2
M  V30 2 1 2 3
M  V30 3 1 1 4
M  V30 4 1 4 5
M  V30 5 1 5 6
M  V30 6 1 6 7
M  V30 7 1 6 8
M  V30 8 1 8 9
M  V30 9 1 1 9
M  V30 END BOND
M  V30 END CTAB
M  END
  )";

std::string validateStereoMolBlockDoubleBondNoStereo2 = R"(
  Mrv0541 07011416342D

 21 22  0  0  0  0            999 V2000
   -1.9814    1.4834    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.9581   -2.7980    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.3658   -2.0787    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.8189    1.5764    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.5800    0.7796    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.9760    2.3967    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.1333   -2.7836    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.9581   -1.3592    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.7256   -2.0690    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.4882   -1.3401    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8471    2.4140    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.7304   -0.6351    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.9007   -0.6351    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.4834    0.0700    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.1333   -1.3497    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -3.3658    0.9831    0.0000 Br  0  0  0  0  0  0  0  0  0  0  0  0
   -1.9814    0.0525    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
   -0.7598    0.7854    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -0.3410    0.0700    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -1.6556    2.2396    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -2.2722    2.7980    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
 20  1  1  0  0  0  0
  4  1  2  0  0  0  0
  5  1  1  0  0  0  0
  2  7  2  0  0  0  0
  3  2  1  0  0  0  0
  3  8  2  0  0  0  0
  6  4  1  0  0  0  0
 16  4  1  0  0  0  0
 18  5  1  0  0  0  0
 17  5  2  0  0  0  0
 21  6  2  0  0  0  0
  7  9  1  0  0  0  0
  8 15  1  0  0  0  0
  9 15  2  0  0  0  0
 10 13  1  0  0  0  0
 11 20  1  0  0  0  0
 13 12  2  0  0  0  0
 15 12  1  0  0  0  0
 14 13  1  0  0  0  0
 19 14  2  0  0  0  0
 19 18  1  0  0  0  0
 21 20  1  0  0  0  0
M  END
> <Compound Name>
VM-0411129

> <CDD Number>
CDD-2839271

$$$$

)";

std::string validateStereoError1 = R"(
  -ISIS-  -- StrEd --

 29 32  0  0  0  0  0  0  0  0999 V2000
   -1.2050   -4.7172    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8959   -3.7660    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
    0.0823   -3.5582    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7514   -4.3013    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.7296   -4.0934    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.0385   -3.1425    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.3694   -2.3993    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.6785   -1.4481    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.0094   -0.7050    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0312   -0.9129    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.9470   -1.1209    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    1.3183    0.2461    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.2694    0.5550    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    2.2694    1.5549    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    1.3183    1.8641    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.0094    2.8151    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    1.6785    3.5582    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.3694    4.5093    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.3912    4.7172    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
   -0.2777    3.9739    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0312    3.0230    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7305    1.0550    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -0.2694    1.0550    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.7694    1.9210    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.7695    1.9210    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.2694    1.0550    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.7695    0.1889    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.7694    0.1889    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.3912   -2.6071    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
  2  3  1  0  0  0  0
  3  4  1  0  0  0  0
  4  5  2  0  0  0  0
  5  6  1  0  0  0  0
  6  7  2  0  0  0  0
  7  8  1  0  0  0  0
  8  9  2  0  0  0  0
  9 10  1  0  0  0  0
 10 11  3  0  0  0  0
  9 12  1  0  0  0  0
 12 13  2  0  0  0  0
 13 14  1  0  0  0  0
 14 15  2  0  0  0  0
 15 16  1  0  0  0  0
 16 17  1  0  0  0  0
 17 18  1  0  0  0  0
 18 19  1  0  0  0  0
 19 20  1  0  0  0  0
 20 21  1  0  0  0  0
 16 21  1  0  0  0  0
 15 22  1  0  0  0  0
 12 22  1  0  0  0  0
 22 23  1  0  0  0  0
 23 24  1  0  0  0  0
 24 25  2  0  0  0  0
 25 26  1  0  0  0  0
 26 27  2  0  0  0  0
 27 28  1  0  0  0  0
 23 28  2  0  0  0  0
  7 29  1  0  0  0  0
  3 29  2  0  0  0  0
M  END
> <Compound Name>
Z362114294

> <CDD Number>
CDD-3164311

$$$$

)";

std::string validateStereoUniq1 = R"(
  Mrv0541 06301412152D

 15 15  0  0  0  0            999 V2000
    1.0464   -0.3197    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    1.0464   -1.1460    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.7601   -1.5571    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    2.4739   -1.1460    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.4739   -0.3197    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.7601    0.0914    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.3309   -1.5706    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
    3.1912    0.0976    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.9103   -0.3114    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.1853    0.9197    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
    3.9115   -1.1336    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.7601    0.9135    0.0000 *   0  0  0  0  0  0  0  0  0  0  0  0
    4.6215   -1.5447    0.0000 *   0  0  0  0  0  0  0  0  0  0  0  0
    1.7601   -2.3793    0.0000 *   0  0  0  0  0  0  0  0  0  0  0  0
    3.1894   -1.5665    0.0000 *   0  0  0  0  0  0  0  0  0  0  0  0
  1  6  1  0  0  0  0
  1  2  1  0  0  0  0
  2  3  1  0  0  0  0
  2  7  2  0  0  0  0
  3  4  1  0  0  0  0
  3 14  1  0  0  0  0
  4 15  1  0  0  0  0
  4  5  2  0  0  0  0
  5  6  1  0  0  0  0
  5  8  1  0  0  0  0
  6 12  1  0  0  0  0
  8  9  1  0  0  0  0
  8 10  2  0  0  0  0
  9 11  2  0  0  0  0
 11 13  1  0  0  0  0
M  STY  4   1 SUP   2 SUP   3 SUP   4 SUP
M  SAL   1  1  12
M  SBL   1  1  11
M  SMT   1 R3a
M  SAP   1  1  12   6  1
M  SCL   1 CXN
M  SAL   2  1  13
M  SBL   2  1  15
M  SMT   2 R3b
M  SAP   2  1  13  11  1
M  SCL   2 CXN
M  SAL   3  1  14
M  SBL   3  1   6
M  SMT   3 R1
M  SAP   3  1  14   3  1
M  SCL   3 CXN
M  SAL   4  1  15
M  SBL   4  1   7
M  SMT   4 R2
M  SAP   4  1  15   4  1
M  SCL   4 CXN
M  END
> <Compound Name>
VM-0021367

> <CDD Number>
CDD-3832787

$$$$
)";

TEST_CASE("ValidateStereo", "[accurateCIP]") {
  SECTION("validateStereoUniqOldCanon1") {
    testStereoValidationFromMol(validateStereoUniq1,
                                "*/C=C/C(=O)C1=C(*)N(*)C(=O)NC1*", true, true);
  }

  SECTION("validateStereoUniqNewCanon1") {
    testStereoValidationFromMol(validateStereoUniq1,
                                "*/C=C/C(=O)C1=C(*)N(*)C(=O)NC1*", false, true);
  }

  SECTION("validateStereoUniqNewNoCanon1") {
    testStereoValidationFromMol(
        validateStereoUniq1, "N1C(=O)N(*)C(*)=C(C(/C=C/*)=O)C1*", false, false);
  }

  SECTION("SprioChiralLostOldNoCanon") {
    testStereoValidationFromMol(validateStereoMolBlockSpiro,
                                "C1C[C@H](Cl)CCC12CC[C@@H](C)CC2", true, false);
  }

  SECTION("SprioChiralLostOldCanon") {
    testStereoValidationFromMol(validateStereoMolBlockSpiro,
                                "C[C@H]1CCC2(CC1)CC[C@H](Cl)CC2", true, true);
  }

  SECTION("SprioChiralNotLostNewNoCanon") {
    testStereoValidationFromMol(validateStereoMolBlockSpiro,
                                "C1C[C@H](Cl)CC[C@]12CC[C@@H](C)CC2", false,
                                false);
  }

  SECTION("SprioChiralNotLostNewCanon") {
    testStereoValidationFromMol(validateStereoMolBlockSpiro,
                                "C[C@H]1CC[C@@]2(CC1)CC[C@H](Cl)CC2", false,
                                true);
  }

  SECTION("DoubleBondMarkedStereoOldNoCanon") {
    testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo,
                                "C1(=CC)COC(C)OC1", true, false);
  }
  SECTION("DoubleBondMarkedStereoNewNoCanon") {
    testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo,
                                "C1(=CC)COC(C)OC1", false, false);
  }

  SECTION("DoubleBondMarkedStereoOldCanon") {
    testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo,
                                "CC=C1COC(C)OC1", true, true);
  }

  SECTION("DoubleBondMarkedStereoNewCanon") {
    testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo,
                                "CC=C1COC(C)OC1", false, true);
  }

  SECTION("DoubleBondStereo2OldCanon") {
    testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo2,
                                "CC(/C=N/NC(=O)c1c(Br)cnn1C)=C\\c1ccccc1", true,
                                true);
  }
  SECTION("DoubleBondStereo2NewCanon") {
    testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo2,
                                "CC(=C\\c1ccccc1)/C=N/NC(=O)c1c(Br)cnn1C",
                                false, true);
  }

  SECTION("DoubleBondStereo2NewNoCanon") {
    testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo2,
                                "c1(C(=O)N/N=C/C(C)=C/c2ccccc2)c(Br)cnn1C",
                                false, false);
  }
  SECTION("DoubleBondStereo2OldNoCanon") {
    testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo2,
                                "c1(C(=O)N/N=C/C(C)=C/c2ccccc2)c(Br)cnn1C",
                                true, false);
  }

  SECTION("ValidateStereoError1OldCanon") {
    testStereoValidationFromMol(
        validateStereoError1,
        "COc1cccc(/C=C(\\C#N)c2nnc(N3CCOCC3)n2-c2ccccc2)c1", true, true);
  }
  SECTION("ValidateStereoError1NewCanon") {
    testStereoValidationFromMol(
        validateStereoError1,
        "COc1cccc(/C=C(\\C#N)c2nnc(N3CCOCC3)n2-c2ccccc2)c1", false, true);
  }
  SECTION("ValidateStereoError1OldNoCanon") {
    testStereoValidationFromMol(
        validateStereoError1,
        "COc1cccc(/C=C(\\C#N)c2nnc(N3CCOCC3)n2-c2ccccc2)c1", true, false);
  }
  SECTION("ValidateStereoError1NewNoCanon") {
    testStereoValidationFromMol(
        validateStereoError1,
        "COc1cccc(/C=C(\\C#N)c2nnc(N3CCOCC3)n2-c2ccccc2)c1", false, false);
  }
}

TEST_CASE(
    "assignStereochemistry should clear crossed double bonds that can't have stereo") {
  SECTION("basics") {
    auto m = "CC=C(C)C"_smiles;
    REQUIRE(m);
    m->getBondWithIdx(1)->setBondDir(Bond::BondDir::EITHERDOUBLE);
    bool clean = true;
    bool flag = true;
    bool force = true;
    {
      UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
      auto cp(*m);
      RDKit::MolOps::assignStereochemistry(cp, clean, force, flag);
      CHECK(cp.getBondWithIdx(1)->getBondDir() == Bond::BondDir::NONE);
    }
    {
      UseLegacyStereoPerceptionFixture reset_stereo_perception(true);
      auto cp(*m);
      RDKit::MolOps::assignStereochemistry(cp, clean, force, flag);
      CHECK(cp.getBondWithIdx(1)->getBondDir() == Bond::BondDir::NONE);
    }
  }
  SECTION("make sure we don't mess with actual potential stereosystems") {
    auto m = "CC=C(C)[13CH3]"_smiles;
    REQUIRE(m);
    m->getBondWithIdx(1)->setBondDir(Bond::BondDir::EITHERDOUBLE);
    bool clean = true;
    bool flag = true;
    bool force = true;
    {
      UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
      auto cp(*m);
      RDKit::MolOps::assignStereochemistry(cp, clean, force, flag);
      // the crossed bond dir has been translated to unknown stereo:
      CHECK(cp.getBondWithIdx(1)->getBondDir() == Bond::BondDir::NONE);
      CHECK(cp.getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOANY);
    }
    {
      UseLegacyStereoPerceptionFixture reset_stereo_perception(true);
      auto cp(*m);
      RDKit::MolOps::assignStereochemistry(cp, clean, force, flag);
      // the crossed bond dir has been translated to unknown stereo:
      CHECK(cp.getBondWithIdx(1)->getBondDir() == Bond::BondDir::NONE);
      CHECK(cp.getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOANY);
    }
  }
  SECTION("make sure stereoatoms are also cleared") {
    auto m = "CC=C(C)C"_smiles;
    REQUIRE(m);
    m->getBondWithIdx(1)->setBondDir(Bond::BondDir::EITHERDOUBLE);
    m->getBondWithIdx(1)->setStereoAtoms(0, 3);
    bool clean = true;
    bool flag = true;
    bool force = true;
    {
      UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
      auto cp(*m);
      RDKit::MolOps::assignStereochemistry(cp, clean, force, flag);
      CHECK(cp.getBondWithIdx(1)->getBondDir() == Bond::BondDir::NONE);
      CHECK(cp.getBondWithIdx(1)->getStereoAtoms().empty());
    }
    {
      UseLegacyStereoPerceptionFixture reset_stereo_perception(true);
      auto cp(*m);
      RDKit::MolOps::assignStereochemistry(cp, clean, force, flag);
      CHECK(cp.getBondWithIdx(1)->getBondDir() == Bond::BondDir::NONE);
      CHECK(cp.getBondWithIdx(1)->getStereoAtoms().empty());
    }
  }
  SECTION("ensure we can enumerate stereo on either double bonds") {
    auto mol = R"CTAB(
  Mrv2004 11072316002D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 8 8 0 0 1
M  V30 BEGIN ATOM
M  V30 1 C 1.859 2.7821 0 0
M  V30 2 C 3.2818 2.1928 0 0
M  V30 3 C 3.8711 0.77 0 0
M  V30 4 C 3.2818 -0.6528 0 0
M  V30 5 C 1.859 -1.2421 0 0
M  V30 6 C 0.4362 -0.6528 0 0
M  V30 7 C -0.1533 0.77 0 0
M  V30 8 C 0.4362 2.1928 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 1 4 3 CFG=2
M  V30 4 2 4 5
M  V30 5 1 5 6
M  V30 6 1 6 7
M  V30 7 1 7 8
M  V30 8 1 1 8
M  V30 END BOND
M  V30 END CTAB
M  END)CTAB"_ctab;
    // mol->debugMol(std::cerr);
    std::string smi = MolToCXSmiles(*mol, SmilesWriteParams());
    std::unique_ptr<ROMol> f(SmilesToMol(smi));
    mol->getBondWithIdx(3)->setStereo(Bond::BondStereo::STEREOCIS);
    f->getBondWithIdx(0)->setStereo(Bond::BondStereo::STEREOCIS);
    CHECK(MolToSmiles(*mol) == "C1=C\\CCCCCC/1");
    CHECK(MolToSmiles(*f) == "C1=C\\CCCCCC/1");
    mol->getBondWithIdx(3)->setStereo(Bond::BondStereo::STEREOTRANS);
    f->getBondWithIdx(0)->setStereo(Bond::BondStereo::STEREOTRANS);
    CHECK(MolToSmiles(*mol) == "C1=C/CCCCCC/1");
    CHECK(MolToSmiles(*f) == "C1=C/CCCCCC/1");
  }
}
TEST_CASE("adding two wedges to chiral centers") {
  SECTION("basics") {
    auto mol = R"CTAB(
  Mrv2219 02112315062D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 5 4 0 0 0
M  V30 BEGIN ATOM
M  V30 1 O -3.6667 2.5 0 0
M  V30 2 C -2.333 3.27 0 0 CFG=1
M  V30 3 F -0.9993 2.5 0 0
M  V30 4 C -3.103 4.6037 0 0
M  V30 5 N -1.3955 4.4918 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 1 2 4
M  V30 4 1 2 5 CFG=1
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
    REQUIRE(mol);
    CHECK(mol->getNumAtoms() == 5);

    Chirality::BondWedgingParameters ps;
    ps.wedgeTwoBondsIfPossible = true;

    {
      RWMol cp(*mol);
      Chirality::wedgeMolBonds(cp);
      CHECK(cp.getBondBetweenAtoms(1, 3)->getBondDir() != Bond::BondDir::NONE);
      CHECK(cp.getBondBetweenAtoms(1, 4)->getBondDir() == Bond::BondDir::NONE);
      CHECK(cp.getBondBetweenAtoms(1, 0)->getBondDir() == Bond::BondDir::NONE);
      CHECK(cp.getBondBetweenAtoms(1, 2)->getBondDir() == Bond::BondDir::NONE);
    }
    {
      RWMol cp(*mol);
      Chirality::wedgeMolBonds(cp, nullptr, &ps);
      CHECK(cp.getBondBetweenAtoms(1, 3)->getBondDir() != Bond::BondDir::NONE);
      CHECK(cp.getBondBetweenAtoms(1, 4)->getBondDir() != Bond::BondDir::NONE);
      CHECK(cp.getBondBetweenAtoms(1, 4)->getBondDir() !=
            cp.getBondBetweenAtoms(1, 3)->getBondDir());
      CHECK(cp.getBondBetweenAtoms(1, 0)->getBondDir() == Bond::BondDir::NONE);
      CHECK(cp.getBondBetweenAtoms(1, 2)->getBondDir() == Bond::BondDir::NONE);
    }
    {
      // Wedge a second bond after we already wedged a first one

      RWMol cp(*mol);
      Chirality::wedgeMolBonds(cp);
      CHECK(cp.getBondBetweenAtoms(1, 3)->getBondDir() != Bond::BondDir::NONE);
      CHECK(cp.getBondBetweenAtoms(1, 4)->getBondDir() == Bond::BondDir::NONE);
      CHECK(cp.getBondBetweenAtoms(1, 0)->getBondDir() == Bond::BondDir::NONE);
      CHECK(cp.getBondBetweenAtoms(1, 2)->getBondDir() == Bond::BondDir::NONE);

      Chirality::wedgeMolBonds(cp, nullptr, &ps);

      REQUIRE(count_wedged_bonds(cp) == 2);

      CHECK(cp.getBondBetweenAtoms(1, 3)->getBondDir() != Bond::BondDir::NONE);
      CHECK(cp.getBondBetweenAtoms(1, 4)->getBondDir() != Bond::BondDir::NONE);
      CHECK(cp.getBondBetweenAtoms(1, 4)->getBondDir() !=
            cp.getBondBetweenAtoms(1, 3)->getBondDir());
    }
    {
      // What if the first wedged bond is not our preferred one?

      for (auto wedgedAtomIdx : {0, 2, 4}) {
        INFO("wedgedAtomIdx: " << wedgedAtomIdx);

        RWMol cp(*mol);
        REQUIRE(count_wedged_bonds(cp) == 0);

        auto bond = cp.getBondBetweenAtoms(1, wedgedAtomIdx);
        if (bond->getEndAtomIdx() == 1) {
          // One of the bonds in the input is reversed, make sure the chiral
          // atom is always at the start so that the wedge is valid!
          auto tmp = bond->getBeginAtomIdx();
          bond->setBeginAtomIdx(bond->getEndAtomIdx());
          bond->setEndAtomIdx(tmp);
        }

        // This probably disagrees with the chirality in some of the test cases,
        // but that's not relevant for this test
        bond->setBondDir(Bond::BondDir::BEGINWEDGE);

        Chirality::wedgeMolBonds(cp, nullptr, &ps);

        CHECK(count_wedged_bonds(cp) == 2);
      }
    }
  }
  SECTION(
      "more complex 1, this should only have one wedge for each of the two chiral centers") {
    std::string smi =
        "[H][C@@]12CC(=O)N1[C@@H](C(=O)O)C(C)(C)S2(=O)=O |(-2.78382,0.183015,;-1.38222,-0.351313,;-2.12923,-1.65207,;-0.828466,-2.39908,;-0.436905,-3.84707,;-0.0814577,-1.09832,;1.03095,-0.0920638,;2.49888,-0.400554,;2.96569,-1.82607,;3.50001,0.71647,;0.41769,1.27685,;1.8432,1.74365,;0.102447,2.74335,;-1.07373,1.11662,;-1.07718,2.61662,;-2.56587,1.26998,)|";
    SmilesParserParams spps;
    spps.removeHs = false;
    std::unique_ptr<RWMol> m{SmilesToMol(smi, spps)};
    REQUIRE(m);
    Chirality::BondWedgingParameters bwps;
    bwps.wedgeTwoBondsIfPossible = true;
    Chirality::wedgeMolBonds(*m, &m->getConformer(), &bwps);
    unsigned nWedged = 0;
    for (const auto bond : m->bonds()) {
      if (bond->getBondDir() != Bond::BondDir::NONE) {
        ++nWedged;
      }
    }
    CHECK(nWedged == 2);
  }
  SECTION("more complex 2, have two wedges around the chiral center") {
    std::string smi =
        "[H][C@@]12CCCN1C(=O)CN1C(=O)[C@](C)(N)O[C@]12O |(-0.888297,0.626611,;-1.19852,-0.840959,;-1.94707,-2.14084,;-3.41464,-1.83061,;-3.5731,-0.339006,;-2.20347,0.272634,;-1.74154,1.69974,;-2.74648,2.81333,;-0.274666,2.01325,;0.730277,0.899655,;2.23028,0.901335,;3.11059,2.11585,;2.6954,-0.52473,;3.44685,-1.82293,;4.06503,0.0869091,;1.48286,-1.40777,;0.26835,-0.527448,;-0.0418744,-1.99502,)|";
    SmilesParserParams spps;
    spps.removeHs = false;
    std::unique_ptr<RWMol> m{SmilesToMol(smi, spps)};
    REQUIRE(m);
    Chirality::BondWedgingParameters bwps;
    bwps.wedgeTwoBondsIfPossible = true;
    Chirality::wedgeMolBonds(*m, &m->getConformer(), &bwps);
    CHECK(m->getBondWithIdx(12)->getBondDir() != Bond::BondDir::NONE);
    CHECK(m->getBondWithIdx(13)->getBondDir() != Bond::BondDir::NONE);
  }
  SECTION("another one") {
    auto m =
        "CC[C@@]1(O)C(=O)OCc2c1cc1-c3nc4ccccc4cc3Cn1c2=O |(-2.67178,3.55256,;-2.43493,2.07138,;-3.59925,1.12567,;-4.32841,2.43652,;-5.01681,0.635215,;-6.15033,1.61763,;-5.30084,-0.837648,;-4.16731,-1.82006,;-2.74976,-1.3296,;-2.46573,0.143259,;-1.04818,0.633713,;0.085343,-0.348697,;1.57389,-0.163687,;2.43243,1.06631,;3.92692,0.937796,;4.78546,2.1678,;6.27994,2.03928,;6.91588,0.680758,;6.05734,-0.549244,;4.56286,-0.420725,;3.70432,-1.65073,;2.20983,-1.52221,;1.11432,-2.54683,;-0.198688,-1.82156,;-1.61624,-2.31201,;-1.90027,-3.78488,)|"_smiles;
    REQUIRE(m);
    Chirality::BondWedgingParameters bwps;
    bwps.wedgeTwoBondsIfPossible = true;
    Chirality::wedgeMolBonds(*m, &m->getConformer(), &bwps);
    CHECK(m->getBondWithIdx(1)->getBondDir() != Bond::BondDir::NONE);
    CHECK(m->getBondWithIdx(1)->getBeginAtomIdx() == 2);
    CHECK(m->getBondWithIdx(2)->getBondDir() != Bond::BondDir::NONE);
    CHECK(m->getBondWithIdx(2)->getBeginAtomIdx() == 2);
  }
  SECTION("favor degree 1") {
    auto m =
        "[H][C@@]12CC[C@@](C)(O)[C@H](CC[C@@](C)(O)C=C)[C@@]1(C)CCCC2(C)C |(3.59567,-1.0058,;2.33379,-0.194852,;2.4456,-1.69068,;1.20608,-2.53542,;-0.145252,-1.88434,;-1.63777,-1.73471,;-0.551787,-3.3282,;-0.257063,-0.388514,;-1.60839,0.262569,;-2.84791,-0.582176,;-4.19924,0.068907,;-5.55057,0.71999,;-4.85032,-1.28242,;-3.54816,1.42024,;-4.3929,2.65976,;0.982456,0.456231,;-0.368873,1.10731,;0.870645,1.95206,;2.11016,2.7968,;3.46149,2.14572,;3.5733,0.649893,;5.02699,1.01975,;4.35205,-0.632117,)|"_smiles;
    REQUIRE(m);
    Chirality::BondWedgingParameters bwps;
    bwps.wedgeTwoBondsIfPossible = true;
    Chirality::wedgeMolBonds(*m, &m->getConformer(), &bwps);
    CHECK(m->getBondWithIdx(9)->getBondDir() != Bond::BondDir::NONE);
    CHECK(m->getBondWithIdx(10)->getBondDir() != Bond::BondDir::NONE);
    CHECK(m->getBondWithIdx(11)->getBondDir() == Bond::BondDir::NONE);
  }
}

TEST_CASE(
    "RDKit Issue #6217: Atoms may get flagged with non-tetrahedral stereo even when it is not allowed",
    "[bug][stereo][non-tetrahedral]") {
  UseLegacyStereoPerceptionFixture reset_stereo_perception(false);

  AllowNontetrahedralChiralityFixture reset_non_tetrahedral_allowed;
  Chirality::setAllowNontetrahedralChirality(false);

  auto m = "CS(=O)(=O)O"_smiles;
  REQUIRE(m);
  REQUIRE(m->getNumAtoms() == 5);

  auto stereoInfo = Chirality::findPotentialStereo(*m);
  CHECK(stereoInfo.size() == 0);

  auto at = m->getAtomWithIdx(1);

  auto sinfo = Chirality::detail::getStereoInfo(at);
  CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);

  REQUIRE(at->getAtomicNum() == 16);
  CHECK(!at->hasProp(common_properties::_ChiralityPossible));
}

TEST_CASE(
    "RDKit Issue #6239: Tri-coordinate atom with implicit + neighbor H atom is found potentially chiral",
    "[bug][stereo]") {
  // Parametrize test to run under legacy and new stereo perception
  const auto legacy_stereo = GENERATE(true, false);
  INFO("Legacy stereo perception == " << legacy_stereo);

  UseLegacyStereoPerceptionFixture reset_stereo_perception(legacy_stereo);

  auto p = SmilesParserParams();
  p.removeHs = false;

  std::unique_ptr<RWMol> m{SmilesToMol("[H]C(C)CC", p)};

  REQUIRE(m);
  REQUIRE(m->getNumAtoms() == 5);

  auto at = m->getAtomWithIdx(1);
  REQUIRE(at->getAtomicNum() == 6);
  REQUIRE(at->getDegree() == 3);

  CHECK(!at->hasProp(common_properties::_ChiralityPossible));

  CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(at));

  auto stereoInfo = Chirality::findPotentialStereo(*m);
  CHECK(stereoInfo.size() == 0);

  auto sinfo = Chirality::detail::getStereoInfo(at);
  CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
}

TEST_CASE("double bonded N with H should be stereogenic", "[bug][stereo]") {
  SECTION("assign stereo") {
    // Parametrize test to run under legacy and new stereo perception
    const auto legacy_stereo = GENERATE(true, false);
    INFO("Legacy stereo perception == " << legacy_stereo);

    UseLegacyStereoPerceptionFixture reset_stereo_perception(legacy_stereo);
    auto m = "[H]/N=C/F"_smiles;
    REQUIRE(m);
    CHECK(m->getBondWithIdx(1)->getStereo() != Bond::BondStereo::STEREONONE);
  }
  SECTION("find potential stereo") {
    auto m = "[H]/N=C/F"_smiles;
    REQUIRE(m);
    CHECK(Chirality::detail::isBondPotentialStereoBond(m->getBondWithIdx(1)));
    bool cleanIt = false;
    bool flagPossible = true;
    auto si = Chirality::findPotentialStereo(*m, cleanIt, flagPossible);
    CHECK(si.size() == 1);
  }
}

TEST_CASE("Issue in GitHub #6473", "[bug][stereo]") {
  constexpr const char *mb = R"CTAB(
     RDKit          2D

  6  5  0  0  0  0  0  0  0  0999 V2000
    2.0443    0.2759    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    0.7038    2.5963    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    3.3828    2.5961    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    2.0444    1.8228    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.6359    1.8229    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.3827   -0.4985    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  4  1  0
  4  2  1  0
  4  3  2  0
  2  5  1  0
  6  1  1  0
M  END)CTAB";

  auto use_legacy_stereo = GENERATE(true, false);
  CAPTURE(use_legacy_stereo);
  UseLegacyStereoPerceptionFixture reset_stereo_perception(use_legacy_stereo);

  std::unique_ptr<ROMol> mol(MolBlockToMol(mb));
  REQUIRE(mol);

  // Removal of the stereogenic H will cause loss of stereo information
  // on the imine double bond, and although the bond is still detected as
  // potentially stereo, it will be reverted to "unspecified"
  auto bond = mol->getBondWithIdx(2);
  REQUIRE(bond->getBondType() == Bond::BondType::DOUBLE);
  CHECK(Chirality::detail::isBondPotentialStereoBond(bond));
  CHECK(bond->getStereo() == Bond::BondStereo::STEREONONE);

  if (!use_legacy_stereo) {
    auto sinfo = Chirality::detail::getStereoInfo(bond);
    REQUIRE(sinfo.type == Chirality::StereoType::Bond_Double);
    CHECK(sinfo.specified == Chirality::StereoSpecified::Unspecified);
    REQUIRE(sinfo.controllingAtoms.size() == 4);
    CHECK(sinfo.controllingAtoms[0] == 0);
    CHECK(sinfo.controllingAtoms[1] == 1);
    CHECK(sinfo.controllingAtoms[2] == Atom::NOATOM);
    CHECK(sinfo.controllingAtoms[3] == Atom::NOATOM);
  }
}

TEST_CASE("GitHub Issue #6640", "[bug][stereo]") {
  UseLegacyStereoPerceptionFixture reset_stereo_perception(false);

  auto p = SmilesParserParams();
  p.sanitize = false;
  p.removeHs = false;
  std::string smiles{"NC1=NC(=N)N=C(N)C1C"};
  std::unique_ptr<RWMol> mol(SmilesToMol(smiles, p));
  REQUIRE(mol);

  MolOps::removeHs(*mol);

  auto cleanIt = true;
  auto force = true;
  auto flagPossibleStereoCenters = true;
  MolOps::assignStereochemistry(*mol, cleanIt, force,
                                flagPossibleStereoCenters);
}

TEST_CASE("zero bond-length chirality cases") {
  SECTION("basics") {
    {
      auto m = R"CTAB(derived from CHEMBL3183068
  Mrv2211 07202306222D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 10 10 0 0 1
M  V30 BEGIN ATOM
M  V30 1 N 3.231 0 0 0
M  V30 2 C 4.0137 -1.3378 0 0
M  V30 3 C 2.6757 -2.1004 0 0
M  V30 4 C 2.6757 -3.6389 0 0
M  V30 5 C 1.3378 -1.3378 0 0
M  V30 6 C 5.4672 -0.99 0 0
M  V30 7 C 1.3378 -4.4216 0 0
M  V30 8 C 0 -2.1205 0 0
M  V30 9 C 0 -3.659 0 0
M  V30 10 F 4.0137 -1.3378 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 2 1 CFG=3
M  V30 2 1 2 3
M  V30 3 1 2 6
M  V30 4 1 2 10
M  V30 5 2 3 4
M  V30 6 1 3 5
M  V30 7 1 4 7
M  V30 8 2 5 8
M  V30 9 2 7 9
M  V30 10 1 8 9
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
      REQUIRE(m);
      CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
    {
      auto m = R"CTAB(derived from CHEMBL3183068
  Mrv2211 07202306222D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 10 10 0 0 1
M  V30 BEGIN ATOM
M  V30 1 N 3.231 0 0 0
M  V30 2 C 4.0137 -1.3378 0 0
M  V30 3 C 2.6757 -2.1004 0 0
M  V30 4 C 2.6757 -3.6389 0 0
M  V30 5 C 1.3378 -1.3378 0 0
M  V30 6 C 4.0137 -1.3378 0 0
M  V30 7 C 1.3378 -4.4216 0 0
M  V30 8 C 0 -2.1205 0 0
M  V30 9 C 0 -3.659 0 0
M  V30 10 F 4.5135 -2.6451 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 2 1 CFG=3
M  V30 2 1 2 3
M  V30 3 1 2 6
M  V30 4 1 2 10
M  V30 5 2 3 4
M  V30 6 1 3 5
M  V30 7 1 4 7
M  V30 8 2 5 8
M  V30 9 2 7 9
M  V30 10 1 8 9
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
      REQUIRE(m);
      CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
}

TEST_CASE("t-shaped chirality cases") {
  SECTION("ChEMBL example") {
    {
      auto m = R"CTAB(CHEMBL3183068
     RDKit          2D

 11 11  0  0  1  0  0  0  0  0999 V2000
    1.7309    0.0000    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    2.1502   -0.7167    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.4334   -1.1252    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.4334   -1.9494    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7167   -0.7167    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.8669   -0.2974    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7167   -2.3687    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -1.1360    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -1.9602    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.5836   -0.7060    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.5694   -1.4334    0.0000 H   0  0  0  0  0  0  0  0  0  0  0  0
  2  1  1  6
  2  3  1  0
  2  6  1  0
  2 11  1  0
  3  4  2  0
  3  5  1  0
  4  7  1  0
  5  8  2  0
  6 10  1  0
  7  9  2  0
  8  9  1  0
M  END
> <chembl_id>
CHEMBL3183068

> <chembl_pref_name>
None
)CTAB"_ctab;
      REQUIRE(m);
      CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    }
    {
      auto m = R"CTAB(CHEMBL3183068 (with an H removed)
     RDKit          2D

 10 10  0  0  1  0  0  0  0  0999 V2000
    1.7309    0.0000    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    2.1502   -0.7167    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.4334   -1.1252    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.4334   -1.9494    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7167   -0.7167    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.8669   -0.2974    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7167   -2.3687    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -1.1360    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -1.9602    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.5836   -0.7060    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  2  1  1  6
  2  3  1  0
  2  6  1  0
  3  4  2  0
  3  5  1  0
  4  7  1  0
  5  8  2  0
  6 10  1  0
  7  9  2  0
  8  9  1  0
M  END
)CTAB"_ctab;
      REQUIRE(m);
      CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    }
  }

  SECTION("four-coordinate") {
    {
      auto m = R"CTAB(
  Mrv2211 07202306492D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 5 4 0 0 1
M  V30 BEGIN ATOM
M  V30 1 N -3.3332 0.9919 0 0
M  V30 2 C -2.5555 -0.3373 0 0
M  V30 3 O -3.885 -1.095 0 0
M  V30 4 C -1.2263 0.4404 0 0
M  V30 5 F -1.7854 -1.6709 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 2 1 CFG=3
M  V30 2 1 2 3
M  V30 3 1 2 4
M  V30 4 1 2 5
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
      REQUIRE(m);
      CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    }
    {
      auto m = R"CTAB(
  Mrv2211 07202306492D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 5 4 0 0 1
M  V30 BEGIN ATOM
M  V30 1 N -3.3332 0.9919 0 0
M  V30 2 C -2.5555 -0.3373 0 0
M  V30 3 O -3.885 -1.095 0 0
M  V30 4 C -1.2263 0.4404 0 0
M  V30 5 F -1.7854 -1.6709 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 2 1
M  V30 2 1 2 3
M  V30 3 1 2 4
M  V30 4 1 2 5 CFG=3
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
      REQUIRE(m);
      CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    }
    {
      auto m = R"CTAB(
  Mrv2211 07202306492D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 5 4 0 0 1
M  V30 BEGIN ATOM
M  V30 1 N -3.3332 0.9919 0 0
M  V30 2 C -2.5555 -0.3373 0 0
M  V30 3 O -3.885 -1.095 0 0
M  V30 4 C -1.2263 0.4404 0 0
M  V30 5 F -1.7854 -1.6709 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 2 1
M  V30 2 1 2 3 CFG=1
M  V30 3 1 2 4
M  V30 4 1 2 5
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
      REQUIRE(m);
      CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    }
  }
}

TEST_CASE("almost linear, degree 4") {
  SECTION("from chembl 1") {
    auto m = R"CTAB(CHEMBL3680147
     RDKit          2D

 15 16  0  0  1  0  0  0  0  0999 V2000
    3.8912   -4.9570    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.8933   -3.7570    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.5951   -3.0039    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.5973   -1.5031    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.2990   -0.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.2990    0.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000    1.5000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.2990    0.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.2990   -0.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -1.5000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    5.2073   -4.4014    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    6.2131   -3.2886    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
    5.4656   -1.9881    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.9978   -2.2972    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    5.9557   -0.8928    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
  2  1  1  1
  2  3  1  0
  3  4  1  0
  4  5  1  0
  5  6  2  0
  6  7  1  0
  7  8  2  0
  8  9  1  0
  9 10  2  0
 10  5  1  0
  2 11  1  0
 11 12  1  0
 12 13  1  0
 13 14  2  0
 14  2  1  0
 13 15  1  0
M  END
)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
  }
  SECTION("from chembl 2") {
    auto m = R"CTAB(CHEMBL76346
     RDKit          2D

 16 17  0  0  1  0  0  0  0  0999 V2000
    3.4292   -0.4250    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    2.7167   -0.0125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.7167    0.8125    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    3.4292   -1.2500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    4.1500   -0.0125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.7667   -1.7292    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
    4.1500    0.8208    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.4292    1.2333    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.0250   -2.5167    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.8500   -2.5167    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
    2.0042   -0.4250    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
    4.1042   -1.7292    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.4250    2.0583    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    2.2250   -2.7292    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.2375   -3.3125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.0042   -3.5250    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
  2  1  1  0
  3  2  1  0
  4  1  1  0
  5  1  1  0
  6  4  1  0
  7  5  2  0
  8  7  1  0
  9  6  1  0
 10 12  1  0
 11  2  2  0
 12  4  1  0
 13  8  1  0
  9 14  1  6
 15  9  1  0
 16 14  1  0
  3  8  2  0
 10  9  1  0
M  END
)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(8)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
  }
  SECTION("from chembl 3") {
    auto m = R"CTAB(CHEMBL3577363
     RDKit          2D

 15 16  0  0  0  0  0  0  0  0999 V2000
   -2.5989    1.5003    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.3863    2.3198    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
   -1.8514    3.7459    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -3.3514    3.7442    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -3.8133    2.3171    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.2990   -0.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.2990    0.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000    1.5000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.2990    0.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.2990   -0.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -1.5000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000    2.7000    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
    2.3383   -1.3500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -3.6369    0.8981    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.1471    4.7175    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  0
  3  4  1  0
  4  5  1  0
  5  1  1  0
  6  7  2  0
  7  8  1  0
  8  9  2  0
  9 10  1  0
 10 11  2  0
 11  6  1  0
  8 12  1  0
  9  1  1  0
  6 13  1  0
  1 14  1  6
  3 15  2  0
M  END
)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(0)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
  }
  SECTION("from chembl 4") {
    auto m =
        R"CTAB(derived from CHEMBL2373651. This was wrong in the RDKit implementation
  Mrv2211 07212313282D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 7 7 0 0 1
M  V30 BEGIN ATOM
M  V30 1 O -7.4486 0.4751 0 0
M  V30 2 C -6.1148 -0.2949 0 0
M  V30 3 C -6.1148 1.2451 0 0
M  V30 4 C -4.7811 -1.0649 0 0
M  V30 5 C -4.7811 2.0151 0 0
M  V30 6 H -6.1148 1.2451 0 0
M  V30 7 H -6.1148 -0.2949 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 2 1
M  V30 2 1 3 1
M  V30 3 1 2 3
M  V30 4 1 2 4
M  V30 5 1 3 5
M  V30 6 1 3 6 CFG=1
M  V30 7 1 2 7 CFG=3
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CW);
    CHECK(m->getAtomWithIdx(2)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CW);
  }
  SECTION("cut from CHEMBL4578507") {
    auto m = R"CTAB(
     RDKit          2D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 9 8 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C 10.694361 -35.424753 0.000000 0
M  V30 2 C 9.463880 -34.700253 0.000000 0
M  V30 3 C 8.217865 -35.405903 0.000000 0
M  V30 4 C 8.207044 -36.840940 0.000000 0
M  V30 5 N 9.449393 -37.570327 0.000000 0
M  V30 6 C 6.965044 -36.121153 0.000000 0
M  V30 7 O 8.215247 -33.985178 0.000000 0
M  V30 8 H 6.885805 -37.382701 0.000000 0
M  V30 9 H 9.169786 -33.305883 0.000000 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 1 4 3
M  V30 4 1 4 5
M  V30 5 1 4 6
M  V30 6 1 2 7
M  V30 7 1 4 8 CFG=3
M  V30 8 1 2 9 CFG=3
M  V30 END BOND
M  V30 END CTAB
M  END
$$$$)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(3)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
  }

  SECTION("derived from CHEMBL4578507") {
    auto m = R"CTAB(CHEMBL4578507
     RDKit          2D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 17 19 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C 6.828300 -21.526500 0.000000 0
M  V30 2 C 7.542200 -21.113800 0.000000 0
M  V30 3 C 7.537900 -20.291100 0.000000 0
M  V30 4 C 6.829700 -19.884700 0.000000 0
M  V30 5 C 6.121000 -21.114600 0.000000 0
M  V30 6 C 6.127300 -20.296500 0.000000 0
M  V30 7 C 5.422300 -19.881400 0.000000 0
M  V30 8 C 4.708400 -20.285700 0.000000 0
M  V30 9 C 4.702200 -21.107900 0.000000 0
M  V30 10 N 5.414000 -21.525800 0.000000 0
M  V30 11 C 3.990600 -20.695500 0.000000 0
M  V30 12 O 4.706900 -19.471700 0.000000 0
M  V30 13 C 3.988500 -19.876800 0.000000 0
M  V30 14 O 3.284900 -21.104500 0.000000 0
M  V30 15 F 8.242300 -19.879800 0.000000 0
M  V30 16 H 3.945200 -21.418300 0.000000 0 MASS=2
M  V30 17 H 5.253800 -19.082500 0.000000 0 MASS=2
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 2 5 1
M  V30 2 1 1 2
M  V30 3 2 2 3
M  V30 4 1 3 4
M  V30 5 2 4 6
M  V30 6 1 5 6
M  V30 7 1 5 10
M  V30 8 1 6 7
M  V30 9 1 7 8
M  V30 10 1 9 8
M  V30 11 1 9 10
M  V30 12 1 9 11
M  V30 13 1 7 12
M  V30 14 1 11 13
M  V30 15 1 13 12
M  V30 16 1 11 14 CFG=3
M  V30 17 1 3 15
M  V30 18 1 9 16 CFG=3
M  V30 19 1 7 17 CFG=3
M  V30 END BOND
M  V30 END CTAB
M  END)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(6)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(8)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(10)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
  }

  SECTION("derived from CHEMBL123021") {
    auto m = R"CTAB(CHEMBL123021
     RDKit          2D

 16 17  0  0  1  0  0  0  0  0999 V2000
   -2.8208   -0.5792    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.8208    0.2458    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -3.5375   -0.1542    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
   -1.4083   -0.5792    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.1125   -0.9917    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.4000    0.2458    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.1125    0.6583    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.6833    0.6583    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.1083   -1.8167    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
   -2.1125    1.4833    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
   -0.6833    1.4833    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.6958   -0.9917    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0250   -0.5875    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
    0.0375    1.8958    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -3.3161    1.1145    0.0000 H   0  0  0  0  0  0  0  0  0  0  0  0
   -2.3141    0.2829    0.0000 H   0  0  0  0  0  0  0  0  0  0  0  0
  2  1  1  0
  1  3  1  0
  4  5  1  0
  5  1  1  0
  6  4  2  0
  7  2  1  0
  8  6  1  0
  9  5  2  0
 10  7  2  0
 11  8  2  0
 12  4  1  0
 13 12  1  0
 14 11  1  0
  2  3  1  0
  7  6  1  0
  2 15  1  6
  1 16  1  6
M  END
)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(0)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
  }

  SECTION("derived from CHEMBL85809") {
    auto m = R"CTAB(
     RDKit          2D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 9 9 0 0 0
M  V30 BEGIN ATOM
M  V30 1 N 7.185783 -5.677981 0.000000 0
M  V30 2 C 7.207427 -7.106480 0.000000 0
M  V30 3 O 9.032790 -6.219079 0.000000 0
M  V30 4 O 10.172645 -8.527707 0.000000 0
M  V30 5 C 10.886895 -7.106480 0.000000 0
M  V30 6 C 7.900033 -8.527707 0.000000 0
M  V30 7 C 10.865251 -5.677981 0.000000 0
M  V30 8 H 7.185783 -8.534979 0.000000 0 MASS=2
M  V30 9 H 10.865251 -8.534979 0.000000 0 MASS=2
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 2 1 CFG=1
M  V30 2 1 3 2
M  V30 3 1 4 6
M  V30 4 1 5 3
M  V30 5 1 6 2
M  V30 6 1 5 7 CFG=1
M  V30 7 1 2 8 CFG=3
M  V30 8 1 5 9 CFG=3
M  V30 9 1 4 5
M  V30 END BOND
M  V30 BEGIN COLLECTION
M  V30 MDLV30/STEABS ATOMS=(2 2 5)
M  V30 END COLLECTION
M  V30 END CTAB
M  END
$$$$
)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CW);
    CHECK(m->getAtomWithIdx(4)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
  }

  SECTION("derived from CHEMBL2333552") {
    auto m = R"CTAB(blah
     RDKit          2D

 18 20  0  0  0  0  0  0  0  0999 V2000
   35.6738   -9.2984    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   34.9598   -8.8850    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   34.9588   -9.7100    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   32.1164   -7.2274    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   32.1164   -8.0541    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   32.8299   -8.4634    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   32.8299   -6.8099    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   33.5434   -7.2274    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   33.5399   -8.0541    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   34.9720   -7.2335    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   34.2572   -6.8149    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   31.3992   -6.8162    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
   32.8308   -9.2901    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   33.5319   -8.8767    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   34.9684   -8.0602    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   34.2507   -8.4688    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   35.7656   -8.2712    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
   34.2458   -7.6408    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
  2  1  1  0
  3  2  1  0
  4  5  1  0
  4  7  1  0
  5  6  1  0
  6  9  1  0
  8  7  2  0
  8  9  1  0
  8 11  1  0
  9 16  1  0
 15 10  1  0
 10 11  1  0
  4 12  2  0
  6 13  1  6
  9 14  1  6
 16 15  1  0
  2 16  1  0
 15  2  1  0
 15 17  1  6
 16 18  1  6
M  END)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(5)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    CHECK(m->getAtomWithIdx(8)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CW);
    CHECK(m->getAtomWithIdx(14)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    CHECK(m->getAtomWithIdx(15)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CW);
  }
  SECTION("CHEMBL94022") {
    auto m = R"CTAB(CHEMBL94022
     RDKit          2D

 16 18  0  0  1  0  0  0  0  0999 V2000
    2.0917   -2.6875    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.2667   -2.7042    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.6917   -3.4125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.6750   -3.2667    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.4792   -4.0667    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    3.5000   -3.2042    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    0.5500   -2.2917    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.1792   -4.5000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.8125   -3.9667    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.1708   -2.7042    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.5500   -1.4667    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8833   -2.2917    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.1708   -1.0542    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8833   -1.4667    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.6792   -3.2917    0.0000 H   0  0  0  0  0  0  0  0  0  0  0  0
    2.7917   -2.2542    0.0000 H   0  0  0  0  0  0  0  0  0  0  0  0
  2  1  1  0
  3  1  1  0
  1  4  1  1
  5  4  2  0
  6  4  1  0
  7  2  1  0
  8  5  1  0
  9  6  1  0
 10  7  2  0
 11  7  1  0
 12 10  1  0
 13 11  2  0
 14 13  1  0
  2 15  1  1
  1 16  1  6
  3  2  1  0
  8  9  1  0
 14 12  2  0
M  END)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(0)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CW);
  }
  SECTION("overlapping neighbors") {
    auto m = R"CTAB(derived from CHEMBL3752539
     RDKit          2D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 14 15 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C -1.237143 -0.714286 0.000000 0
M  V30 2 C -1.237143 0.714286 0.000000 0
M  V30 3 C -2.474381 1.428571 0.000000 0
M  V30 4 C -3.711524 0.714286 0.000000 0
M  V30 5 C -2.474381 -1.428571 0.000000 0
M  V30 6 C -3.711524 -0.714286 0.000000 0
M  V30 7 O -2.474381 -2.857143 0.000000 0
M  V30 8 H -4.536286 -0.238095 0.000000 0
M  V30 9 H -1.649524 -1.904762 0.000000 0
M  V30 10 C -4.948762 -1.428571 0.000000 0
M  V30 11 C -2.474381 -1.428571 0.000000 0
M  V30 12 C -3.711524 0.714286 0.000000 0
M  V30 13 O -3.711524 -0.714286 0.000000 0
M  V30 14 H -2.474381 2.380952 0.000000 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 1 5
M  V30 3 1 2 3
M  V30 4 1 3 4
M  V30 5 1 4 6
M  V30 6 1 5 6
M  V30 7 1 5 7
M  V30 8 1 6 10
M  V30 9 1 6 8 CFG=1
M  V30 10 1 5 9 CFG=3
M  V30 11 1 1 11 CFG=3
M  V30 12 1 3 12
M  V30 13 1 12 13
M  V30 14 1 11 13
M  V30 15 1 3 14 CFG=1
M  V30 END BOND
M  V30 BEGIN COLLECTION
M  V30 MDLV30/STEABS ATOMS=(1 6)
M  V30 MDLV30/STERAC1 ATOMS=(3 1 3 5)
M  V30 END COLLECTION
M  V30 END CTAB
M  END
$$$$
)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(2)->getChiralTag() ==
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(0)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
  }
  SECTION("bond atoms overlapping central atom at the end of wedge bonds") {
    auto m = R"CTAB(CHEMBL3612237
     RDKit          2D

 14 15  0  0  0  0  0  0  0  0999 V2000
   -0.6828   -1.6239    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.6828    0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.6090    0.5905    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.9007    0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.9007   -1.6239    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.6090   -2.3805    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.9007    1.3471    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -0.6828    1.3471    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.6090    2.0668    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.6319    1.4849    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.5072    2.6784    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
   -1.7280    0.9964    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
   -0.6828    0.0000    0.0000 H   0  0  0  0  0  0  0  0  0  0  0  0
    1.9007    0.0000    0.0000 H   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  1  6  1  0
  2  3  1  0
  3  4  1  0
  4  5  1  0
  5  6  1  0
  4  7  1  0
  2  8  1  0
  8  9  1  0
  7  9  1  0
  3 10  1  1
 10 11  1  0
 10 12  2  0
  2 13  1  1
  4 14  1  1
M  END)CTAB"_ctab;
    REQUIRE(m);
    // if the bond is wedged, then we should have chirality even if the bonded
    // atom overlaps the central atom
    CHECK(m->getAtomWithIdx(1)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(3)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(m->getAtomWithIdx(2)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
  }
}

TEST_CASE("github #6931: atom maps influencing chirality perception") {
  SECTION("basics") {
    auto m = "[CH3:1]C([CH3:2])(O)F"_smiles;
    REQUIRE(m);
    bool cleanIt = true;
    bool force = true;
    bool flagPossibleStereoCenters = true;
    UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
    MolOps::assignStereochemistry(*m, cleanIt, force,
                                  flagPossibleStereoCenters);
    CHECK(
        !m->getAtomWithIdx(1)->hasProp(common_properties::_ChiralityPossible));
  }
  SECTION(
      "github #8391: atom maps on dummy atoms do influence chirality perception") {
    auto m = "[*:1]C([*:2])(O)F"_smiles;
    REQUIRE(m);
    bool cleanIt = true;
    bool force = true;
    bool flagPossibleStereoCenters = true;
    UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
    MolOps::assignStereochemistry(*m, cleanIt, force,
                                  flagPossibleStereoCenters);
    CHECK(m->getAtomWithIdx(1)->hasProp(common_properties::_ChiralityPossible));
  }
}

TEST_CASE(
    "Github Issue #6981: Parsing a Mol leaks the \"_needsDetectBondStereo\" property",
    "[bug][stereo]") {
  // Parametrize test to run under legacy and new stereo perception
  const auto legacy_stereo = GENERATE(true, false);
  INFO("Legacy stereo perception == " << legacy_stereo);

  UseLegacyStereoPerceptionFixture reset_stereo_perception(legacy_stereo);

  auto m = R"CTAB(
  Mrv2311 12122315472D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 4 3 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C -9.2083 1.8333 0 0
M  V30 2 C -8.0639 0.8029 0 0
M  V30 3 C -6.5239 0.8029 0 0
M  V30 4 C -5.7539 -0.5308 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 2 1 CFG=2
M  V30 2 2 2 3
M  V30 3 1 3 4
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;

  REQUIRE(m);
  REQUIRE(m->getNumAtoms() == 4);

  CHECK(m->hasProp("_needsDetectBondStereo") == false);
}

TEST_CASE(
    "Github Issue #7076: new stereo code not properly handling crossed double bonds") {
  // Parametrize test to run under legacy and new stereo perception
  SECTION("second part") {
    const auto legacy_stereo = GENERATE(true, false);
    INFO("Legacy stereo perception == " << legacy_stereo);

    UseLegacyStereoPerceptionFixture reset_stereo_perception(legacy_stereo);

    auto m = R"CTAB(
  Mrv2211 01252410552D

 10  9  0  0  0  0            999 V2000
    0.0000   -1.4364    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7108   -3.4884    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -3.8988    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8208   -1.4364    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -2.2572    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7108   -2.6676    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.4217   -2.2572    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -4.7196    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.2439   -5.4378    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.7108   -5.1300    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  4  1  0  0  0  0
  1  5  2  0  0  0  0
  2  3  1  0  0  0  0
  2  6  2  3  0  0  0
  3  8  2  0  0  0  0
  5  6  1  0  0  0  0
  6  7  1  0  0  0  0
  8  9  1  0  0  0  0
  8 10  1  0  0  0  0
M  END)CTAB"_ctab;

    REQUIRE(m);
    CHECK(m->getBondWithIdx(3)->getStereo() == Bond::BondStereo::STEREOANY);
  }
  SECTION("original") {
    std::string ctab = R"CTAB(7630532
     RDKit          2D

 37 41  0  0  0  0  0  0  0  0999 V2000
    0.0000   -1.7500    0.0000 P   0  0  0  0  0  0  0  0  0  0  0  0
    0.8660   -4.2500    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -4.7500    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000    0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.7500   -1.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.0000   -1.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -2.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8675    0.4975    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.2475   -0.8825    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.4975   -2.6175    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.8675    0.4975    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.2475   -2.6175    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.4975   -0.8825    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.8660   -3.2500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8675    1.5027    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.2527   -0.8825    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.5027   -2.6175    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.8675    1.5027    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.2527   -2.6175    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.5027   -0.8825    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.0000    2.0104    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.7604   -1.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -3.0104   -1.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.7321   -2.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -5.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.5155   -6.6250    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8660   -6.2500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.3801   -6.1225    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8631   -7.2500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.5126   -7.6250    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.7306   -5.7475    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.2507   -6.6251    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.7337   -7.7526    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.3832   -8.1276    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.6012   -6.2501    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.2566   -7.6302    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.6071   -7.2552    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  4  1  0
  1  5  1  0
  1  6  1  0
  1  7  2  0
  2  3  1  0
  2 14  2  3
  3 25  2  0
  4  8  2  0
  4 11  1  0
  5  9  2  0
  5 12  1  0
  6 10  2  0
  6 13  1  0
  7 14  1  0
  8 15  1  0
  9 16  1  0
 10 17  1  0
 11 18  2  0
 12 19  2  0
 13 20  2  0
 14 24  1  0
 15 21  2  0
 16 22  2  0
 17 23  2  0
 18 21  1  0
 19 22  1  0
 20 23  1  0
 25 26  1  0
 25 27  1  0
 26 28  2  0
 26 30  1  0
 27 29  2  0
 27 31  1  0
 28 32  1  0
 29 33  1  0
 30 34  2  0
 31 35  2  0
 32 36  2  0
 33 37  2  0
 34 36  1  0
 35 37  1  0
M  END)CTAB";
    const auto legacy_stereo = GENERATE(true, false);
    INFO("Legacy stereo perception == " << legacy_stereo);

    UseLegacyStereoPerceptionFixture reset_stereo_perception(legacy_stereo);
    {
      // normal file parsing
      auto m = std::unique_ptr<RWMol>(MolBlockToMol(ctab));
      REQUIRE(m);
      CHECK(m->getBondWithIdx(5)->getStereo() == Bond::BondStereo::STEREOANY);
      CHECK(m->getBondWithIdx(6)->getStereo() == Bond::BondStereo::STEREONONE);
    }
    {
      // no sanitization during parsing
      bool sanitize = false;
      bool removeHs = false;
      auto m = std::unique_ptr<RWMol>(MolBlockToMol(ctab, sanitize, removeHs));
      REQUIRE(m);
      MolOps::sanitizeMol(*m);
      bool cleanIt = true;
      bool force = true;
      MolOps::assignStereochemistry(*m, cleanIt, force);
      CHECK(m->getBondWithIdx(5)->getStereo() == Bond::BondStereo::STEREOANY);
      CHECK(m->getBondWithIdx(6)->getStereo() == Bond::BondStereo::STEREONONE);
    }
  }
}

TEST_CASE(
    "github #3369: support new CIP code and StereoGroups in addStereoAnnotations()") {
  auto m1 =
      "C[C@@H]1N[C@H](C)[C@@H]([C@H](C)[C@@H]1C)C1[C@@H](C)O[C@@H](C)[C@@H](C)[C@H]1C/C=C/C |a:5,o1:1,8,o2:14,16,&1:18,&2:3,6,r|"_smiles;
  REQUIRE(m1);
  SECTION("defaults") {
    ROMol m2(*m1);
    Chirality::addStereoAnnotations(m2);

    std::string txt;
    CHECK(m2.getAtomWithIdx(5)->getPropIfPresent(common_properties::atomNote,
                                                 txt));
    CHECK(txt == "abs (S)");
    CHECK(m2.getAtomWithIdx(3)->getPropIfPresent(common_properties::atomNote,
                                                 txt));
    CHECK(txt == "and2");
  }
  SECTION("double bonds") {
    ROMol m2(*m1);
    REQUIRE(m2.getBondBetweenAtoms(20, 21));
    m2.getBondBetweenAtoms(20, 21)->setStereo(Bond::BondStereo::STEREOTRANS);
    // initially no label is assigned since we have TRANS
    Chirality::addStereoAnnotations(m2);
    CHECK(
        !m2.getBondBetweenAtoms(20, 21)->hasProp(common_properties::bondNote));

    CIPLabeler::assignCIPLabels(m2);
    std::string txt;
    CHECK(m2.getBondBetweenAtoms(20, 21)->getPropIfPresent(
        common_properties::_CIPCode, txt));
    CHECK(txt == "E");
    Chirality::addStereoAnnotations(m2);
    CHECK(m2.getBondBetweenAtoms(20, 21)->getPropIfPresent(
        common_properties::bondNote, txt));
    CHECK(txt == "(E)");
  }

  SECTION("custom labels") {
    ROMol m2(*m1);
    CIPLabeler::assignCIPLabels(m2);

    std::string absLabel = "abs [{cip}]";
    std::string orLabel = "o{id} ({cip})";
    std::string andLabel = "&{id} ({cip})";
    std::string cipLabel = "[{cip}]";
    std::string bondLabel = "[{cip}]";
    Chirality::addStereoAnnotations(m2, absLabel, orLabel, andLabel, cipLabel,
                                    bondLabel);

    std::string txt;

    CHECK(m2.getAtomWithIdx(5)->getPropIfPresent(common_properties::atomNote,
                                                 txt));
    CHECK(txt == "abs [S]");

    CHECK(m2.getAtomWithIdx(3)->getPropIfPresent(common_properties::atomNote,
                                                 txt));
    CHECK(txt == "&2 (R)");

    CHECK(m2.getAtomWithIdx(1)->getPropIfPresent(common_properties::atomNote,
                                                 txt));
    CHECK(txt == "o1 (S)");

    CHECK(m2.getAtomWithIdx(11)->getPropIfPresent(common_properties::atomNote,
                                                  txt));
    CHECK(txt == "[R]");

    CHECK(m2.getBondBetweenAtoms(20, 21)->getPropIfPresent(
        common_properties::bondNote, txt));
    CHECK(txt == "[E]");
  }

  SECTION("empty labels") {
    ROMol m2(*m1);
    CIPLabeler::assignCIPLabels(m2);

    std::string absLabel = "";
    std::string orLabel = "o{id} ({cip})";
    std::string andLabel = "&{id} ({cip})";
    std::string cipLabel = "[{cip}]";
    std::string bondLabel = "";
    Chirality::addStereoAnnotations(m2, absLabel, orLabel, andLabel, cipLabel,
                                    bondLabel);

    std::string txt;

    CHECK(!m2.getAtomWithIdx(5)->getPropIfPresent(common_properties::atomNote,
                                                  txt));

    CHECK(m2.getAtomWithIdx(3)->getPropIfPresent(common_properties::atomNote,
                                                 txt));
    CHECK(txt == "&2 (R)");

    CHECK(m2.getAtomWithIdx(1)->getPropIfPresent(common_properties::atomNote,
                                                 txt));
    CHECK(txt == "o1 (S)");

    CHECK(m2.getAtomWithIdx(11)->getPropIfPresent(common_properties::atomNote,
                                                  txt));
    CHECK(txt == "[R]");

    CHECK(!m2.getBondBetweenAtoms(20, 21)->getPropIfPresent(
        common_properties::bondNote, txt));
  }
}

TEST_CASE("do not wedge bonds to attachment points") {
  SECTION("basics") {
    auto m = R"CTAB(
     RDKit          2D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 4 3 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C 0.000000 0.000000 0.000000 0 ATTCHPT=1
M  V30 2 F -1.299038 0.750000 0.000000 0
M  V30 3 Cl -0.000000 -1.500000 0.000000 0
M  V30 4 O 1.299038 0.750000 0.000000 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 1 3
M  V30 3 1 1 4 CFG=1
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getNumAtoms() == 4);
    MolOps::expandAttachmentPoints(*m);
    CHECK(m->getNumAtoms() == 5);
    CHECK(m->getAtomWithIdx(4)->getTotalValence() == 1);
    Chirality::wedgeMolBonds(*m);
    CHECK(m->getBondBetweenAtoms(0, 4)->getBondDir() == Bond::BondDir::NONE);
  }
  SECTION("cage") {
    auto m = R"CTAB(
  Mrv2305 03052406362D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 8 9 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C -3.125 2.5817 0 0 CFG=1
M  V30 2 C -4.4587 1.8117 0 0
M  V30 3 C -4.4587 0.2716 0 0
M  V30 4 C -3.125 -0.4984 0 0
M  V30 5 C -1.7913 0.2716 0 0
M  V30 6 N -1.7913 1.8117 0 0
M  V30 7 O -2.5357 1.1589 0 0
M  V30 8 * -3.125 4.1217 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 1 3 4
M  V30 4 1 4 5
M  V30 5 1 5 6
M  V30 6 1 1 7
M  V30 7 1 7 4
M  V30 8 1 1 8
M  V30 9 1 1 6 CFG=1
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
    m->getAtomWithIdx(7)->setProp(common_properties::_fromAttachPoint, 1);
    Chirality::wedgeMolBonds(*m);
    CHECK(m->getBondBetweenAtoms(0, 7)->getBondDir() == Bond::BondDir::NONE);
  }
}

TEST_CASE(
    "github #7203: Remove invalid stereo groups on MolOps::assignStereochemistry") {
  SECTION("single-atom groups") {
    for (auto use_legacy : {false, true}) {
      UseLegacyStereoPerceptionFixture reset_stereo_perception(use_legacy);
      INFO(use_legacy);
      auto m = "C[C@H](N)[C@@H](C)C |o1:1,o2:3|"_smiles;
      REQUIRE(m);
      CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
      CHECK(m->getAtomWithIdx(3)->getChiralTag() ==
            Atom::ChiralType::CHI_UNSPECIFIED);
      CHECK(m->getStereoGroups().size() == 1);
    }
  }
  SECTION("two-atom groups") {
    for (auto use_legacy : {false, true}) {
      Chirality::setUseLegacyStereoPerception(use_legacy);
      INFO(use_legacy);
      {  // no removal necessary
        auto m = "C[C@H](N)[C@@H](F)C |o1:1,3|"_smiles;
        REQUIRE(m);
        CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
              Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
        CHECK(m->getAtomWithIdx(3)->getChiralTag() ==
              Atom::ChiralType::CHI_TETRAHEDRAL_CW);
        CHECK(m->getStereoGroups().size() == 1);
        CHECK(m->getStereoGroups()[0].getAtoms().size() == 2);
      }
      {  // removal
        auto m = "C[C@H](N)[C@@H](C)C |o1:1,3|"_smiles;
        REQUIRE(m);
        CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
              Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
        CHECK(m->getAtomWithIdx(3)->getChiralTag() ==
              Atom::ChiralType::CHI_UNSPECIFIED);
        CHECK(m->getStereoGroups().size() == 1);
        CHECK(m->getStereoGroups()[0].getAtoms().size() == 1);
      }
    }
  }
}

TEST_CASE(
    "GitHub Issue #7346: Trigonal Pyramid Carbon may or not have a parity depending on atom ordering",
    "[bug]") {
  // First atom not in the same plane as the rest
  const auto m0 = R"CTAB(
                    3D

  5  4  0  0  1  0            999 V2000
   -0.2052    0.0804   -0.7454 F   0  0  0  0  0  0
   -0.5696    0.2231   -2.0688 C   0  0  0  0  0  0
   -2.2748   -0.0294   -1.1593 Br  0  0  0  0  0  0
    0.2659   -1.3655   -2.0764 Cl  0  0  0  0  0  0
   -0.1867    1.1526   -2.4961 D   0  0  0  0  0  0
  1  2  1  0  0  0
  2  3  1  0  0  0
  2  4  1  0  0  0
  2  5  1  0  0  0
M  END)CTAB"_ctab;

  // All atoms in the same plane except the rest
  const auto m1 = R"CTAB(
                    3D

  5  4  0  0  1  0            999 V2000
   -0.1867    1.1526   -2.4961 D   0  0  0  0  0  0
   -0.5696    0.2231   -2.0688 C   0  0  0  0  0  0
   -2.2748   -0.0294   -1.1593 Br  0  0  0  0  0  0
    0.2659   -1.3655   -2.0764 Cl  0  0  0  0  0  0
   -0.2052    0.0804   -0.7454 F   0  0  0  0  0  0
  1  2  1  0  0  0
  2  3  1  0  0  0
  2  4  1  0  0  0
  2  5  1  0  0  0
M  END)CTAB"_ctab;

  REQUIRE(m0);
  REQUIRE(m1);

  const auto cAt0 = m0->getAtomWithIdx(1);
  const auto cAt1 = m1->getAtomWithIdx(1);
  REQUIRE(cAt0->getAtomicNum() == 6);
  REQUIRE(cAt1->getAtomicNum() == 6);

  // Two atoms changes positions in the molblocks, but their coordinates
  // didn't change, so they must have opposite parities in order to
  // preserve the absolute chirality
  CHECK(cAt0->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
  CHECK(cAt1->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL_CW);

  CIPLabeler::assignCIPLabels(*m0);
  CIPLabeler::assignCIPLabels(*m1);

  CHECK(cAt0->getProp<std::string>(common_properties::_CIPCode) ==
        cAt1->getProp<std::string>(common_properties::_CIPCode));
}

TEST_CASE(
    "Github #7300: incorrect chiral carbon perception for some phosphates") {
  SECTION("basics") {
    auto m = "NC(CP(=O)(O)O)CP(=O)(O)O"_smiles;
    REQUIRE(m);
    auto sis = Chirality::findPotentialStereo(*m);
    CHECK(sis.empty());
  }
  SECTION("make sure Ps can still yield chiral centers") {
    auto m = "NC(CP(=O)(O)[O-])CP(=O)(O)[O-]"_smiles;
    REQUIRE(m);
    auto sis = Chirality::findPotentialStereo(*m);
    CHECK(sis.size() == 3);
  }
}

TEST_CASE("github 7371") {
  SECTION("basics") {
    auto m = R"CTAB(
     RDKit          2D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 20 21 0 0 0
M  V30 BEGIN ATOM
M  V30 1 N -0.024300 -1.051200 0.000000 0
M  V30 2 C 0.690200 -1.463700 0.000000 0
M  V30 3 O 0.690200 -2.288700 0.000000 0
M  V30 4 N 1.404500 -1.051200 0.000000 0
M  V30 5 C 2.119100 -1.463700 0.000000 0
M  V30 6 C 2.119100 -2.288700 0.000000 0
M  V30 7 C 1.404500 -2.701200 0.000000 0
M  V30 8 C 2.833500 -2.701200 0.000000 0
M  V30 9 C 3.547900 -2.288700 0.000000 0
M  V30 10 N 3.547900 -1.463700 0.000000 0
M  V30 11 C 2.833600 -1.051200 0.000000 0
M  V30 12 C 3.363800 -0.419100 0.000000 0
M  V30 13 C 4.176300 -0.562300 0.000000 0
M  V30 14 C 3.081600 0.356100 0.000000 0
M  V30 15 C 1.404500 -0.226000 0.000000 0
M  V30 16 N 2.155300 0.191900 0.000000 0
M  V30 17 C 2.161900 1.051300 0.000000 0
M  V30 18 C 1.417800 1.480900 0.000000 0
M  V30 19 C 0.676800 1.045500 0.000000 0
M  V30 20 C 0.690200 0.186400 0.000000 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 2 2 3
M  V30 3 1 4 2
M  V30 4 1 4 5
M  V30 5 2 5 6
M  V30 6 1 6 7
M  V30 7 1 6 8
M  V30 8 2 8 9
M  V30 9 1 9 10
M  V30 10 2 10 11
M  V30 11 1 11 12
M  V30 12 1 12 13
M  V30 13 1 12 14
M  V30 14 1 4 15
M  V30 15 2 15 16
M  V30 16 1 16 17
M  V30 17 2 17 18
M  V30 18 1 18 19
M  V30 19 2 19 20
M  V30 20 1 5 11 CFG=1
M  V30 21 1 20 15
M  V30 END BOND
M  V30 END CTAB
M  END)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getBondWithIdx(3)->getStereo() == Bond::BondStereo::STEREOATROPCW);
    // after reading in, there's no bond wedging:
    for (const auto bnd : m->bonds()) {
      INFO(bnd->getIdx());
      CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
    }
    Chirality::wedgeMolBonds(*m, &m->getConformer());
    // now there is:
    for (const auto bnd : m->bonds()) {
      INFO(bnd->getIdx());
      if (bnd->getIdx() != 19) {
        CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
      } else {
        CHECK(bnd->getBondDir() == Bond::BondDir::BEGINWEDGE);
      }
    }
  }
  SECTION("3d") {
    auto m = R"CTAB(
     RDKit          3D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 20 21 0 0 0
M  V30 BEGIN ATOM
M  V30 1 N 0.382755 -0.645531 -2.970173 0
M  V30 2 C 1.191902 -0.080895 -1.951313 0
M  V30 3 O 2.258061 0.469395 -2.326427 0
M  V30 4 N 0.799020 -0.142115 -0.557889 0
M  V30 5 C -0.412487 -0.780398 -0.245786 0
M  V30 6 C -0.494802 -2.127175 0.018641 0
M  V30 7 C 0.666160 -3.010952 -0.001627 0
M  V30 8 C -1.739073 -2.694470 0.315567 0
M  V30 9 C -2.888797 -1.963937 0.355103 0
M  V30 10 N -2.786157 -0.648937 0.093334 0
M  V30 11 C -1.586505 -0.059517 -0.200695 0
M  V30 12 C -1.679580 1.394735 -0.453406 0
M  V30 13 C -1.637015 2.096392 0.888082 0
M  V30 14 C -3.022840 1.757751 -1.085608 0
M  V30 15 C 1.696224 0.471157 0.348870 0
M  V30 16 N 1.608949 1.758263 0.730152 0
M  V30 17 C 2.457188 2.346328 1.581612 0
M  V30 18 C 3.501956 1.626784 2.127981 0
M  V30 19 C 3.644663 0.298166 1.774403 0
M  V30 20 C 2.724579 -0.259689 0.878536 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 2 2 3
M  V30 3 1 4 2
M  V30 4 1 4 5
M  V30 5 2 5 6
M  V30 6 1 6 7
M  V30 7 1 6 8
M  V30 8 2 8 9
M  V30 9 1 9 10
M  V30 10 2 10 11
M  V30 11 1 11 12
M  V30 12 1 12 13
M  V30 13 1 12 14
M  V30 14 1 4 15
M  V30 15 2 15 16
M  V30 16 1 16 17
M  V30 17 2 17 18
M  V30 18 1 18 19
M  V30 19 2 19 20
M  V30 20 1 5 11 CFG=1
M  V30 21 1 20 15
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getBondWithIdx(3)->getStereo() == Bond::BondStereo::STEREOATROPCW);
    // after reading in, there's no bond wedging:
    for (const auto bnd : m->bonds()) {
      INFO(bnd->getIdx());
      CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
    }
    Chirality::wedgeMolBonds(*m, &m->getConformer());
    // now there is:
    for (const auto bnd : m->bonds()) {
      INFO(bnd->getIdx());
      if (bnd->getIdx() != 4) {
        CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
      } else {
        CHECK(bnd->getBondDir() == Bond::BondDir::BEGINWEDGE);
      }
    }
  }
  SECTION("favor larger rings") {
    auto m = R"CTAB(
  Mrv2401 04262410272D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 13 14 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C 2.5796 -3.2152 0 0
M  V30 2 N 2.1037 -4.6798 0 0
M  V30 3 N 0.5637 -4.6798 0 0
M  V30 4 C 0.0878 -3.2152 0 0
M  V30 5 C -1.3768 -2.7393 0 0
M  V30 6 C 1.3337 -2.31 0 0
M  V30 7 C 1.3337 -0.77 0 0
M  V30 8 C 0 0 0 0
M  V30 9 Cl -1.3337 -0.77 0 0
M  V30 10 C 0 1.54 0 0
M  V30 11 C 1.3337 2.31 0 0
M  V30 12 C 2.6674 1.54 0 0
M  V30 13 C 2.6674 -0 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 2 3 4
M  V30 4 1 4 5
M  V30 5 1 6 4 CFG=1
M  V30 6 2 1 6
M  V30 7 1 6 7
M  V30 8 2 7 8
M  V30 9 1 8 9
M  V30 10 1 8 10
M  V30 11 2 10 11
M  V30 12 1 11 12
M  V30 13 2 12 13
M  V30 14 1 7 13
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getBondBetweenAtoms(5, 6)->getStereo() ==
          Bond::BondStereo::STEREOATROPCW);
    for (const auto bnd : m->bonds()) {
      INFO(bnd->getIdx());
      CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
    }
    Chirality::wedgeMolBonds(*m, &m->getConformer());
    // now there is:
    for (const auto bnd : m->bonds()) {
      INFO(bnd->getIdx());
      if (bnd->getIdx() != 13) {
        CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
      } else {
        CHECK(bnd->getBondDir() == Bond::BondDir::BEGINWEDGE);
      }
    }
  }
  SECTION("favor larger rings 3D") {
    auto m = R"CTAB(
     RDKit          3D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 13 14 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C 1.472629 -1.845227 -0.264711 0
M  V30 2 N 2.829375 -1.724790 -0.334942 0
M  V30 3 N 3.127980 -0.437811 -0.267564 0
M  V30 4 C 1.973684 0.284699 -0.153598 0
M  V30 5 C 1.905135 1.771086 -0.051971 0
M  V30 6 C 0.912735 -0.591792 -0.150090 0
M  V30 7 C -0.495016 -0.243797 -0.046092 0
M  V30 8 C -1.102614 -0.154763 1.189160 0
M  V30 9 Cl -0.084572 -0.483042 2.603743 0
M  V30 10 C -2.434893 0.170808 1.361855 0
M  V30 11 C -3.202022 0.420663 0.247454 0
M  V30 12 C -2.626093 0.340227 -1.000486 0
M  V30 13 C -1.279199 0.010190 -1.157641 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 2 3 4
M  V30 4 1 4 5
M  V30 5 1 6 4 CFG=3
M  V30 6 2 1 6
M  V30 7 1 6 7
M  V30 8 2 7 8
M  V30 9 1 8 9
M  V30 10 1 8 10
M  V30 11 2 10 11
M  V30 12 1 11 12
M  V30 13 2 12 13
M  V30 14 1 7 13
M  V30 END BOND
M  V30 END CTAB
M  END

)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getBondBetweenAtoms(5, 6)->getStereo() ==
          Bond::BondStereo::STEREOATROPCW);
    for (const auto bnd : m->bonds()) {
      INFO(bnd->getIdx());
      CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
    }
    Chirality::wedgeMolBonds(*m, &m->getConformer());
    // now there is:
    for (const auto bnd : m->bonds()) {
      INFO(bnd->getIdx());
      if (bnd->getIdx() != 7) {
        CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
      } else {
        CHECK(bnd->getBondDir() == Bond::BondDir::BEGINWEDGE);
      }
    }
  }

  SECTION("avoid macrocycles") {
    auto m =
        "C1=C(C2=CCCCCCCCCCC2)CCC1 |(-1.71835,-1.22732,;-1.61655,-0.232318,;-0.751953,0.270082,;-0.754753,1.27008,;0.109847,1.77248,;0.977247,1.27488,;1.84185,1.77728,;2.70925,1.27968,;2.71205,0.279682,;1.84745,-0.222718,;1.85025,-1.22272,;0.985647,-1.72512,;0.118247,-1.22752,;0.115447,-0.227518,;-2.53135,0.171882,;-3.19835,-0.573118,;-2.69595,-1.43772,)|"_smiles;
    REQUIRE(m);
    m->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOATROPCW);
    Chirality::wedgeMolBonds(*m, &m->getConformer());
    // now there is:
    for (const auto bnd : m->bonds()) {
      INFO(bnd->getIdx());
      if (bnd->getIdx() != 13) {
        CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
      } else {
        CHECK(bnd->getBondDir() == Bond::BondDir::BEGINWEDGE);
      }
    }
  }
}

TEST_CASE(
    "github #7434: planar amide nitrogen incorrectly flagged as _ChiralityPossible") {
  SECTION("as reported") {
    auto m1 = "O=C1CCCCC[C@@H]2CN1CCO2"_smiles;
    REQUIRE(m1);
    // new stereo
    CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
        m1->getAtomWithIdx(7)));
    CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
        m1->getAtomWithIdx(9)));

    // force old stereo
    UseLegacyStereoPerceptionFixture resetStereoPerception{true};
    bool cleanIt = true;
    bool flagPossible = true;
    auto si = Chirality::findPotentialStereo(*m1, cleanIt, flagPossible);
    CHECK(si.size() == 1);
  }
  SECTION("details") {
    auto m1 = "C1CCCCC[C@@H]2CN1CCO2"_smiles;
    REQUIRE(m1);
    CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
        m1->getAtomWithIdx(6)));
    CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
        m1->getAtomWithIdx(8)));

    // force old stereo
    UseLegacyStereoPerceptionFixture resetStereoPerception{true};
    bool cleanIt = true;
    bool flagPossible = true;
    {
      auto si = Chirality::findPotentialStereo(*m1, cleanIt, flagPossible);
      CHECK(si.size() == 2);
    }
    m1->getAtomWithIdx(8)->setHybridization(Atom::HybridizationType::SP2);
    CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
        m1->getAtomWithIdx(8)));
    {
      auto si = Chirality::findPotentialStereo(*m1, cleanIt, flagPossible);
      CHECK(si.size() == 1);
    }
    m1->getAtomWithIdx(8)->setHybridization(Atom::HybridizationType::SP3);
    m1->getBondBetweenAtoms(0, 8)->setIsConjugated(true);
    CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
        m1->getAtomWithIdx(8)));
    {
      auto si = Chirality::findPotentialStereo(*m1, cleanIt, flagPossible);
      CHECK(si.size() == 1);
    }
  }
}

TEST_CASE("github #7438: expose code for simplified stereo labels") {
  SECTION("basics") {
    {
      auto m = "C[C@H](N)[C@@H](C)F |o1:1,3|"_smiles;
      REQUIRE(m);
      {
        ROMol m2(*m);
        Chirality::simplifyEnhancedStereo(m2);
        std::string note;
        CHECK(m2.getPropIfPresent(common_properties::molNote, note));
        CHECK(note == "OR enantiomer");
        CHECK(m2.getStereoGroups().empty());
      }
      {
        ROMol m2(*m);
        bool removeAffectedStereoGroups = false;
        Chirality::simplifyEnhancedStereo(m2, removeAffectedStereoGroups);
        std::string note;
        CHECK(m2.getPropIfPresent(common_properties::molNote, note));
        CHECK(note == "OR enantiomer");
        CHECK(m2.getStereoGroups().size() == 1);
      }
    }
    {
      auto m = "C[C@H](N)[C@@H](C)F |&1:1,3|"_smiles;
      REQUIRE(m);
      {
        ROMol m2(*m);
        Chirality::simplifyEnhancedStereo(m2);
        std::string note;
        CHECK(m2.getPropIfPresent(common_properties::molNote, note));
        CHECK(note == "AND enantiomer");
        CHECK(m2.getStereoGroups().empty());
      }
      {
        ROMol m2(*m);
        bool removeAffectedStereoGroups = false;
        Chirality::simplifyEnhancedStereo(m2, removeAffectedStereoGroups);
        std::string note;
        CHECK(m2.getPropIfPresent(common_properties::molNote, note));
        CHECK(note == "AND enantiomer");
        CHECK(m2.getStereoGroups().size() == 1);
      }
    }
  }

  SECTION("incomplete") {
    auto m = "C[C@H](N)[C@@H](C)F |o1:1|"_smiles;
    REQUIRE(m);
    {
      ROMol m2(*m);
      Chirality::simplifyEnhancedStereo(m2);
      CHECK(!m2.hasProp(common_properties::molNote));
      CHECK(m2.getStereoGroups().size() == 1);
    }
  }
}

TEST_CASE("CipLabelAtropsOnRing", "[basic]") {
  SECTION("atropisomers1") {
    std::string molBlock = R"(
  Mrv2304 04172300142D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 24 26 0 0 1
M  V30 BEGIN ATOM
M  V30 1 C -0.0002 1.5403 0 0
M  V30 2 N -0.0002 3.0805 0 0 CFG=1
M  V30 3 C -1.334 3.8508 0 0
M  V30 4 C -2.6678 3.0807 0 0
M  V30 5 C -1.334 5.391 0 0
M  V30 6 C 1.3338 5.391 0 0
M  V30 7 C 1.3338 3.8508 0 0
M  V30 8 Br 2.6676 3.0807 0 0
M  V30 9 C -1.3338 0.7702 0 0
M  V30 10 C -2.6678 1.5403 0 0
M  V30 11 C -1.3338 -0.7702 0 0
M  V30 12 C -2.6678 -1.5401 0 0
M  V30 13 C -0.0002 -1.5403 0 0
M  V30 14 N -0.0002 -3.0805 0 0 CFG=1
M  V30 15 C 1.3338 -3.8508 0 0
M  V30 16 C 2.6676 -3.0805 0 0
M  V30 17 C 1.3338 -5.391 0 0
M  V30 18 C -1.334 -5.391 0 0
M  V30 19 C -1.334 -3.8508 0 0
M  V30 20 Br -2.6678 -3.0805 0 0
M  V30 21 C 1.3338 -0.7702 0 0
M  V30 22 C 2.6678 -1.5403 0 0
M  V30 23 C 1.3338 0.7702 0 0
M  V30 24 C 2.6678 1.5404 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 1 3 4
M  V30 4 2 3 5
M  V30 5 1 5 6
M  V30 6 2 6 7
M  V30 7 1 2 7 CFG=1
M  V30 8 1 7 8
M  V30 9 2 1 9
M  V30 10 1 9 10
M  V30 11 1 9 11
M  V30 12 1 11 12
M  V30 13 2 11 13
M  V30 14 1 13 14
M  V30 15 1 14 15 CFG=1
M  V30 16 1 15 16
M  V30 17 2 15 17
M  V30 18 1 17 18
M  V30 19 2 18 19
M  V30 20 1 14 19
M  V30 21 1 19 20
M  V30 22 1 13 21
M  V30 23 1 21 22
M  V30 24 2 21 23
M  V30 25 1 1 23
M  V30 26 1 23 24
M  V30 END BOND
M  V30 END CTAB
M  END
 )";

    UseLegacyStereoPerceptionFixture useLegacy(false);

    std::unique_ptr<RWMol> mol(MolBlockToMol(molBlock, true, false));

    REQUIRE(mol);

    CHECK(mol->getBondWithIdx(0)->getStereo() ==
          Bond::BondStereo::STEREOATROPCCW);
    CHECK(mol->getBondWithIdx(13)->getStereo() ==
          Bond::BondStereo::STEREOATROPCCW);
  }
}

TEST_CASE(
    "GitHub #7509: atomChiralTypeFromBondDirPseudo3D fails for poorly scaled molecular coordinates") {
  auto m = R"CTAB(
     RDKit          2D

  5  4  0  0  0  0  0  0  0  0999 V2000
   -2.0785    0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.7794    0.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.5196   -0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.8187    0.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.5196   -1.5000    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  0
  3  4  1  1
  3  5  1  0
M  END
)CTAB"_ctab;
  REQUIRE(m);

  auto at = m->getAtomWithIdx(2);
  REQUIRE(at->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL_CW);

  auto &pos = m->getConformer().getPositions();
  std::for_each(pos.begin(), pos.end(),
                [](RDGeom::Point3D &pos) { pos *= 6.0; });

  // Reset chirality and the original bond direction
  // (it was stripped after parsing m for the first time)
  at->setChiralTag(Atom::ChiralType::CHI_UNSPECIFIED);
  m->getBondBetweenAtoms(2, 3)->setBondDir(Bond::BondDir::BEGINWEDGE);

  MolOps::assignChiralTypesFromBondDirs(*m);

  CHECK(at->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL_CW);
}

TEST_CASE("findMesoCenters") {
  UseLegacyStereoPerceptionFixture reset_stereo_perception(false);

  SECTION("basics") {
    std::vector<std::pair<std::string,
                          std::vector<std::pair<unsigned int, unsigned int>>>>
        cases{
            {"C[C@@H](Cl)C[C@H](C)Cl", {{1, 4}}},
            {"C[C@@H](Cl)C[C@@H](Cl)C", {{1, 4}}},
            {"C[C@H](Cl)C[C@H](C)Cl", {}},
            {"OC(F)C([C@H](F)O)[C@@H](F)O", {{4, 7}}},
            {"N[C@H]1CC[C@@H](O)CC1", {{1, 4}}},
            {"N[C@H]1CC[C@H](O)CC1", {{1, 4}}},
            {"N[C@H]1CC[C@@H](N)CC1", {{1, 4}}},
            {"N[C@H]1CC[C@H](N)CC1", {{1, 4}}},
            {"N[C@H]1C[C@@H](N)C1", {{1, 3}}},
            {"N[C@H]1C[C@H](N)C1", {{1, 3}}},
            {"C1CC[C@H]2CCCC[C@H]2C1", {{3, 8}}},
            // multiple groups
            {"C[C@@H](Cl)C([C@H](C)Cl)C([C@H](F)O)[C@@H](F)O",
             {{1, 4}, {8, 11}}},
            {"C1[C@H](F)C[C@H]1C[C@H]1C[C@H](N)C1", {{1, 4}, {6, 8}}},
            // not sure if this next one is right:
            {"N[C@H]1[C@H](F)[C@@H](N)[C@@H]1F", {{1, 4}, {2, 6}}},
        };
    for (auto &[smi, expected] : cases) {
      INFO(smi);
      auto m = v2::SmilesParse::MolFromSmiles(smi);
      REQUIRE(m);
      auto res = Chirality::findMesoCenters(*m);
      REQUIRE(res.size() == expected.size());
      CHECK(res == expected);
      for (auto [a1, a2] : res) {
        unsigned int oa = m->getNumAtoms() + 1;
        CHECK(m->getAtomWithIdx(a1)->getPropIfPresent(
            common_properties::_mesoOtherAtom, oa));
        CHECK(oa == a2);
        CHECK(m->getAtomWithIdx(a2)->getPropIfPresent(
            common_properties::_mesoOtherAtom, oa));
        CHECK(oa == a1);
      }
    }
  }
  SECTION("with enhanced stereo") {
    std::vector<std::pair<std::string,
                          std::vector<std::pair<unsigned int, unsigned int>>>>
        cases{{"N[C@H]1CC[C@@H](O)CC1 |o1:1,4|", {{1, 4}}}};
    for (auto &[smi, expected] : cases) {
      INFO(smi);
      auto m = v2::SmilesParse::MolFromSmiles(smi);
      REQUIRE(m);
      auto res = Chirality::findMesoCenters(*m);
      REQUIRE(res.size() == expected.size());
      CHECK(res == expected);
    }
  }
  SECTION("options") {
    {
      auto m = "[13CH3][C@@H](C)C[C@@H](C)[13CH3]"_smiles;
      REQUIRE(m);
      auto res = Chirality::findMesoCenters(*m);
      REQUIRE(res.size() == 1);
      CHECK(res == std::vector<std::pair<unsigned int, unsigned int>>{{1, 4}});
      bool includeIsotopes = false;
      res = Chirality::findMesoCenters(*m, includeIsotopes);
      REQUIRE(res.empty());
    }
  }
}

TEST_CASE(
    "github #7598: FindPotentialStereo() missing some results if cleanIt is False") {
  SECTION("as reported") {
    std::vector<std::string> smileses = {"C[C@H](F)C(C)[C@H](F)C",
                                         "CC([C@H](C)F)[C@@H](C)F",
                                         "CC([C@H](C)F)[C@H](F)C"};
    for (const auto &smi : smileses) {
      auto m = v2::SmilesParse::MolFromSmiles(smi);
      REQUIRE(m);
      bool flagPossible = true;
      for (bool cleanIt : {true, false}) {
        auto si = Chirality::findPotentialStereo(*m, cleanIt, flagPossible);
        INFO(smi + " " + std::to_string(cleanIt));
        CHECK(si.size() == 3);
      }
    }
  }
}

TEST_CASE(
    "GitHub Issue #7929: AssignStereochemistry(cleanIt=True) does not clean _CIPCode property on bonds") {
  UseLegacyStereoPerceptionFixture reset_stereo_perception(true);

  auto m = "CC"_smiles;
  REQUIRE(m);

  m->getAtomWithIdx(0)->setProp(common_properties::_CIPCode, "X");
  m->getBondWithIdx(0)->setProp(common_properties::_CIPCode, "X");

  bool clean = true;
  bool flag = true;
  bool force = true;

  SECTION("legacy stereo perception") {
    Chirality::setUseLegacyStereoPerception(true);

    RDKit::MolOps::assignStereochemistry(*m, clean, force, flag);

    CHECK(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode) == false);
    CHECK(m->getBondWithIdx(0)->hasProp(common_properties::_CIPCode) == false);
  }
  SECTION("new stereo perception") {
    Chirality::setUseLegacyStereoPerception(false);

    RDKit::MolOps::assignStereochemistry(*m, clean, force, flag);

    CHECK(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode) == false);
    CHECK(m->getBondWithIdx(0)->hasProp(common_properties::_CIPCode) == false);
  }
}

TEST_CASE("Github issue #7983: stereogroup lost on chiral sulfoxide") {
  SECTION("basics") {
    auto m = R"CTAB(
  Mrv2317 02032512242D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 21 22 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C -4.001 -2.31 0 0
M  V30 2 C -2.6674 -3.08 0 0
M  V30 3 C -1.3337 -2.31 0 0
M  V30 4 C 0 -3.08 0 0
M  V30 5 C 0 -4.6198 0 0
M  V30 6 N 1.3337 -5.3898 0 0
M  V30 7 C 2.6674 -4.6198 0 0
M  V30 8 O 2.6674 -3.08 0 0
M  V30 9 O 4.001 -5.3898 0 0
M  V30 10 C 5.3345 -4.6198 0 0
M  V30 11 C 6.6682 -5.3898 0 0
M  V30 12 C 8.0019 -4.6198 0 0
M  V30 13 C 9.3356 -5.3898 0 0
M  V30 14 C 9.3356 -6.9298 0 0
M  V30 15 C 8.0019 -7.6998 0 0
M  V30 16 C 6.6682 -6.9298 0 0
M  V30 17 S 5.3345 -7.6998 0 0 CFG=2
M  V30 18 C 4.001 -6.9298 0 0
M  V30 19 O 5.3345 -9.2398 0 0
M  V30 20 C -1.3337 -5.3898 0 0
M  V30 21 C -2.6674 -4.6198 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 4 2 3
M  V30 3 4 3 4
M  V30 4 4 4 5
M  V30 5 1 5 6
M  V30 6 1 6 7
M  V30 7 2 7 8
M  V30 8 1 7 9
M  V30 9 1 9 10
M  V30 10 1 10 11
M  V30 11 4 11 12
M  V30 12 4 12 13
M  V30 13 4 13 14
M  V30 14 4 14 15
M  V30 15 4 15 16
M  V30 16 4 11 16
M  V30 17 1 17 16
M  V30 18 2 17 19
M  V30 19 4 5 20
M  V30 20 4 20 21
M  V30 21 4 2 21
M  V30 22 1 17 18 CFG=1
M  V30 END BOND
M  V30 BEGIN COLLECTION
M  V30 MDLV30/STEABS ATOMS=(1 17)
M  V30 END COLLECTION
M  V30 END CTAB
M  END)CTAB"_ctab;
    REQUIRE(m);
    auto &sgs = m->getStereoGroups();
    REQUIRE(sgs.size() == 1);
    CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_ABSOLUTE);
    CHECK(sgs[0].getAtoms().size() == 1);
    CHECK(sgs[0].getAtoms().at(0)->getIdx() == 16);
  }
  SECTION("making sure sulfoxides do not trigger atropisomerism") {
    auto m = R"CTAB(
     RDKit          2D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 10 10 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C 1.892852 4.651432 0.000000 0
M  V30 2 C 3.199931 3.915535 0.000000 0
M  V30 3 C 3.216171 2.415622 0.000000 0
M  V30 4 C 1.925330 1.651602 0.000000 0
M  V30 5 C 0.618251 2.387492 0.000000 0
M  V30 6 C 0.602011 3.887408 0.000000 0
M  V30 7 S -0.705072 4.623298 0.000000 0
M  V30 8 C -0.672591 1.623472 0.000000 0
M  V30 9 O -0.721314 6.123210 0.000000 0
M  V30 10 C -1.995913 3.859276 0.000000 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 2 1 2
M  V30 2 1 2 3
M  V30 3 2 3 4
M  V30 4 1 4 5
M  V30 5 2 5 6
M  V30 6 1 6 1 CFG=1
M  V30 7 1 6 7
M  V30 8 1 5 8
M  V30 9 2 7 9
M  V30 10 1 7 10 CFG=1
M  V30 END BOND
M  V30 BEGIN COLLECTION
M  V30 MDLV30/STEABS ATOMS=(1 7)
M  V30 END COLLECTION
M  V30 END CTAB
M  END
$$$$
)CTAB"_ctab;
    REQUIRE(m);
    CHECK(m->getAtomWithIdx(6)->getChiralTag() !=
          Atom::ChiralType::CHI_UNSPECIFIED);
    CHECK(m->getBondBetweenAtoms(5, 6)->getStereo() ==
          Bond::BondStereo::STEREONONE);
  }
  SECTION("examples from #8323") {
    std::string pathName = getenv("RDBASE");
    pathName += "/Code/GraphMol/test_data/Github8323.sdf";
    SDMolSupplier suppl(pathName);
    while (!suppl.atEnd()) {
      std::unique_ptr<ROMol> mol(suppl.next());
      REQUIRE(mol);
      auto &sgs = mol->getStereoGroups();
      REQUIRE(sgs.size() == 1);
      REQUIRE(mol->hasProp("StereoGroupOnAtom"));
      auto aid = std::stoul(mol->getProp<std::string>("StereoGroupOnAtom"));
      CHECK(sgs[0].getAtoms().size() == 1);
      CHECK(sgs[0].getAtoms().at(0)->getIdx() == aid);
    }
  }
}

TEST_CASE("Github #8420: imines and crossed bonds") {
  bool useLegacy = GENERATE(true, false);
  CAPTURE(useLegacy);
  UseLegacyStereoPerceptionFixture reset_stereo_perception(useLegacy);
  SECTION("as reported") {
    std::string ctab = R"CTAB(
  MJ250100

  7  7  0  0  1  0  0  0  0  0999 V2000
    0.6961    0.4995    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.0196    1.7395    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.7332    0.4968    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    1.4118    0.9090    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.4118    1.7395    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.6961    2.1583    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.0196    0.9101    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  3  7  2  3  0  0  0
  4  5  1  0  0  0  0
  5  6  1  0  0  0  0
  2  6  1  0  0  0  0
  2  7  1  0  0  0  0
  1  4  1  0  0  0  0
  1  7  1  0  0  0  0
M  END)CTAB";
    auto m = v2::FileParsers::MolFromMolBlock(ctab);
    REQUIRE(m);
    CHECK(m->getBondWithIdx(0)->getStereo() == Bond::BondStereo::STEREONONE);
  }
}

TEST_CASE(
    "Github #8712: Modern stereo + 3D SD file leads to bad stereo detection for some molecules") {
  UseLegacyStereoPerceptionFixture reset_stereo_perception(false);

  constexpr bool cleanUpStereo = false;
  constexpr bool flagPossibleCenters = true;

  SECTION("as reported") {
    std::string ctab = R"CTAB(
     RDKit          3D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 23 23 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C 2.628222 -0.014974 0.390914 0
M  V30 2 N 1.918195 0.260032 -0.835284 0
M  V30 3 C 0.487110 0.206615 -0.681308 0
M  V30 4 C 0.079788 -1.165009 -0.217881 0
M  V30 5 C -1.428295 -1.378335 -0.390798 0
M  V30 6 S -2.156694 -0.136366 0.671587 0
M  V30 7 O -1.585579 -0.347357 2.045208 0
M  V30 8 O -3.651696 -0.172351 0.647489 0
M  V30 9 C -1.519569 1.415993 0.067834 0
M  V30 10 C -0.010801 1.307036 0.207366 0
M  V30 11 H 1.988100 -0.320407 1.230786 0
M  V30 12 H 3.238742 0.867078 0.711200 0
M  V30 13 H 3.359131 -0.825206 0.185242 0
M  V30 14 H 2.223613 -0.317010 -1.647727 0
M  V30 15 H 0.041778 0.347830 -1.688472 0
M  V30 16 H 0.284353 -1.370499 0.832996 0
M  V30 17 H 0.580676 -1.905979 -0.844599 0
M  V30 18 H -1.673500 -2.379109 -0.016810 0
M  V30 19 H -1.696056 -1.195326 -1.452807 0
M  V30 20 H -1.827609 1.481487 -0.996993 0
M  V30 21 H -1.921584 2.283368 0.626641 0
M  V30 22 H 0.172726 1.108075 1.288878 0
M  V30 23 H 0.468947 2.250412 -0.133459 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 1 3 4
M  V30 4 1 4 5
M  V30 5 1 5 6
M  V30 6 2 6 7
M  V30 7 2 6 8
M  V30 8 1 6 9
M  V30 9 1 9 10
M  V30 10 1 10 3
M  V30 11 1 1 11
M  V30 12 1 1 12
M  V30 13 1 1 13
M  V30 14 1 2 14
M  V30 15 1 3 15
M  V30 16 1 4 16
M  V30 17 1 4 17
M  V30 18 1 5 18
M  V30 19 1 5 19
M  V30 20 1 9 20
M  V30 21 1 9 21
M  V30 22 1 10 22
M  V30 23 1 10 23
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB";
    auto m = v2::FileParsers::MolFromMolBlock(ctab);
    REQUIRE(m);

    auto centers =
        Chirality::findPotentialStereo(*m, cleanUpStereo, flagPossibleCenters);
    CHECK(centers.empty());
  }
  SECTION("overcorrection #1") {
    // Do not break this one!
    // This one comes from the doctests. I'm adding it here because it seems we
    // don't have this case in any other C++ test.
    auto m = R"SMI(C1C[C@H](C)[C@H](C)[C@H](C)C1)SMI"_smiles;
    REQUIRE(m);
    auto centers =
        Chirality::findPotentialStereo(*m, cleanUpStereo, flagPossibleCenters);
    CHECK(centers.size() == 3);
  }
  SECTION("overcorrection #2") {
    // Do not break this one either!
    // This case seems to be related to the order of the atoms, since I haven't
    // been able to reproduce with the equivalent SMILES with explicit Hs.
    auto m = R"SMI(C[C@H]1C[C@@H](C)C1)SMI"_smiles;
    REQUIRE(m);
    MolOps::addHs(*m);  // This only manifests if Hs are present
    auto centers =
        Chirality::findPotentialStereo(*m, cleanUpStereo, flagPossibleCenters);
    CHECK(centers.size() == 2);
  }
}

#if 1
TEST_CASE(
    "Github #8689: stereo canonicalization depends on bond iteration order") {
  bool useLegacy = GENERATE(true, false);
  CAPTURE(useLegacy);
  UseLegacyStereoPerceptionFixture reset_stereo_perception(useLegacy);

  std::string pathName = getenv("RDBASE");
  pathName += "/Code/GraphMol/test_data/";

  SECTION("simplified") {
    pathName += "github8689_2.sdf";
    v2::FileParsers::SDMolSupplier suppl(pathName);
    auto m1 = suppl[0];
    REQUIRE(m1);
    auto m2 = suppl[1];
    REQUIRE(m2);
    // m1->debugMol(std::cerr);
    // m2->debugMol(std::cerr);

    CIPLabeler::assignCIPLabels(*m1);
    CIPLabeler::assignCIPLabels(*m2);
    REQUIRE(m1->getAtomWithIdx(7)->hasProp(common_properties::_CIPCode));
    REQUIRE(m1->getAtomWithIdx(8)->hasProp(common_properties::_CIPCode));
    REQUIRE(m2->getAtomWithIdx(7)->hasProp(common_properties::_CIPCode));
    REQUIRE(m2->getAtomWithIdx(8)->hasProp(common_properties::_CIPCode));

    CHECK(m1->getAtomWithIdx(7)->getProp<std::string>(
              common_properties::_CIPCode) ==
          m2->getAtomWithIdx(7)->getProp<std::string>(
              common_properties::_CIPCode));
    CHECK(m1->getAtomWithIdx(8)->getProp<std::string>(
              common_properties::_CIPCode) ==
          m2->getAtomWithIdx(8)->getProp<std::string>(
              common_properties::_CIPCode));

    auto smi1 = MolToSmiles(*m1);
    auto smi2 = MolToSmiles(*m2);
    CHECK(smi1 == smi2);
  }
}
#endif
TEST_CASE("extra ring stereo with new stereo perception") {
  UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
  SECTION("basics") {
    std::string smi = "C2O[C@H]3[C@@]4([C@](CCC(C24))(O)CC=C3)C";
    auto m = v2::SmilesParse::MolFromSmiles(smi);
    REQUIRE(m);
    for (auto idx : {2, 3, 4}) {
      INFO(idx);
      const auto atm = m->getAtomWithIdx(idx);
      REQUIRE(atm);
      CHECK(atm->getChiralTag() != Atom::ChiralType::CHI_UNSPECIFIED);
      CHECK(!atm->hasProp(common_properties::_ringStereoAtoms));
    }
  }
  SECTION("don't destroy actual ring stereo") {
    std::string smi = "C[C@@H]1CC[C@@H](C)CC1";
    auto m = v2::SmilesParse::MolFromSmiles(smi);
    REQUIRE(m);
    // m->debugMol(std::cerr);
    // for (const auto atm : m->atoms()) {
    //   std::cerr << atm->getIdx() << ": "
    //             << atm->getProp<unsigned int>(
    //                    common_properties::_ChiralAtomRank)
    //             << std::endl;
    // }
    for (auto idx : {1, 4}) {
      INFO(idx);
      const auto atm = m->getAtomWithIdx(idx);
      REQUIRE(atm);
      CHECK(atm->getChiralTag() != Atom::ChiralType::CHI_UNSPECIFIED);
      CHECK(atm->hasProp(common_properties::_ringStereoAtoms));
    }
  }
  SECTION("#8956 ensure ring stereochemistry is not inverted on round trip") {
    auto useLegacy = GENERATE(true, false);
    CAPTURE(useLegacy);
    UseLegacyStereoPerceptionFixture fx(useLegacy);
    auto [smi1, smi2] = GENERATE(
        std::make_pair("CC[C@]1(C)CCC[C@](C)(O)C1",
                       "CC[C@@]1(C)CCC[C@@](C)(O)C1"),
        std::make_pair("C[C@H]1CCC[C@](C)(O)C1", "C[C@@H]1CCC[C@@](C)(O)C1"));
    auto m1 = v2::SmilesParse::MolFromSmiles(smi1);
    REQUIRE(m1);
    auto m2 = v2::SmilesParse::MolFromSmiles(smi2);
    REQUIRE(m2);

    MolOps::assignStereochemistry(*m1, true, true, true);
    MolOps::assignStereochemistry(*m2, true, true, true);

    auto roundtrip1 = MolToSmiles(*m1);
    auto roundtrip2 = MolToSmiles(*m2);
    CHECK(roundtrip1 == smi1);
    CHECK(roundtrip2 == smi2);
    CHECK(roundtrip2 != roundtrip1);
  }
}
TEST_CASE("ring stereo basics with new stereo") {
  UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
  auto m = "CC(C)[C@H]1CCCCN1C(=O)[C@H]1CC[C@@H](C)CC1 |a:3,11,&1:14|"_smiles;
  REQUIRE(m);
  auto smi = MolToCXSmiles(*m);
  CHECK(smi == "CC(C)[C@H]1CCCCN1C(=O)[C@H]1CC[C@@H](C)CC1 |a:3,11,&1:14|");
}

TEST_CASE("zero chiral volume and T shape molecule") {
  std::string pathName = getenv("RDBASE");
  pathName += "/Code/GraphMol/test_data/";

  SECTION("simplified") {
    auto m1 = R"CTAB(178 degrees (not T shaped)
     RDKit          2D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 4 3 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C  0.000000 1.000000 0.000000 0
M  V30 2 C  0.000000 0.000000 0.000000 0
M  V30 3 O -0.999847 -0.017452 0.000000 0
M  V30 4 F  0.999847 -0.017452 0.000000 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 2 1 CFG=1
M  V30 2 1 2 3
M  V30 3 1 2 4
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
    REQUIRE(m1);
    // make sure we perceived it correctly:
    CHECK(m1->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
    // make sure we round trip it:
    auto m1mb = MolToV3KMolBlock(*m1);
    auto m1r = v2::FileParsers::MolFromMolBlock(m1mb);
    REQUIRE(m1r);
    CHECK(MolToSmiles(*m1) == MolToSmiles(*m1r));

    auto m2 = R"CTAB(179 degrees (T shaped)
     RDKit          2D

  0  0  0  0  0  0  0  0  0  0999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 4 3 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C  0.000000 1.000000 0.000000 0
M  V30 2 C  0.000000 0.000000 0.000000 0
M  V30 3 O -0.999962 -0.0087265 0.000000 0
M  V30 4 F  0.999962 -0.0087265 0.000000 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 2 1 CFG=1
M  V30 2 1 2 3
M  V30 3 1 2 4
M  V30 END BOND
M  V30 END CTAB
M  END
)CTAB"_ctab;
    REQUIRE(m2);
    CHECK(m2->getAtomWithIdx(1)->getChiralTag() ==
          Atom::ChiralType::CHI_TETRAHEDRAL_CW);
    auto m2mb = MolToV3KMolBlock(*m2);
    auto m2r = v2::FileParsers::MolFromMolBlock(m2mb);
    REQUIRE(m2r);
    CHECK(MolToSmiles(*m2) == MolToSmiles(*m2r));
  }

  SECTION("as reported") {
    pathName += "zero_chiral_volume_1.sdf";
    auto m = v2::FileParsers::MolFromMolFile(pathName);
    REQUIRE(m);
    for (const auto idx : {15, 22, 39}) {
      INFO(idx);
      CHECK(m->getAtomWithIdx(idx)->getChiralTag() !=
            Atom::ChiralType::CHI_UNSPECIFIED);
    }
  }
  SECTION("large rings") {
    for (const std::string fn :
         {"zero_chiral_volume_2.60.mol", "zero_chiral_volume_2.80.mol"}) {
      INFO(fn);
      std::string lpath = pathName + fn;
      auto m = v2::FileParsers::MolFromMolFile(lpath);
      REQUIRE(m);
      CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
            Atom::ChiralType::CHI_TETRAHEDRAL_CW);
    }
  }
  SECTION("mol block round trip") {
    std::string rdbase = getenv("RDBASE");
    std::string fname =
        rdbase + "/Code/GraphMol/test_data/zero_chiral_volume_simple.mol";
    auto mol = v2::FileParsers::MolFromMolFile(fname);
    REQUIRE(mol);

    {
      ROMol mol2(*mol);
      Chirality::wedgeMolBonds(mol2);
      CHECK(mol2.getBondBetweenAtoms(0, 1)->getBondDir() ==
            Bond::BondDir::BEGINDASH);
    }
    auto mb = MolToMolBlock(*mol);
    auto mol2 = v2::FileParsers::MolFromMolBlock(mb);

    auto smi = MolToSmiles(*mol);
    auto smimb = MolToSmiles(*mol2);
    CHECK(smi == smimb);
  }
}

TEST_CASE(
    "GitHub Issue #8965: Stereo bond inversion in SMILES Writer canonicalization") {
  const auto legacy_stereo = GENERATE(true, false);
  CAPTURE(legacy_stereo);
  UseLegacyStereoPerceptionFixture stereo_mode(legacy_stereo);

  // These are all (non-canonical) iterations of mol in the issue
  constexpr std::array<std::string_view, 4> samples = {
      R"smi(C/C=C\C=C(/C=C\C)C(/C=C\C)=C/C)smi",  // the reported SMILES
      R"smi(C/C=C(\C=C/C)C(/C=C\C)=C/C=C\C)smi",
      R"smi(C/C=C\C(=C/C)C(/C=C\C)=C/C=C\C)smi",
      R"smi(C/C=C\C(=C/C=C\C)C(/C=C\C)=C/C)smi",
  };

  // These mol are small and simple enough for CIP calculation to not
  // be too expensive.
  // This is not exhaustive (we should check which bond is what),
  // but it should be enough for this test.
  auto get_bond_stereo_labels = [](auto &m) {
    CIPLabeler::assignCIPLabels(m);
    std::vector<std::string> bond_labels;
    for (auto b : m.bonds()) {
      std::string label;
      if (b->getPropIfPresent(common_properties::_CIPCode, label)) {
        bond_labels.push_back(label);
      }
    }
    std::ranges::sort(bond_labels);
    return bond_labels;
  };

  std::optional<std::vector<std::string>> labels_reference;

  std::set<std::string> canonicalized_smiles;
  for (auto smiles : samples) {
    auto m =
        v2::SmilesParse::MolFromSmiles(static_cast<const std::string>(smiles));
    REQUIRE(m);

    auto canonical_smiles = MolToSmiles(*m);
    canonicalized_smiles.insert(canonical_smiles);

    // Do this after getting the canonical SMILES, so that
    // it doesn't have any chances to influence the canonicalization
    if (!labels_reference.has_value()) {
      labels_reference = get_bond_stereo_labels(*m);
    } else {
      CHECK(*labels_reference == get_bond_stereo_labels(*m));
    }
  }

  // All samples should canonicalize to the same SMILES
  REQUIRE(canonicalized_smiles.size() == 1);

  // ... and the canonical SMILES should preserve stereo labels
  auto m = v2::SmilesParse::MolFromSmiles(*canonicalized_smiles.begin());
  REQUIRE(m);
  CHECK(*labels_reference == get_bond_stereo_labels(*m));
}
