require "json"
require "./data_files"

module JosieHealth::LUT
  module AlcoholLUT
    ETHANOL_DENSITY = 0.789 # g/ml

    # Serving sizes in ml
    SERVING_SIZES = {
      "shot"    => 50,
      "shots"   => 50,
      "bottle"  => 330,
      "bottles" => 330,
      "pint"    => 568,
      "pints"   => 568,
      "glass"   => 150,
      "glasses" => 150,
      "can"     => 330,
      "cans"    => 330,
      "unit"    => 1,
      "units"   => 1,
    }

    # Category defaults
    CATEGORY_DEFAULTS = {
      "beer"     => {abv: 5.0, default_ml: 330},
      "cider"    => {abv: 5.0, default_ml: 440},
      "wine"     => {abv: 12.5, default_ml: 150},
      "spirits"  => {abv: 40.0, default_ml: 50},
      "liqueur"  => {abv: 25.0, default_ml: 50},
      "cocktail" => {abv: 12.0, default_ml: 200},
    }

    @@brands : Hash(String, NamedTuple(abv: Float64, category: String, style: String, default_ml: Int32))?
    @@loaded = false

    # Load alcohol data from embedded JSON
    def self.load_data
      return if @@loaded

      begin
        json = DataFiles.alcohol

        @@brands = {} of String => NamedTuple(abv: Float64, category: String, style: String, default_ml: Int32)

        if brands_json = json["brands"]?
          brands_json.as_h.each do |name, data|
            @@brands.not_nil![name.to_s] = {
              abv:        data["abv"].as_f,
              category:   data["category"].as_s,
              style:      data["style"].as_s,
              default_ml: data["default_ml"].as_i,
            }
          end
        end

        puts "Loaded #{@@brands.not_nil!.size} alcohol brands"
        @@loaded = true
      rescue ex
        puts "Error loading alcohol.json: #{ex.message}"
        @@brands = {} of String => NamedTuple(abv: Float64, category: String, style: String, default_ml: Int32)
        @@loaded = true
      end
    end

    # Lookup a brand by name
    def self.lookup_brand(name : String) : NamedTuple(abv: Float64, category: String, style: String, default_ml: Int32)?
      load_data
      normalized = name.downcase.gsub(/[\s\-_]/, "")
      @@brands.not_nil![normalized]?
    end

    # Check if a substance is an alcoholic beverage
    def self.is_alcohol?(name : String) : Bool
      lookup_brand(name) != nil
    end

    # Get category defaults
    def self.get_category_defaults(category : String) : NamedTuple(abv: Float64, default_ml: Int32)?
      CATEGORY_DEFAULTS[category.downcase]?
    end

    # Calculate alcohol grams from volume and ABV
    def self.calculate_grams(volume_ml : Float64, abv_percent : Float64) : Float64
      (volume_ml * (abv_percent / 100.0) * ETHANOL_DENSITY).round(1)
    end

    # Parse dosage string like "2pints", "500ml", "1bottle"
    # Returns {count, serving_type, volume_ml} or nil if not parseable
    def self.parse_dosage(dosage : String) : NamedTuple(count: Int32, serving: String?, volume_ml: Int32)?
      normalized = dosage.downcase.strip

      # Try to match "Nunit" pattern (e.g., "2pints", "1bottle", "500ml")
      if match = normalized.match(/^(\d+(?:\.\d+)?)(ml|bottle|bottles|pint|pints|shot|shots|glass|glasses|can|cans|unit|units)?$/)
        count_str = match[1]
        serving = match[2]?

        # Handle decimal counts
        count_f = count_str.to_f
        count = count_f.to_i

        if serving == "ml"
          # Direct ml specification
          return {count: 1, serving: "ml", volume_ml: count_f.to_i}
        elsif serving && SERVING_SIZES.has_key?(serving)
          volume_ml = SERVING_SIZES[serving] * count
          return {count: count, serving: serving, volume_ml: volume_ml}
        elsif serving.nil?
          # Just a number, assume count of default serving
          return {count: count, serving: nil, volume_ml: 0}
        end
      end

      nil
    end

    # Parse ABV from string like "5%", "5.5%", "40"
    def self.parse_abv(abv_str : String) : Float64?
      normalized = abv_str.downcase.strip.gsub("%", "")
      normalized.to_f?
    end

    # Full alcohol dose calculation
    # Input: dosage string, substance name, optional ABV override
    # Returns: {grams, annotation} or nil
    def self.calculate_dose(dosage : String, substance : String, abv_override : Float64? = nil) : NamedTuple(grams: Float64, annotation: String)?
      parsed = parse_dosage(dosage)
      return nil unless parsed

      brand_info = lookup_brand(substance)

      # Determine ABV
      abv = abv_override
      abv ||= brand_info.try(&.[:abv])

      # If no brand found and no ABV override, can't calculate
      return nil unless abv

      # Determine volume
      volume_ml = if parsed[:volume_ml] > 0
                    parsed[:volume_ml]
                  elsif brand_info
                    parsed[:count] * brand_info[:default_ml]
                  else
                    # Unknown brand, use beer default
                    parsed[:count] * 330
                  end

      grams = calculate_grams(volume_ml.to_f, abv)

      # Build details string
      serving_str = parsed[:serving] ? "#{parsed[:count]}#{parsed[:serving]}" : parsed[:count].to_s
      details = "#{substance} #{serving_str} (#{volume_ml}ml #{abv}%) = #{grams}g"

      {grams: grams, details: details}
    end

    # Get all brand names
    def self.all_brands : Array(String)
      load_data
      @@brands.not_nil!.keys
    end
  end
end
