module CanBePublished
  class StateMachine
    include AASM

    delegate :current_state, to: :aasm

    def initialize(record)
      @record = record

      aasm.current_state = calculated_state
    end

    def calculated_state
      matching_time = Time.zone.now

      state = %i[archived published internal].find { |state_name| calculated_state_valid?(state_name, matching_time) }

      state || :draft
    end

    def calculated_state_valid?(state_name, time)
      date = @record.send "#{state_name}_at"

      date.present? && date < time
    end

    def set_timestamp
      override_unarchived_state if aasm.to_state == :unarchived
      @record.send "#{aasm.to_state}_at=", Time.zone.now.change(sec: 0)
    end

    def override_unarchived_state
      aasm.to_state = @record.published_at.present? ? :published : :internal
    end

    def update_using_current_user(user)
      %i[archived internal published].each { |state_name| update_state_using_current_user(user, state_name) }
    end

    def update_state_using_current_user(user, state_name)
      return if !@record.send("#{state_name}_at_changed?")

      new_value = @record.send("#{state_name}_at").present? ? user : nil
      @record.send("#{state_name}_by=", new_value)
    end

    def clear_archived
      @record.archived_at = nil
    end

    def save_record
      @record.save!
    end

    aasm do
      state :draft,    initial: true
      state :internal
      state :published
      state :archived
      state :unarchived #magic

      event :internal do
        transitions from:  :draft,
                    to:    :internal,
                    guard: :guard_internal?,
                    after: :set_timestamp
      end

      event :publish do
        transitions from:  %i[draft internal],
                    to:    :published,
                    guard: :guard_publish?,
                    after: :set_timestamp
      end

      event :archive do
        transitions from:  %i[published internal],
                    to:    :archived,
                    guard: :guard_archive?,
                    after: :set_timestamp
      end

      event :unarchive do
        transitions from:  :archived,
                    to:    :unarchived,
                    guard: :guard_unarchive?,
                    after: %i[clear_archived set_timestamp]
      end

      after_all_events    %i[update_using_current_user save_record mark_as_idle]
      error_on_all_events :mark_as_idle
    end

    def mark_as_idle
      aasm.send(:current_event=, nil) # nullify current_event after transitioning
    end

    def guard_internal?
      draft?
    end

    def guard_publish?
      draft? || internal?
    end

    def guard_archive?
      internal? || published?
    end

    def guard_unarchive?
      archived?
    end
  end
end
