# typed: strict
# frozen_string_literal: true

module Stats
  # Base class for data normalization and denormalization
  class DataNormalizer
    extend T::Sig
    extend T::Helpers
    abstract!

    sig(:final) { params(data_points: T::Array[Stats::DataPoint]).void }
    def initialize(data_points)
      data_points.sort_by! { |point| point.x }
      @x_values = T.let(data_points.map { |p| p.x.to_f }, T::Array[Float])
      @y_values = T.let(data_points.map { |p| p.y.to_f }, T::Array[Float])

      # Calculate min/max for normalization
      x_minmax = T.cast(@x_values.minmax, [Float, Float])
      y_minmax = T.cast(@y_values.minmax, [Float, Float])
      @x =
        T.let(axis_class.new(min: x_minmax[0], max: x_minmax[1]), Stats::Axis)
      @y =
        T.let(axis_class.new(min: y_minmax[0], max: y_minmax[1]), Stats::Axis)
    end

    sig(:final) { returns(T::Array[Float]) }
    attr_reader :x_values

    sig(:final) { returns(T::Array[Float]) }
    attr_reader :y_values

    sig(:final) { returns(Stats::Axis) }
    attr_reader :x

    sig(:final) { returns(Stats::Axis) }
    attr_reader :y

    sig(:final) { returns(String) }
    def x_range
      @x.as_string
    end

    sig(:final) { returns(String) }
    def y_range
      @y.as_string { |x| Time.at(x) }
    end

    # Default transformation matrix (identity for linear/quadratic)
    sig { returns(T::Array[T::Array[Float]]) }
    def normalized_x_matrix
      @x_values.map { |x| [@x.normalize(x)] }
    end

    sig(:final) { returns(T::Array[Float]) }
    def normalized_y_vector
      @y_values.map { |y| @y.normalize(y) }
    end

    # Generate regression line points in original scale
    sig(:final) { returns(T::Array[Float]) }
    def regression_x_range
      step_size = @x.scale / 50.0
      @x.range.step(step_size).to_a
    end

    # Abstract method for denormalizing regression results
    sig do
      abstract
        .params(regression_x: T::Array[Float], weight_vec: T::Array[Float])
        .returns(T::Array[Float])
    end
    def denormalize_regression(regression_x, weight_vec)
    end

    protected

    sig { abstract.returns(T.class_of(Stats::Axis)) }
    def axis_class
    end
  end
end
