defmodule Growth.Score.WeightForHeight do
  @moduledoc """
  Calculate z-score for weight for height.

  This module calculates the z-score for weight relative to height, which is an important
  indicator for assessing whether a child's weight is appropriate for their height,
  regardless of age.

  **Limitation**: the memasurements do not differentiate between length and height, and
  always assume height.
  """

  @behaviour Growth.Score.Scorer

  alias Growth.Score.Scorer

  @impl Scorer
  @spec measure_name() :: atom()
  @doc """
  Name of the measurement used in weight for height indicator.
  """
  def measure_name do
    :weight_for_height
  end

  @doc """
  Custom implementation for weight-for-height lookup, as it requires both weight and height.

  This overrides the default implementation in the Scorer module.
  """
  @spec lms(Growth.t(), module()) :: [{String.t(), {number(), number(), number()}}]
  def lms(%Growth{gender: gender, weight: weight, height: height}, _indicator)
      when is_number(weight) and is_number(height) do
    # For weight-for-height, we use height as the lookup value instead of age
    key = {{gender, :height, height}, :_}

    case :ets.match_object(__MODULE__, key) do
      [{{^gender, _, ^height}, %{l: l, m: m, s: s}} | _] ->
        [{"height", {l, m, s}}]

      _ ->
        # Try to find the closest height value
        find_closest_height(gender, height)
    end
  end

  def lms(_growth, _indicator) do
    []
  end

  @doc """
  Find the closest height value in the ETS table when an exact match isn't found.
  """
  @spec find_closest_height(Growth.gender(), number()) :: [
          {String.t(), {number(), number(), number()}}
        ]
  def find_closest_height(gender, height) do
    # Fetch all entries for the given gender and height measurement
    matcher = [
      {
        {{:"$1", :"$2", :"$3"}, :_},
        [
          {:andalso,
           {:andalso, {:andalso, {:==, :"$1", gender}, {:==, :"$2", :height}},
            {:>, :"$3", height - 1.0}}, {:<, :"$3", height + 1.0}}
        ],
        [:"$_"]
      }
    ]

    gender_height_entries = :ets.select(__MODULE__, matcher)

    case gender_height_entries do
      [] ->
        # No entries found for this gender
        []

      entries ->
        # Find the entry with the height closest to the target height
        closest_entry =
          entries
          |> Enum.filter(fn {{_, _, entry_height}, _lms_data} ->
            abs(entry_height - height) <= 0.5
          end)
          |> Enum.min_by(fn {{_, _, entry_height}, _lms_data} ->
            abs(entry_height - height)
          end)

        # Extract the LMS data from the closest entry
        {_, %{l: l, m: m, s: s}} = closest_entry

        [{"height", {l, m, s}}]
    end
  end
end
