package logbook

import (
    "errors"
    "sort"
    "time"

    "github.com/aclements/go-moremath/fit"
)

// Predictable expresses that values can be predicted at arbitrary times.
type Predictable interface {
    // Predict predicts the value of a variable at the given date (using LOESS).
    // Returns the predicted value and error.
    Predict(variable string, date time.Time, span float64) (float64, error)
}

// Ensure MeasurementRepository implements Predictable
var _ Predictable = (*MeasurementRepository)(nil)


// Predict uses LOESS to predict the value of a variable at a given date.
// span is the smoothing parameter for LOESS (typical values: 0.3-0.8).
func (r *MeasurementRepository) Predict(variable string, date time.Time, span float64) (float64, error) {
    // 1. Get all measurements for the variable
    measurements, err := r.GetByVariable(variable)
    if err != nil {
        return 0, err
    }
    if len(measurements) == 0 {
        return 0, errors.New("no measurements found for variable")
    }

    // 2. Sort by date
    sort.Slice(measurements, func(i, j int) bool {
        return measurements[i].Date.Before(measurements[j].Date)
    })

    // 3. Prepare data for LOESS: x = seconds since epoch, y = value
    xs := make([]float64, len(measurements))
    ys := make([]float64, len(measurements))
    for i, m := range measurements {
        xs[i] = float64(m.Date.Unix())
        ys[i] = m.Value
    }

    // 4. Fit LOESS model (degree 2 is typical, span 0.3-0.8)
    // LOESS returns a function f(x float64) float64
    f := fit.LOESS(xs, ys, 2, span)
    if f == nil {
        return 0, errors.New("LOESS fitting failed")
    }

    // 5. Predict for the requested date
    predX := float64(date.Unix())
    predY := f(predX)
    return predY, nil
}

// GetByVariable returns all measurements for a variable.
func (r *MeasurementRepository) GetByVariable(variable string) ([]*Measurement, error) {
    all, err := r.FindAll()
    if err != nil {
        return nil, err
    }
    var filtered []*Measurement
    for _, m := range all {
        if m.Variable == variable {
            filtered = append(filtered, m)
        }
    }
    return filtered, nil
}