# frozen_string_literal: true

module Visits
  # Merges consecutive visits that are likely part of the same stay
  class Merger
    MAXIMUM_VISIT_GAP = 30.minutes
    SIGNIFICANT_MOVEMENT_THRESHOLD = 50 # meters

    attr_reader :points

    def initialize(points)
      @points = points
    end

    def merge_visits(visits)
      return visits if visits.empty?

      merged = []
      current_merged = visits.first

      visits[1..].each do |visit|
        if can_merge_visits?(current_merged, visit)
          # Merge the visits
          current_merged[:end_time] = visit[:end_time]
          current_merged[:points].concat(visit[:points])
        else
          merged << current_merged
          current_merged = visit
        end
      end

      merged << current_merged
      merged
    end

    private

    def can_merge_visits?(first_visit, second_visit)
      return false unless same_location?(first_visit, second_visit)
      return false if gap_too_large?(first_visit, second_visit)
      return false if significant_movement_between?(first_visit, second_visit)

      true
    end

    def same_location?(first_visit, second_visit)
      distance = Geocoder::Calculations.distance_between(
        [first_visit[:center_lat], first_visit[:center_lon]],
        [second_visit[:center_lat], second_visit[:center_lon]],
        units: :km
      )

      # Convert to meters and check if within threshold
      (distance * 1000) <= SIGNIFICANT_MOVEMENT_THRESHOLD
    end

    def gap_too_large?(first_visit, second_visit)
      gap = second_visit[:start_time] - first_visit[:end_time]
      gap > MAXIMUM_VISIT_GAP
    end

    def significant_movement_between?(first_visit, second_visit)
      # Get points between the two visits
      between_points = points.where(
        timestamp: (first_visit[:end_time] + 1)..(second_visit[:start_time] - 1)
      )

      return false if between_points.empty?

      visit_center = [first_visit[:center_lat], first_visit[:center_lon]]
      max_distance = between_points.map do |point|
        Geocoder::Calculations.distance_between(
          visit_center,
          [point.lat, point.lon],
          units: :km
        )
      end.max

      # Convert to meters and check if exceeds threshold
      (max_distance * 1000) > SIGNIFICANT_MOVEMENT_THRESHOLD
    end
  end
end
