// Generated by hand for Phase B / vote schema v3 (B3 deliverable).
// The other prover/*_gen.go files come from prover/synth/synth.go;
// this circuit doesn't fit the synth flow because the metamodel has
// no notion of curve points, so it lives next to its generated peers
// to keep the prover/ layout flat.

package prover

import (
	"github.com/consensys/gnark-crypto/ecc/twistededwards"
	"github.com/consensys/gnark/frontend"
	tedwards "github.com/consensys/gnark/std/algebra/native/twistededwards"
)

// VoteCastHomomorphicChoices is the bin-count K for the K=8 variant.
// A poll with fewer than K choices pads its one-hot vector with zeros
// at the unused indices — the Σ V = 1 constraint still pins exactly
// one bin to 1.
const VoteCastHomomorphicChoices = 8

// MerkleDepth must match the voter registry tree depth used by
// VoteCastCircuit (kept in sync with prover/vote_gen.go).
const homomorphicMerkleDepth = 20

// VoteCastHomomorphicCircuit_8 — per-voter ZK proof of a well-formed
// encrypted ballot (K=8) plus voter-registry / nullifier privacy. See
// docs/homomorphic-tally-spec.md for the protocol. Public inputs are
// what the server / verifier consumes to build the aggregate; private
// witness is everything that must stay with the voter.
//
// Public:
//   PollID, VoterRegistryRoot, Nullifier, MaxChoices, PkCreator (X,Y),
//   CtA[K] (X,Y), CtB[K] (X,Y)
//
// Private:
//   VoterSecret, VoterWeight, V[K], R[K], PathElements[D], PathIndices[D]
//
// Constraints:
//
//  1. one-hot:        each V[j] ∈ {0,1};  Σ V[j] = 1
//  2. range bound:    Σ over indices [MaxChoices, K) of V[j] = 0
//                     (extra bins must be empty when poll has < K choices)
//  3. ElGamal bind:   CtA[j] = G · R[j]
//                     CtB[j] = G · V[j] + PkCreator · R[j]
//  4. Merkle:         leaf = mimc(secret, weight); fold along path
//                     to VoterRegistryRoot
//  5. Nullifier:      Nullifier = mimc(secret, PollID)
type VoteCastHomomorphicCircuit_8 struct {
	// --- public ---
	PollID            frontend.Variable `gnark:",public"`
	VoterRegistryRoot frontend.Variable `gnark:",public"`
	Nullifier         frontend.Variable `gnark:",public"`
	MaxChoices        frontend.Variable `gnark:",public"`

	PkCreator tedwards.Point                                `gnark:",public"`
	CtA       [VoteCastHomomorphicChoices]tedwards.Point    `gnark:",public"`
	CtB       [VoteCastHomomorphicChoices]tedwards.Point    `gnark:",public"`

	// --- private witness ---
	VoterSecret frontend.Variable
	VoterWeight frontend.Variable

	V [VoteCastHomomorphicChoices]frontend.Variable
	R [VoteCastHomomorphicChoices]frontend.Variable

	PathElements [homomorphicMerkleDepth]frontend.Variable
	PathIndices  [homomorphicMerkleDepth]frontend.Variable
}

func (c *VoteCastHomomorphicCircuit_8) Define(api frontend.API) error {
	curve, err := tedwards.NewEdCurve(api, twistededwards.BN254)
	if err != nil {
		return err
	}

	// Curve-canonical base point G (BabyJubJub).
	params := curve.Params()
	G := tedwards.Point{X: params.Base[0], Y: params.Base[1]}

	// 1. one-hot: each V[j] ∈ {0,1}, Σ V[j] = 1.
	sumV := frontend.Variable(0)
	for j := 0; j < VoteCastHomomorphicChoices; j++ {
		api.AssertIsBoolean(c.V[j])
		sumV = api.Add(sumV, c.V[j])
	}
	api.AssertIsEqual(sumV, 1)

	// 2. Range bound: V[j] = 0 for j ≥ MaxChoices.
	//    Encoded as ToBinary(V[j] · (MaxChoices − (j+1)), 4): when V[j]=0
	//    the probe is 0; when V[j]=1 the probe must be a 4-bit non-
	//    negative field element, which forces MaxChoices ≥ j+1.
	//    MaxChoices is independently constrained to 4 bits (K=8 ≤ 15).
	maxBits := api.ToBinary(c.MaxChoices, 4)
	api.AssertIsEqual(c.MaxChoices, api.FromBinary(maxBits...))
	for j := 0; j < VoteCastHomomorphicChoices; j++ {
		probe := api.Mul(c.V[j], api.Sub(c.MaxChoices, j+1))
		api.ToBinary(probe, 4)
	}

	// 3. ElGamal binding for each bin.
	//    CtA[j] == G · R[j]
	//    CtB[j] == G · V[j] + PkCreator · R[j]
	for j := 0; j < VoteCastHomomorphicChoices; j++ {
		expectedA := curve.ScalarMul(G, c.R[j])
		api.AssertIsEqual(expectedA.X, c.CtA[j].X)
		api.AssertIsEqual(expectedA.Y, c.CtA[j].Y)

		gV := curve.ScalarMul(G, c.V[j])
		pkR := curve.ScalarMul(c.PkCreator, c.R[j])
		expectedB := curve.Add(gV, pkR)
		api.AssertIsEqual(expectedB.X, c.CtB[j].X)
		api.AssertIsEqual(expectedB.Y, c.CtB[j].Y)
	}

	// 4. Merkle membership: leaf = mimc(secret, weight); fold along path.
	leaf := synthMimcHash(api, c.VoterSecret, c.VoterWeight)
	current := leaf
	for i := 0; i < homomorphicMerkleDepth; i++ {
		api.AssertIsBoolean(c.PathIndices[i])
		left := api.Select(c.PathIndices[i], c.PathElements[i], current)
		right := api.Select(c.PathIndices[i], current, c.PathElements[i])
		current = synthMimcHash(api, left, right)
	}
	api.AssertIsEqual(current, c.VoterRegistryRoot)

	// 5. Nullifier binding.
	expectedNullifier := synthMimcHash(api, c.VoterSecret, c.PollID)
	api.AssertIsEqual(c.Nullifier, expectedNullifier)

	return nil
}
