require 'will_paginate/array'
module JoyUssdEngine
    class PaginateMenu < JoyUssdEngine::Menu
        # NOTE THIS CLASS SHOULD NEVER BE USED DIRECTLY BUT RATHER BE TREATED AS AN ABSTRACT CLASS
        # FROM WHICH OTHER CLASSES CAN INHERIT FROM AND IMPLEMENT CERTAIN CUSTOM BEHAVIOURS PERTAINING TO THEIR OPERATION

        # Paginating Menus will need to set entire collection in paginating_items, 
        # The current_page is automatically determined from the user input and 
        # the default values items_per_page, back_key, and next_key can be overiding in any class that inherits from the PaginatingMenu 
        # Every paginating menu must set the current_client_state property to be their client_state this keeps track of pagination and menu location

        # To paginate a menu you just call the `paginate` method and to display a paginated menu you call the `show_menu` method and pass the values returned from the `paginate` method. 
        # To get a item the user selects from the menu just use the `get_selected_item` method
        # EXAMPLE: 
        # my_items = paginate
        # show_menu(my_items)
        # selected_item = get_selected_item
        
        attr_reader :paginating_items, :paginating_items_selector, :paginating_error, :current_page, :items_per_page, :back_key, :next_key 

        def initialize(context)
            super(context)
            @items_per_page = 5
            @back_key = '0'
            @next_key = '#'
        end

        def paginate 

            before_paginate
            return [] if @paginating_error
            
            @current_page = get_current_page
            
            paginated_items = @paginating_items.to_a.paginate(page: @current_page, per_page: @items_per_page)

            is_first_render = @context.get_state[:"#{@field_name}_paginate_initiation"].blank?

            @context.set_state({"#{@field_name}_paginate".to_sym => @current_page, "#{@field_name}_paginated_list_size".to_sym => paginated_items.length, "#{@field_name}_list_size".to_sym => paginated_items.length, "#{@field_name}_paginate_initiation".to_sym => is_first_render ? "is_new" : "exists"})

            paginated_items
        end

        def show_menu(items=[], title: '', key: '')

            return if has_selected?
            
            @errors = true if @paginating_error || items.blank?
            return @error_text if @paginating_error
            return raise_error("Sorry something went wrong!") if items.blank?

            more_menu = !is_last_page(@current_page, items.length)
            back_menu = !is_first_page(@current_page) 

            item_number = (@current_page - 1) * @items_per_page

            tmp_menu = []
            
            first_option = item_number
            items.each do |m|
              tmp_menu << "#{first_option+=1}. #{key.blank? ? m : m[:"#{key}"]}"
            end

            tmp_menu << "#{back_key}. Back" if back_menu
            tmp_menu << "#{next_key}. Next" if more_menu
            text = tmp_menu.join("\n")
            text = "#{title}\n#{text}" unless title.blank?
            text
        end

        def load_menu(menu_to_load)
            return render_menu_error[:data] if @menu_error
            next_menu = menu_to_load.to_s
            if has_selected?
                @context.set_state({"#{@field_name}_paginate_initiation".to_sym =>  nil})
                set_previous_state 
                @context.current_menu = @current_client_state = next_menu
                @context.load_from_paginate_menu(next_menu) 
            end
        end

        def has_selected?
            can_paginate? &&  ((@context.params[:message] != @next_key) && (@context.params[:message] != @back_key))
        end

        def can_paginate?
            @context.get_state[:"#{@field_name}_paginate_initiation"] == 'exists'
        end

        def can_load_more?
            !@context.get_state[:"#{@field_name}_paginate"].blank? &&
            (@context.params[:message] == @next_key) && !is_last_page(get_previous_page, @context.get_state[:"#{@field_name}_paginated_list_size"])
        end

        def can_go_back?
            !@context.get_state[:"#{@field_name}_paginate"].blank? &&
            (@context.params[:message] == @back_key) && !is_first_page(get_previous_page)
        end

        def get_selected_item(error_message = "Sorry wrong option selected")

            selected_item = nil
            check_input = is_numeric(@context.params[:message]) && @context.params[:message].to_i != 0
            if check_input 
                selected_item = @paginating_items[@context.params[:message].to_i - 1]
            end
            

            @paginating_error = selected_item.blank?
            @error_text = error_message if @paginating_error
            selected_item
        end

        def load_next_page
            (@context.get_state[:"#{@field_name}_paginate"].to_i + 1)
        end

        def load_prev_page
            (@context.get_state[:"#{@field_name}_paginate"].to_i - 1)
        end

        def get_previous_page
            can_paginate? ? @context.get_state[:"#{@field_name}_paginate"] : 1
        end

        def get_current_page
            return 1 unless !@context.get_state[:"#{@field_name}_paginate"].blank?
            return load_prev_page if can_go_back?
            return load_next_page if can_load_more?
            @current_page
        end

        def check_input_errors?
           @errors = (!can_load_more? && @context.params[:message] == @next_key) || (!can_go_back? && @context.params[:message] == @back_key)

           @error_text = "Sorry invalid input" if @errors
           @errors
        end

        def is_last_page(page_number, page_items_size)
            (((page_number - 1) * 5) + page_items_size) >= @paginating_items.count
        end

        def is_first_page(page_number)
            page_number.present? ? page_number == 1 : true
        end

        def before_paginate
           @paginating_error = check_input_errors?
        end

        def before_render
            # To use pagination in a particular menu we need to follow the other of execution in the before_render method
            # 
            # 1. {my_items = paginate} - first you have to execute the `paginate` method and cache the value
            # 2. {show_menu(my_items)} - then you pass the cached value returned from the `paginate` method 
            # and save in some data in some variable you will use in the `render` method to render the page.
            # 3. {get_selected_item if has_selected?} - we use this to `get_selected_item` to return the item
            # the user selected and we do so by checking if the user has selected an item with the `has_selected?` method
        end

        def render
            # Paginating menu's render method calls the `load_menu` method to load a particular menu
            # This can come from a selected paginating item
            # load_menu(menu_name)

            # Paginating menus also terminates the session with a `joy_release` method to end the ussd_application
            # joy_release(@error_message - optional)
        end

        def render_previous
            @context.current_menu = @previous_menu.current_client_state = @previous_client_state
            @context.set_state({"#{@field_name}_paginate_initiation".to_sym =>  nil})
            @previous_menu.render_field_error
        end
        
        def execute
            save_field_value
            do_validation
            before_render
            return render_menu_error[:data] if @paginating_error || @menu_error
            if allow_validation
                return render_previous if @previous_menu.field_error
            end
            response = render
            response = response.blank? ? joy_response(@current_client_state) : response
            after_render
            save_state(response[:ClientState]) if response[:ClientState] != "EndJoyUssdEngineiuewhjsdj"
            @context.reset_state if response[:ClientState] == "EndJoyUssdEngineiuewhjsdj" 
            response[:data].blank? ? response : response[:data]
        end
    end
end