# frozen_string_literal: true

# Track segmentation logic for splitting GPS points into meaningful track segments.
#
# This module provides the core algorithm for determining where one track ends
# and another begins, based on time gaps and distance jumps between consecutive points.
#
# How it works:
# 1. Analyzes consecutive GPS points to detect gaps that indicate separate journeys
# 2. Uses configurable time and distance thresholds to identify segment boundaries
# 3. Splits large arrays of points into smaller arrays representing individual tracks
# 4. Provides utilities for handling both Point objects and hash representations
#
# Segmentation criteria:
# - Time threshold: Gap longer than X minutes indicates a new track
# - Distance threshold: Jump larger than X meters indicates a new track
# - Minimum segment size: Segments must have at least 2 points to form a track
#
# The module is designed to be included in classes that need segmentation logic
# and requires the including class to implement distance_threshold_meters and
# time_threshold_minutes methods.
#
# Used by:
# - Tracks::ParallelGenerator and related jobs for splitting points during parallel track generation
# - Tracks::BoundaryDetector for cross-chunk track merging
#
# Example usage:
#   class MyTrackProcessor
#     include Tracks::Segmentation
#
#     def distance_threshold_meters; 500; end
#     def time_threshold_minutes; 60; end
#
#     def process_points(points)
#       segments = split_points_into_segments(points)
#       # Process each segment...
#     end
#   end
#
module Tracks::Segmentation
  extend ActiveSupport::Concern

  private

  def split_points_into_segments(points)
    return [] if points.empty?

    segments = []
    current_segment = []

    points.each do |point|
      if should_start_new_segment?(point, current_segment.last)
        # Finalize current segment if it has enough points
        segments << current_segment if current_segment.size >= 2
        current_segment = [point]
      else
        current_segment << point
      end
    end

    # Don't forget the last segment
    segments << current_segment if current_segment.size >= 2

    segments
  end

  # Alternative segmentation using Geocoder (no SQL dependency)
  def split_points_into_segments_geocoder(points)
    return [] if points.empty?

    segments = []
    current_segment = []

    points.each do |point|
      if should_start_new_segment_geocoder?(point, current_segment.last)
        # Finalize current segment if it has enough points
        segments << current_segment if current_segment.size >= 2
        current_segment = [point]
      else
        current_segment << point
      end
    end

    # Don't forget the last segment
    segments << current_segment if current_segment.size >= 2

    segments
  end

  def should_start_new_segment?(current_point, previous_point)
    return false if previous_point.nil?

    # Check time threshold (convert minutes to seconds)
    current_timestamp = current_point.timestamp
    previous_timestamp = previous_point.timestamp

    time_diff_seconds = current_timestamp - previous_timestamp
    time_threshold_seconds = time_threshold_minutes.to_i * 60

    return true if time_diff_seconds > time_threshold_seconds

    # Check distance threshold - convert km to meters to match frontend logic
    distance_km = calculate_km_distance_between_points(previous_point, current_point)
    distance_meters = distance_km * 1000 # Convert km to meters

    return true if distance_meters > distance_threshold_meters

    false
  end

  # Alternative segmentation logic using Geocoder (no SQL dependency)
  def should_start_new_segment_geocoder?(current_point, previous_point)
    return false if previous_point.nil?

    # Check time threshold (convert minutes to seconds)
    current_timestamp = current_point.timestamp
    previous_timestamp = previous_point.timestamp

    time_diff_seconds = current_timestamp - previous_timestamp
    time_threshold_seconds = time_threshold_minutes.to_i * 60

    return true if time_diff_seconds > time_threshold_seconds

    # Check distance threshold using Geocoder
    distance_km = calculate_km_distance_between_points_geocoder(previous_point, current_point)
    distance_meters = distance_km * 1000 # Convert km to meters

    return true if distance_meters > distance_threshold_meters

    false
  end

  def calculate_km_distance_between_points(point1, point2)
    distance_meters = Point.connection.select_value(
      'SELECT ST_Distance(ST_GeomFromEWKT($1)::geography, ST_GeomFromEWKT($2)::geography)',
      nil,
      [point1.lonlat, point2.lonlat]
    )

    distance_meters.to_f / 1000.0 # Convert meters to kilometers
  end

  # In-memory distance calculation using Geocoder (no SQL dependency)
  def calculate_km_distance_between_points_geocoder(point1, point2)
    begin
      distance = point1.distance_to_geocoder(point2, :km)

      # Validate result
      if !distance.finite? || distance < 0
        return 0
      end

      distance
    rescue StandardError => e
      0
    end
  end

  def should_finalize_segment?(segment_points, grace_period_minutes = 5)
    return false if segment_points.size < 2

    last_point = segment_points.last
    last_timestamp = last_point.timestamp
    current_time = Time.current.to_i

    # Don't finalize if the last point is too recent (within grace period)
    time_since_last_point = current_time - last_timestamp
    grace_period_seconds = grace_period_minutes * 60

    time_since_last_point > grace_period_seconds
  end

  def point_coordinates(point)
    [point.lat, point.lon]
  end

  def distance_threshold_meters
    raise NotImplementedError, "Including class must implement distance_threshold_meters"
  end

  def time_threshold_minutes
    raise NotImplementedError, "Including class must implement time_threshold_minutes"
  end
end
