#pragma once
// hyper_ideal_hessian.hpp
//
// Phase 4a — Hessian of the hyper-ideal discrete conformal functional.
//
// ┌──────────────────────────────────────────────────────────────────────────┐
// │  Implementation strategy                                                 │
// │                                                                          │
// │  The hyper-ideal functional involves angle functions (ζ, σ, α, β)       │
// │  composed through several nested layers (lij → ζ13/14/15 → β/α).       │
// │  Deriving closed-form Hessian entries analytically through all these     │
// │  layers is feasible but lengthy; an analytical Hessian is left for a    │
// │  future phase.                                                           │
// │                                                                          │
// │  Here we compute the Hessian by symmetric finite differences of the     │
// │  gradient, which is exact to O(ε²) and sufficient for Newton's method   │
// │  at the meshes typical in Phase 4 (< 500 DOFs):                         │
// │                                                                          │
// │    H[i,j] = (G(x + ε·eⱼ)[i] − G(x − ε·eⱼ)[i]) / (2ε)                │
// │                                                                          │
// │  The hyper-ideal energy is strictly convex (Springborn 2020), so H is   │
// │  positive semi-definite everywhere and Eigen::SimplicialLDLT applies     │
// │  directly.                                                               │
// └──────────────────────────────────────────────────────────────────────────┘

#include "hyper_ideal_functional.hpp"
#include <Eigen/Sparse>
#include <vector>
#include <cmath>

namespace conformallab {

// ── Numerical Hessian via symmetric finite differences ────────────────────────
//
// Returns the n×n sparse Hessian, where n = hyper_ideal_dimension(mesh, m).
// eps: finite-difference step size (default 1e-5 gives ~1e-10 relative error).
inline Eigen::SparseMatrix<double> hyper_ideal_hessian(
    ConformalMesh&             mesh,
    const std::vector<double>& x,
    const HyperIdealMaps&      m,
    double                     eps = 1e-5)
{
    const int n = hyper_ideal_dimension(mesh, m);
    std::vector<Eigen::Triplet<double>> trips;
    trips.reserve(static_cast<std::size_t>(n * n));   // dense upper bound

    std::vector<double> xp = x, xm = x;

    for (int j = 0; j < n; ++j) {
        const std::size_t sj = static_cast<std::size_t>(j);
        xp[sj] = x[sj] + eps;
        xm[sj] = x[sj] - eps;

        auto Gp = evaluate_hyper_ideal(mesh, xp, m, /*energy=*/false).gradient;
        auto Gm = evaluate_hyper_ideal(mesh, xm, m, /*energy=*/false).gradient;

        xp[sj] = xm[sj] = x[sj];   // restore

        for (int i = 0; i < n; ++i) {
            double val = (Gp[static_cast<std::size_t>(i)]
                        - Gm[static_cast<std::size_t>(i)]) / (2.0 * eps);
            if (std::abs(val) > 1e-15)
                trips.emplace_back(i, j, val);
        }
    }

    Eigen::SparseMatrix<double> H(n, n);
    H.setFromTriplets(trips.begin(), trips.end());
    return H;
}

// ── Symmetrised Hessian ───────────────────────────────────────────────────────
//
// The FD Hessian is symmetric in exact arithmetic; floating-point rounding
// can introduce tiny asymmetries.  This helper returns (H + Hᵀ)/2.
inline Eigen::SparseMatrix<double> hyper_ideal_hessian_sym(
    ConformalMesh&             mesh,
    const std::vector<double>& x,
    const HyperIdealMaps&      m,
    double                     eps = 1e-5)
{
    auto H = hyper_ideal_hessian(mesh, x, m, eps);
    Eigen::SparseMatrix<double> Ht = H.transpose();
    return (H + Ht) * 0.5;
}

} // namespace conformallab
