package dsp

import "fmt"

// Op type categories for static (compile-time / editor) type propagation.
// These mirror the runtime dispatch in the synthetic graph evaluator and the
// frontend's inferNodeTypes (web/src/lib/synthTypes.ts) — keep the three in
// sync; a parity test guards the Go/TS pair.
var (
	// reductionOps collapse an array (or scalar) to a single scalar.
	reductionOps = map[string]bool{
		"index": true, "length": true, "sum": true,
		"mean": true, "min": true, "max": true,
	}
	// arrayProducerOps require an array input and yield an array.
	arrayProducerOps = map[string]bool{
		"fft": true, "slice": true,
	}
	// scalarOnlyOps reject array inputs and yield a scalar. Stateful filters
	// plus lua (whose state/closure cannot be broadcast per array lane).
	scalarOnlyOps = map[string]bool{
		"moving_average": true, "rms": true, "lowpass": true,
		"derivative": true, "integrate": true, "lua": true,
	}
	// statefulOps accumulate state across evaluations, so a single-shot trace
	// (fresh state) only approximates their true running value. lua is included
	// because a script may persist state in its state table.
	statefulOps = map[string]bool{
		"moving_average": true, "rms": true, "lowpass": true,
		"derivative": true, "integrate": true, "lua": true,
	}
)

// IsStateful reports whether an op accumulates state across evaluations. The
// editor's live/debug trace flags such nodes as "approx" since it evaluates a
// single tick with fresh state.
func IsStateful(op string) bool { return statefulOps[op] }

// OpOutputType reports the output ValType of an op given its input types, and
// an error if the inputs are definitely incompatible with the op. Inputs may be
// ValUnknown (a source whose real type is not yet known at compile time); such
// inputs never trigger an error — runtime Sample typing is authoritative.
func OpOutputType(op string, in []ValType) (ValType, error) {
	switch {
	case reductionOps[op]:
		return ValScalar, nil

	case arrayProducerOps[op]:
		for _, t := range in {
			if t == ValScalar {
				return ValUnknown, fmt.Errorf("%s requires an array input", op)
			}
		}
		return ValArray, nil

	case scalarOnlyOps[op]:
		for _, t := range in {
			if t == ValArray {
				return ValUnknown, fmt.Errorf("%s does not accept an array input", op)
			}
		}
		return ValScalar, nil

	default:
		// Elementwise stateless ops (gain, offset, add, subtract, multiply,
		// divide, clamp, threshold, expr): array if any input is an array,
		// scalar if all inputs are definitely scalar, otherwise unknown.
		anyArray, anyUnknown := false, false
		for _, t := range in {
			switch t {
			case ValArray:
				anyArray = true
			case ValUnknown:
				anyUnknown = true
			}
		}
		switch {
		case anyArray:
			return ValArray, nil
		case anyUnknown:
			return ValUnknown, nil
		default:
			return ValScalar, nil
		}
	}
}
