# frozen_string_literal: true

require "calagator/vcalendar"

# == Source::Parser::Ical
#
# Reads iCalendar events.
#
# Example:
#   events = Source::Parser::Ical.to_events('http://appendix.23ae.com/calendars/AlternateHolidays.ics')
#
# Sample sources:
#   webcal://appendix.23ae.com/calendars/AlternateHolidays.ics
#   http://appendix.23ae.com/calendars/AlternateHolidays.ics
module Calagator
  class Source::Parser::Ical < Source::Parser
    self.label = :iCalendar

    # Override Source::Parser.read_url to handle "webcal" scheme addresses.
    def self.read_url(url)
      url = url.gsub(/^webcal:/, "http:")
      super
    end

    def to_events
      return false unless vcalendars

      current_vevents = vcalendars.flat_map(&:vevents).reject(&:old?)
      current_events = current_vevents.map { |vevent| to_event(vevent) }
      dedup(current_events)
    end

    private

    def vcalendars
      @vcalendars ||= VCalendar.parse(raw_ical)
    end

    def raw_ical
      self.class.read_url(url)
    end

    def to_event(vevent)
      event = EventMapper.new(vevent).to_event
      event.venue = VenueMapper.new(vevent.vvenue, vevent.location).to_venue
      event.source = source
      event
    end

    def dedup(events)
      events.map do |event|
        event = event_or_duplicate(event)
        event.venue = venue_or_duplicate(event.venue) if event.venue
        event
      end.uniq
    end

    # Converts a VEvent instance into an Event
    class EventMapper < Struct.new(:vevent)
      def to_event
        Event.new(
          title: vevent.summary,
          description: vevent.description,
          url: vevent.url,
          start_time: vevent.start_time,
          end_time: vevent.end_time
        )
      end
    end

    # Converts a VVenue instance into a Venue
    class VenueMapper < Struct.new(:vvenue, :fallback)
      def to_venue
        from_vvenue || from_fallback || return
      end

      private

      def from_vvenue
        return unless vvenue

        Venue.new(
          title: vvenue.name,
          street_address: vvenue.address,
          locality: vvenue.city,
          region: vvenue.region,
          postal_code: vvenue.postalcode,
          country: vvenue.country,
          latitude: vvenue.latitude,
          longitude: vvenue.longitude, &:geocode!
        )
      end

      def from_fallback
        return if fallback.blank?

        Venue.new(title: fallback)
      end
    end
  end
end
