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
}

// BuildPipeline converts a []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 BuildPipeline(defs []NodeDef) ([]dsp.Node, error) {
	nodes := make([]dsp.Node, 0, len(defs))
	for i, d := range defs {
		n, err := buildNode(d)
		if err != nil {
			return nil, fmt.Errorf("pipeline node %d (%q): %w", i, d.Type, err)
		}
		nodes = append(nodes, n)
	}
	return nodes, nil
}

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 "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")}, nil

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

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