package adapter

import (
	"fmt"
	"math"
	"math/rand"
	"time"
)

type randomAdapter struct {
	sourceDim int
	targetDim int
	matrix    [][]float32
}

func newRandomAdapter(sourceDim, targetDim int, seed int64) *randomAdapter {
	if seed == 0 {
		seed = time.Now().UnixNano()
	}
	//nolint:gosec // deterministic seeded RNG for projection matrix generation, not security use
	rng := rand.New(rand.NewSource(seed))

	// Gaussian N(0, 1/targetDim) — preserves expected squared norms (Johnson-Lindenstrauss)
	stddev := 1.0 / math.Sqrt(float64(targetDim))
	matrix := make([][]float32, targetDim)
	for i := range matrix {
		row := make([]float32, sourceDim)
		for j := range row {
			row[j] = float32(rng.NormFloat64() * stddev)
		}
		matrix[i] = row
	}

	return &randomAdapter{sourceDim: sourceDim, targetDim: targetDim, matrix: matrix}
}

func (a *randomAdapter) SourceDim() int { return a.sourceDim }
func (a *randomAdapter) TargetDim() int { return a.targetDim }

func (a *randomAdapter) Adapt(vec []float32) ([]float32, error) {
	if len(vec) != a.sourceDim {
		return nil, fmt.Errorf("random adapt: %w: got %d, want %d", ErrDimMismatch, len(vec), a.sourceDim)
	}
	return L2Norm(matVecMul(a.matrix, vec)), nil
}
