package datascience

// Pivot transforma datos del formato largo al formato ancho (pivot table).
// Agrupa por index, expande los valores unicos de columns como nuevas columnas
// y agrega values con la funcion indicada.
// Funciones de agregacion soportadas: sum, count, mean, min, max, first, last.
// Valores numericos faltantes se rellenan con 0.
func Pivot(rows []map[string]any, index, columns, values, agg string) []map[string]any {
	// Mantener orden de aparicion de index y column values
	indexOrder := []any{}
	seenIndex := map[any]bool{}
	colOrder := []any{}
	seenCols := map[any]bool{}

	for _, row := range rows {
		idx := row[index]
		col := row[columns]
		if !seenIndex[idx] {
			seenIndex[idx] = true
			indexOrder = append(indexOrder, idx)
		}
		if !seenCols[col] {
			seenCols[col] = true
			colOrder = append(colOrder, col)
		}
	}

	// Acumular: groups[indexVal][colVal] = lista de valores
	type key struct{ idx, col any }
	groups := map[key][]any{}
	for _, row := range rows {
		idx := row[index]
		col := row[columns]
		val := row[values]
		if val != nil {
			k := key{idx, col}
			groups[k] = append(groups[k], val)
		}
	}

	aggregate := func(vals []any, fn string) any {
		if len(vals) == 0 {
			return 0
		}
		switch fn {
		case "count":
			return len(vals)
		case "first":
			return vals[0]
		case "last":
			return vals[len(vals)-1]
		}
		// Funciones numericas: sum, mean, min, max
		toFloat := func(v any) float64 {
			switch n := v.(type) {
			case float64:
				return n
			case float32:
				return float64(n)
			case int:
				return float64(n)
			case int64:
				return float64(n)
			case int32:
				return float64(n)
			}
			return 0
		}
		sum := 0.0
		mn := toFloat(vals[0])
		mx := toFloat(vals[0])
		for _, v := range vals {
			f := toFloat(v)
			sum += f
			if f < mn {
				mn = f
			}
			if f > mx {
				mx = f
			}
		}
		switch fn {
		case "sum":
			return sum
		case "mean":
			return sum / float64(len(vals))
		case "min":
			return mn
		case "max":
			return mx
		}
		return sum
	}

	result := make([]map[string]any, 0, len(indexOrder))
	for _, idx := range indexOrder {
		record := map[string]any{index: idx}
		for _, col := range colOrder {
			k := key{idx, col}
			vals := groups[k]
			if len(vals) > 0 {
				record[col.(string)] = aggregate(vals, agg)
			} else {
				record[col.(string)] = 0
			}
		}
		result = append(result, record)
	}
	return result
}
