// Copyright (c) 2025-2026, SFCGAL team.
// SPDX-License-Identifier: LGPL-2.0-or-later

#include "constructors.hpp"

#include <SFCGAL/Kernel.h>
#include <SFCGAL/PolyhedralSurface.h>
#include <SFCGAL/Solid.h>
#include <SFCGAL/algorithm/rotate.h>
#include <SFCGAL/algorithm/translate.h>
#include <SFCGAL/numeric.h>
#include <SFCGAL/primitive3d/Box.h>
#include <SFCGAL/primitive3d/Cone.h>
#include <SFCGAL/primitive3d/Cylinder.h>
#include <SFCGAL/primitive3d/Sphere.h>
#include <SFCGAL/primitive3d/Torus.h>

#include <algorithm>
#include <cmath>

namespace Constructors {

auto
make_sphere(double x, double y, double z, double radius,
            unsigned int num_subdivisions) -> std::unique_ptr<SFCGAL::Geometry>
{
  SFCGAL::Sphere sphere(radius, num_subdivisions);

  // Apply translation to position the center at (x, y, z)
  if (std::abs(x) > SFCGAL::EPSILON || std::abs(y) > SFCGAL::EPSILON ||
      std::abs(z) > SFCGAL::EPSILON) {
    sphere.translate(SFCGAL::Kernel::Vector_3(x, y, z));
  }

  auto polyhedral_surface = sphere.generatePolyhedralSurface();
  return std::make_unique<SFCGAL::PolyhedralSurface>(
      std::move(polyhedral_surface));
}

auto
make_box(double base_x, double base_y, double base_z, double x_extent,
         double y_extent, double z_extent) -> std::unique_ptr<SFCGAL::Geometry>
{
  SFCGAL::Box box(x_extent, y_extent, z_extent);

  // Apply translation to position the base at (base_x, base_y, base_z)
  if (std::abs(base_x) > SFCGAL::EPSILON ||
      std::abs(base_y) > SFCGAL::EPSILON ||
      std::abs(base_z) > SFCGAL::EPSILON) {
    box.translate(SFCGAL::Kernel::Vector_3(base_x, base_y, base_z));
  }

  auto polyhedral_surface = box.generatePolyhedralSurface();
  return std::make_unique<SFCGAL::PolyhedralSurface>(
      std::move(polyhedral_surface));
}

auto
make_cube(double base_x, double base_y, double base_z, double size)
    -> std::unique_ptr<SFCGAL::Geometry>
{
  return make_box(base_x, base_y, base_z, size, size, size);
}

auto
make_cylinder(double base_x, double base_y, double base_z, double axis_x,
              double axis_y, double axis_z, double radius, double height,
              unsigned int num_radial) -> std::unique_ptr<SFCGAL::Geometry>
{
  SFCGAL::Cylinder cylinder(radius, height, num_radial);

  // Normalize the axis vector
  double axis_length =
      std::sqrt((axis_x * axis_x) + (axis_y * axis_y) + (axis_z * axis_z));

  // If axis is non-zero and not aligned with +Z, apply rotation
  if (axis_length > SFCGAL::EPSILON) {
    double nx = axis_x / axis_length;
    double ny = axis_y / axis_length;
    double nz = axis_z / axis_length;

    // Check if axis is not already aligned with +Z (0, 0, 1)
    if (std::abs(nx) > SFCGAL::EPSILON || std::abs(ny) > SFCGAL::EPSILON ||
        std::abs(nz - 1.0) > SFCGAL::EPSILON) {
      // Compute rotation axis: cross product of +Z with desired axis
      // +Z = (0, 0, 1), so cross product is (ny, -nx, 0) (normalized later)
      SFCGAL::Kernel::Vector_3 rotation_axis(ny, -nx, 0);

      // Compute rotation angle using dot product: +Z · normalized_axis = nz
      double angle = std::acos(std::clamp(nz, -1.0, 1.0));

      if (std::abs(angle) > SFCGAL::EPSILON) {
        cylinder.rotate(angle, rotation_axis);
      }
    }
  }

  // Apply translation to position the base at (base_x, base_y, base_z)
  if (std::abs(base_x) > SFCGAL::EPSILON ||
      std::abs(base_y) > SFCGAL::EPSILON ||
      std::abs(base_z) > SFCGAL::EPSILON) {
    cylinder.translate(SFCGAL::Kernel::Vector_3(base_x, base_y, base_z));
  }

  auto polyhedral_surface = cylinder.generatePolyhedralSurface();
  return std::make_unique<SFCGAL::PolyhedralSurface>(
      std::move(polyhedral_surface));
}

auto
make_cone(double base_x, double base_y, double base_z, double axis_x,
          double axis_y, double axis_z, double bottom_radius, double top_radius,
          double height, unsigned int num_radial)
    -> std::unique_ptr<SFCGAL::Geometry>
{
  // SFCGAL::Cone constructor creates a cone with base centered at origin,
  // oriented along +Z axis. We need to apply transformations for custom
  // position/orientation.

  SFCGAL::Cone cone(bottom_radius, top_radius, height, num_radial);

  // Normalize the axis vector
  double axis_length =
      std::sqrt((axis_x * axis_x) + (axis_y * axis_y) + (axis_z * axis_z));

  // If axis is non-zero and not aligned with +Z, apply rotation
  if (axis_length > SFCGAL::EPSILON) {
    double nx = axis_x / axis_length;
    double ny = axis_y / axis_length;
    double nz = axis_z / axis_length;

    // Check if axis is not already aligned with +Z (0, 0, 1)
    if (std::abs(nx) > SFCGAL::EPSILON || std::abs(ny) > SFCGAL::EPSILON ||
        std::abs(nz - 1.0) > SFCGAL::EPSILON) {
      // Compute rotation axis: cross product of +Z with desired axis
      // +Z = (0, 0, 1), so cross product is (ny, -nx, 0) (normalized later)
      SFCGAL::Kernel::Vector_3 rotation_axis(ny, -nx, 0);

      // Compute rotation angle using dot product: +Z · normalized_axis = nz
      double angle = std::acos(std::clamp(nz, -1.0, 1.0));

      if (std::abs(angle) > SFCGAL::EPSILON) {
        cone.rotate(angle, rotation_axis);
      }
    }
  }

  // Apply translation to position the base at (base_x, base_y, base_z)
  if (std::abs(base_x) > SFCGAL::EPSILON ||
      std::abs(base_y) > SFCGAL::EPSILON ||
      std::abs(base_z) > SFCGAL::EPSILON) {
    cone.translate(SFCGAL::Kernel::Vector_3(base_x, base_y, base_z));
  }

  auto polyhedral_surface = cone.generatePolyhedralSurface();
  return std::make_unique<SFCGAL::PolyhedralSurface>(
      std::move(polyhedral_surface));
}

auto
make_torus(double center_x, double center_y, double center_z, double axis_x,
           double axis_y, double axis_z, double major_radius,
           double minor_radius, unsigned int num_major, unsigned int num_minor)
    -> std::unique_ptr<SFCGAL::Geometry>
{
  // SFCGAL::Torus constructor creates a torus centered at origin,
  // oriented in the XY plane (normal along +Z axis).
  // We need to apply transformations for custom position/orientation.

  SFCGAL::Torus torus(major_radius, minor_radius, num_major, num_minor);

  // Normalize the axis vector
  double axis_length =
      std::sqrt((axis_x * axis_x) + (axis_y * axis_y) + (axis_z * axis_z));

  // If axis is non-zero and not aligned with +Z, apply rotation
  if (axis_length > SFCGAL::EPSILON) {
    double nx = axis_x / axis_length;
    double ny = axis_y / axis_length;
    double nz = axis_z / axis_length;

    // Check if axis is not already aligned with +Z (0, 0, 1)
    if (std::abs(nx) > SFCGAL::EPSILON || std::abs(ny) > SFCGAL::EPSILON ||
        std::abs(nz - 1.0) > SFCGAL::EPSILON) {
      // Compute rotation axis: cross product of +Z with desired axis
      // +Z = (0, 0, 1), so cross product is (ny, -nx, 0)
      SFCGAL::Kernel::Vector_3 rotation_axis(ny, -nx, 0);

      // Compute rotation angle using dot product: +Z · normalized_axis = nz
      double angle = std::acos(std::clamp(nz, -1.0, 1.0));

      if (std::abs(angle) > SFCGAL::EPSILON) {
        torus.rotate(angle, rotation_axis);
      }
    }
  }

  // Apply translation to position the center at (center_x, center_y, center_z)
  if (std::abs(center_x) > SFCGAL::EPSILON ||
      std::abs(center_y) > SFCGAL::EPSILON ||
      std::abs(center_z) > SFCGAL::EPSILON) {
    torus.translate(SFCGAL::Kernel::Vector_3(center_x, center_y, center_z));
  }

  auto polyhedral_surface = torus.generatePolyhedralSurface();
  return std::make_unique<SFCGAL::PolyhedralSurface>(
      std::move(polyhedral_surface));
}

auto
make_solid(std::unique_ptr<SFCGAL::Geometry> polyhedralsurface)
    -> std::unique_ptr<SFCGAL::Geometry>
{
  // Check if input is a PolyhedralSurface
  if (polyhedralsurface->geometryTypeId() != SFCGAL::TYPE_POLYHEDRALSURFACE) {
    throw std::invalid_argument(
        "make_solid: input geometry must be a PolyhedralSurface");
  }

  // Create and return the Solid with the PolyhedralSurface as exterior shell
  return std::make_unique<SFCGAL::Solid>(polyhedralsurface->clone());
}

} // namespace Constructors
