package synthetic

import (
	"fmt"

	"github.com/uopi/uopi/internal/dsp"
)

// floatParam extracts a float64 from params; returns 0 if missing or wrong type.
func floatParam(params map[string]any, key string) float64 {
	if params == nil {
		return 0
	}
	v, ok := params[key]
	if !ok {
		return 0
	}
	f, ok := v.(float64)
	if !ok {
		return 0
	}
	return f
}

// stringParam extracts a string from params; returns "" if missing or wrong type.
func stringParam(params map[string]any, key string) string {
	if params == nil {
		return ""
	}
	v, ok := params[key]
	if !ok {
		return ""
	}
	s, ok := v.(string)
	if !ok {
		return ""
	}
	return s
}

// stringSliceParam extracts a []string from params; JSON arrays decode to []any,
// so each element is coerced via its string value. Returns nil if missing.
func stringSliceParam(params map[string]any, key string) []string {
	if params == nil {
		return nil
	}
	v, ok := params[key]
	if !ok {
		return nil
	}
	arr, ok := v.([]any)
	if !ok {
		return nil
	}
	out := make([]string, 0, len(arr))
	for _, e := range arr {
		if s, ok := e.(string); ok && s != "" {
			out = append(out, s)
		}
	}
	return out
}

// buildNode converts a single NodeDef (from JSON) into a dsp.Node ready for
// execution. JSON numbers are float64, so all numeric params are handled as
// float64 regardless of the final type needed.
func buildNode(d NodeDef) (dsp.Node, error) {
	p := d.Params
	switch d.Type {
	case "gain":
		return &dsp.GainNode{Gain: floatParam(p, "gain")}, nil

	case "offset":
		return &dsp.OffsetNode{Offset: floatParam(p, "offset")}, nil

	case "add":
		return &dsp.AddNode{}, nil

	case "subtract":
		return &dsp.SubtractNode{}, nil

	case "multiply":
		return &dsp.MultiplyNode{}, nil

	case "divide":
		return &dsp.DivideNode{}, nil

	case "moving_average":
		return &dsp.MovingAverageNode{Window: max(1, int(floatParam(p, "window")))}, nil

	case "rms":
		return &dsp.RMSNode{Window: max(1, int(floatParam(p, "window")))}, nil

	case "derivative":
		return &dsp.DerivativeNode{}, nil

	case "integrate":
		return &dsp.IntegrateNode{}, nil

	case "clamp":
		return &dsp.ClampNode{
			Min: floatParam(p, "min"),
			Max: floatParam(p, "max"),
		}, nil

	case "threshold":
		return &dsp.ThresholdNode{
			Threshold: floatParam(p, "threshold"),
			High:      floatParam(p, "high"),
			Low:       floatParam(p, "low"),
		}, nil

	case "expr":
		return &dsp.ExprNode{Expr: stringParam(p, "expr"), Vars: stringSliceParam(p, "vars")}, nil

	case "lowpass":
		order := int(floatParam(p, "order"))
		if order < 1 {
			order = 1
		}
		return &dsp.LowPassNode{
			Freq:  floatParam(p, "freq"),
			Order: order,
		}, nil

	case "lua":
		return &dsp.LuaNode{Script: stringParam(p, "script"), Vars: stringSliceParam(p, "vars")}, nil

	case "index":
		return &dsp.IndexNode{I: int(floatParam(p, "i"))}, nil

	case "slice":
		return &dsp.SliceNode{Start: int(floatParam(p, "start")), End: int(floatParam(p, "end"))}, nil

	case "sum":
		return &dsp.SumNode{}, nil

	case "mean":
		return &dsp.MeanNode{}, nil

	case "min":
		return &dsp.MinNode{}, nil

	case "max":
		return &dsp.MaxNode{}, nil

	case "length":
		return &dsp.LengthNode{}, nil

	case "fft":
		return &dsp.FFTNode{}, nil

	default:
		return nil, fmt.Errorf("unknown node type %q", d.Type)
	}
}
