package dsp

import (
	"errors"
	"fmt"
	"math"
)

// This file holds ArrayNode ops: those that operate natively on waveform
// (float64 array) Samples — reductions (array→scalar), producers (array→array),
// and element access. Each also implements the legacy scalar Node interface
// (treating a scalar as a single-element array) so it remains usable from the
// scalar eval path.

// reductionProcess adapts a scalar Process call to a reduction ArrayNode.
func reductionProcess(n ArrayNode, in []float64, st map[string]any) (float64, error) {
	s, err := n.ProcessSample(scalarInputs(in), st)
	if err != nil {
		return 0, err
	}
	return s.F, nil
}

// ── IndexNode ───────────────────────────────────────────────────────────────

// IndexNode extracts element I of an array input (array→scalar).
type IndexNode struct{ I int }

func (n *IndexNode) Type() string { return "index" }
func (n *IndexNode) Process(in []float64, st map[string]any) (float64, error) {
	return reductionProcess(n, in, st)
}
func (n *IndexNode) ProcessSample(in []Sample, _ map[string]any) (Sample, error) {
	if len(in) == 0 {
		return Sample{}, errors.New("index: no inputs")
	}
	arr := in[0].AsArray()
	if n.I < 0 || n.I >= len(arr) {
		return Sample{}, fmt.Errorf("index: %d out of range [0,%d)", n.I, len(arr))
	}
	return Scalar(arr[n.I]), nil
}

// ── SliceNode ───────────────────────────────────────────────────────────────

// SliceNode returns a sub-range [Start,End) of an array input (array→array),
// clamped to the array bounds. End <= 0 means "to the end".
type SliceNode struct{ Start, End int }

func (n *SliceNode) Type() string { return "slice" }
func (n *SliceNode) Process(in []float64, st map[string]any) (float64, error) {
	s, err := n.ProcessSample(scalarInputs(in), st)
	if err != nil {
		return 0, err
	}
	if len(s.Arr) == 0 {
		return 0, nil
	}
	return s.Arr[0], nil
}
func (n *SliceNode) ProcessSample(in []Sample, _ map[string]any) (Sample, error) {
	if len(in) == 0 {
		return Sample{}, errors.New("slice: no inputs")
	}
	arr := in[0].AsArray()
	start := n.Start
	if start < 0 {
		start = 0
	}
	if start > len(arr) {
		start = len(arr)
	}
	end := n.End
	if end <= 0 || end > len(arr) {
		end = len(arr)
	}
	if end < start {
		end = start
	}
	out := make([]float64, end-start)
	copy(out, arr[start:end])
	return Array(out), nil
}

// ── reductions ────────────────────────────────────────────────────────────────

// SumNode sums an array input (array→scalar).
type SumNode struct{}

func (n *SumNode) Type() string { return "sum" }
func (n *SumNode) Process(in []float64, st map[string]any) (float64, error) {
	return reductionProcess(n, in, st)
}
func (n *SumNode) ProcessSample(in []Sample, _ map[string]any) (Sample, error) {
	if len(in) == 0 {
		return Sample{}, errors.New("sum: no inputs")
	}
	var s float64
	for _, v := range in[0].AsArray() {
		s += v
	}
	return Scalar(s), nil
}

// MeanNode averages an array input (array→scalar).
type MeanNode struct{}

func (n *MeanNode) Type() string { return "mean" }
func (n *MeanNode) Process(in []float64, st map[string]any) (float64, error) {
	return reductionProcess(n, in, st)
}
func (n *MeanNode) ProcessSample(in []Sample, _ map[string]any) (Sample, error) {
	if len(in) == 0 {
		return Sample{}, errors.New("mean: no inputs")
	}
	arr := in[0].AsArray()
	if len(arr) == 0 {
		return Scalar(0), nil
	}
	var s float64
	for _, v := range arr {
		s += v
	}
	return Scalar(s / float64(len(arr))), nil
}

// MinNode returns the minimum element of an array input (array→scalar).
type MinNode struct{}

func (n *MinNode) Type() string { return "min" }
func (n *MinNode) Process(in []float64, st map[string]any) (float64, error) {
	return reductionProcess(n, in, st)
}
func (n *MinNode) ProcessSample(in []Sample, _ map[string]any) (Sample, error) {
	if len(in) == 0 {
		return Sample{}, errors.New("min: no inputs")
	}
	arr := in[0].AsArray()
	if len(arr) == 0 {
		return Scalar(0), nil
	}
	m := arr[0]
	for _, v := range arr[1:] {
		if v < m {
			m = v
		}
	}
	return Scalar(m), nil
}

// MaxNode returns the maximum element of an array input (array→scalar).
type MaxNode struct{}

func (n *MaxNode) Type() string { return "max" }
func (n *MaxNode) Process(in []float64, st map[string]any) (float64, error) {
	return reductionProcess(n, in, st)
}
func (n *MaxNode) ProcessSample(in []Sample, _ map[string]any) (Sample, error) {
	if len(in) == 0 {
		return Sample{}, errors.New("max: no inputs")
	}
	arr := in[0].AsArray()
	if len(arr) == 0 {
		return Scalar(0), nil
	}
	m := arr[0]
	for _, v := range arr[1:] {
		if v > m {
			m = v
		}
	}
	return Scalar(m), nil
}

// LengthNode returns the element count of an array input (array→scalar).
type LengthNode struct{}

func (n *LengthNode) Type() string { return "length" }
func (n *LengthNode) Process(in []float64, st map[string]any) (float64, error) {
	return reductionProcess(n, in, st)
}
func (n *LengthNode) ProcessSample(in []Sample, _ map[string]any) (Sample, error) {
	if len(in) == 0 {
		return Sample{}, errors.New("length: no inputs")
	}
	return Scalar(float64(len(in[0].AsArray()))), nil
}

// ── FFTNode ────────────────────────────────────────────────────────────────

// FFTNode computes the magnitude spectrum of an array input (array→array). The
// input is zero-padded to the next power of two; the output has that length and
// holds |X[k]| for each frequency bin.
type FFTNode struct{}

func (n *FFTNode) Type() string { return "fft" }
func (n *FFTNode) Process(in []float64, st map[string]any) (float64, error) {
	s, err := n.ProcessSample(scalarInputs(in), st)
	if err != nil {
		return 0, err
	}
	if len(s.Arr) == 0 {
		return 0, nil
	}
	return s.Arr[0], nil
}
func (n *FFTNode) ProcessSample(in []Sample, _ map[string]any) (Sample, error) {
	if len(in) == 0 {
		return Sample{}, errors.New("fft: no inputs")
	}
	return Array(fftMagnitude(in[0].AsArray())), nil
}

// fftMagnitude returns the magnitude spectrum of x, zero-padded to the next
// power of two. Returns an empty slice for empty input.
func fftMagnitude(x []float64) []float64 {
	if len(x) == 0 {
		return nil
	}
	n := nextPow2(len(x))
	re := make([]float64, n)
	im := make([]float64, n)
	copy(re, x)
	fftRadix2(re, im)
	mag := make([]float64, n)
	for i := range mag {
		mag[i] = math.Hypot(re[i], im[i])
	}
	return mag
}
