/***********************************************************************************

    Copyright (C) 2007-2026 Ahmet Öztürk (aoz_2@yahoo.com)

    This file is part of Lifeograph.

    Lifeograph is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Lifeograph is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Lifeograph.  If not, see <http://www.gnu.org/licenses/>.

***********************************************************************************/


#ifndef LIFEOGRAPH_POPICKER_HEADER
#define LIFEOGRAPH_POPICKER_HEADER


#include <gtkmm.h>

#include "../lifeograph.hpp"
#include "../helpers.hpp"
#include "../helpers_gtk.hpp"


namespace LoG
{

// POPOVER FOR SELECTING ITEMS =====================================================================
template< class T >
class PoPicker : public Gtk::Popover
{
    struct RowItem
    {
        T       id;
        Ustring label;
        String  icon_name;
        bool    F_stock;
        RowItem( const T& i, const Ustring& l, const String& in, bool Fs )
        : id( i ), label( l ), icon_name( in ), F_stock( Fs ) { }
    };

    public:
        using VecElems = std::vector< RowItem >;

        PoPicker( Gtk::Widget& parent, const Ustring& title )
        { 
            m_builder       = Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/po_picker.ui" );

            auto Bx_main    = m_builder->get_widget< Gtk::Box >( "Bx_main" );
            m_LB            = m_builder->get_widget< Gtk::ListBox >( "LB_list" );
            m_L_title       = m_builder->get_widget< Gtk::Label >( "L_title" );
            m_B_add         = m_builder->get_widget< Gtk::Button >( "B_add" );
            m_E_search      = m_builder->get_widget< Gtk::SearchEntry >( "E_search" );

            m_B_add->set_visible( m_F_show_add && !m_F_select_only );
            m_L_title->set_label( title );

            m_LB->signal_row_selected().connect(
                    [ this ]( Gtk::ListBoxRow* row )
                    { ensure_selected_LB_row_in_view( m_LB, row ); } );

            m_LB->signal_row_activated().connect(
                    [ this ]( Gtk::ListBoxRow* )
                    {
                        m_Sg_item_activated.emit( get_selected_from_LB() );
                        popdown();
                    } );

            m_B_add->signal_clicked().connect(
                    [ this ]()
                    {
                        m_Sg_add.emit();
                        update_LB();
                    } );

            m_E_search->signal_changed().connect(
                    [ this ]()
                    {
                        if( m_F_internal ) return;
                        update_LB();
                    } );
            m_E_search->signal_activate().connect(
                    [ this ]() { on_key_press_event( GDK_KEY_Return, 0,
                                                     Gdk::ModifierType( 0 ) ); } );

            signal_show().connect(
                    [ this ]()
                    {
                        m_F_internal++;
                        update_LB();
                        m_E_search->set_text( "" );
                        m_E_search->grab_focus();
                        m_F_internal--;
                    } );

            // CONTROLLERS
            auto controller_key{ Gtk::EventControllerKey::create() };
            controller_key->signal_key_pressed().connect(
                    sigc::mem_fun( *this, &PoPicker::on_key_press_event ), false );
            add_controller( controller_key );

            auto controller_key_entry { Gtk::EventControllerKey::create() };
            controller_key_entry->signal_key_pressed().connect(
                    sigc::mem_fun( *this, &PoPicker::on_key_press_event ), false );
            m_E_search->add_controller( controller_key_entry );

            set_child( *Bx_main );
            set_parent( parent );
        }

        void
        set_show_edit_button( bool F_show_edit )
        { m_F_show_edit = F_show_edit; }

        void
        set_show_duplicate_button( bool F_show_duplicate )
        { m_F_show_duplicate = F_show_duplicate; }

        void
        set_show_add_button( bool F_show_add )
        { m_F_show_add = F_show_add; }

        void
        set_select_only( bool F_select_only )
        {
            m_F_select_only = F_select_only;
            m_B_add->set_visible( m_F_show_add && !F_select_only );
        }

        void
        set_modal( bool F_modal )
        { set_autohide( F_modal ); }

        void
        clear()
        { m_items.clear(); }

        void
        add_none_item_to_map() // called externally
        {
            // TODO: 3.2: not used, to be removed or embraced
            add_item_to_map( get_sstr_i( CSTR::NONE ), T{}, "", true );
        }
        void
        add_item_to_map( const Ustring& label, T id, const Ustring& icon, bool F_stock = false )
        { m_items.push_back( { label, id, icon, F_stock } ); }

        void
        popup( const Gdk::Rectangle& rect )
        {
            set_pointing_to( rect );
            Gtk::Popover::popup();
        }

        sigc::signal< void( const T& ) >    m_Sg_item_activated;
        SignalVoid                          m_Sg_add;
        sigc::signal< void( const T& ) >    m_Sg_duplicate;
        sigc::signal< void( const T& ) >    m_Sg_edit;
        sigc::signal< void( const T& ) >    m_Sg_dismiss;
        // SignalKeyEvaluate                   m_Sg_additional_shortcut_evaluate;
        SignalVoid                          m_Sg_show_next;

    protected:
        Gtk::Button*
        create_button( const String& icon_name )
        {
            auto button { Gtk::manage( new Gtk::Button ) };
            auto icon   { Gtk::manage( new Gtk::Image ) };

            icon->set_from_icon_name( icon_name );
            button->set_child( *icon );
            //button->set_relief( Gtk::RELIEF_NONE );

            return button;
        }

        Gtk::Label*
        create_item_label( const Ustring& name, bool F_stock )
        {
            auto L_item_name { Gtk::manage( new Gtk::Label( F_stock ? "<i>&lt;" + name + "&gt;</i>"
                                                                    : name,
                                                            Gtk::Align::START ) ) };
            if( F_stock )
                L_item_name->set_use_markup( true );
            L_item_name->set_margin_end( 15 ); // this makes it look better
            L_item_name->set_ellipsize( Pango::EllipsizeMode::END );
            L_item_name->set_hexpand( true );
            return L_item_name;
        }
        void
        add_item_to_LB( const RowItem& item, int idx )
        {
            auto Bx_item     { Gtk::manage( new Gtk::Box( Gtk::Orientation::HORIZONTAL, 5 ) ) };
            auto L_item_name { create_item_label( item.label, item.F_stock ) };

            if( !item.icon_name.empty() )
            {
                Gtk::Image* I_icon{ Gtk::make_managed< Gtk::Image >() };
                I_icon->set_from_icon_name( item.icon_name );
                Bx_item->append( *I_icon );
            }

            Bx_item->append( *L_item_name );
            m_LB->append( *Bx_item );

            m_item_indices.push_back( idx );

            if( !m_F_select_only )
            {
                if( m_F_show_duplicate )
                {
                    auto&& B_dupli{ create_button( "edit-copy-symbolic" ) };
                    B_dupli->set_tooltip_text( _( "Duplicate" ) );
                    B_dupli->set_has_frame( false );
                    Bx_item->append( *B_dupli );
                    B_dupli->signal_clicked().connect(
                            sigc::bind( sigc::mem_fun( *this, &PoPicker::handle_item_duplicate ),
                                        item.id ) );
                }

                if( m_F_show_edit && !item.F_stock )
                {
                    auto&& B_edit{ create_button( "document-edit-symbolic" ) };
                    B_edit->set_tooltip_text( _( "Edit" ) );
                    B_edit->set_has_frame( false );
                    Bx_item->append( *B_edit );
                    B_edit->signal_clicked().connect(
                            sigc::bind( sigc::mem_fun( *this, &PoPicker::handle_item_edit ),
                                        item.id ) );
                }

                if( m_F_show_dismiss && !item.F_stock && m_items.size() > 1 )
                {
                    auto&& B_remove{ create_button( "edit-delete-symbolic" ) };
                    B_remove->set_tooltip_text( _( "Dismiss" ) );
                    B_remove->set_has_frame( false );
                    Bx_item->append( *B_remove );
                    B_remove->signal_clicked().connect(
                            sigc::bind( sigc::mem_fun( *this, &PoPicker::handle_item_remove ),
                                        item.id ) );
                }
            }
        }

        bool
        update_LB()
        {
            bool          F_exact_match { false };
            const auto&&  filter        { STR::make_searchable( m_E_search->get_text() ) };
            int           idx           { -1 }; // used preincremented, hence -1

            m_F_internal++;

            remove_all_children_from_LBx( m_LB );
            m_item_indices.clear();

            for( auto& item : m_items )
            {
                ++idx;

                if( !filter.empty() )
                {
                    if( STR::make_searchable( item.label ).find( filter ) == Ustring::npos ||
                        item.id == T{} ) // none item is skipped when filtering
                        continue;
                }

                add_item_to_LB( item, idx );
            }

            // always make the top row readily selected
            auto row_first { m_LB->get_row_at_index( 0 ) };
            if( row_first )
                m_LB->select_row( *row_first );

            m_F_internal--;

            return F_exact_match;
        }

        void
        handle_item_duplicate( T& id )
        {
            m_Sg_duplicate.emit( id );
            update_LB();
        }

        void
        handle_item_remove( T& id )
        {
            m_Sg_dismiss.emit( id );
            update_LB();
        }

        void
        handle_item_edit( T& id )
        {
            m_Sg_edit.emit( id );
            popdown();
        }

        bool
        on_key_press_event( guint keyval, guint, Gdk::ModifierType state )
        {
            if( state == Gdk::ModifierType( 0 ) )
            {
                switch( keyval )
                {
                    case GDK_KEY_Return:
                        if( m_LB->get_selected_row() ) // i.e. has selection
                        {
                            m_Sg_item_activated.emit( get_selected_from_LB() );
                            popdown();
                        }
                        return true;
                    case GDK_KEY_Escape:
                        hide();
                        return true;
                    case GDK_KEY_Down:
                        select_LB_item_next( m_LB );
                        return true;
                    case GDK_KEY_Up:
                        select_LB_item_prev( m_LB );
                        return true;
                    default:
                        return false;
                }
            }
            else if( state  == Gdk::ModifierType( CTRL_SHIFT_MASK ) )
            {
                if( keyval == GDK_KEY_P || keyval == GDK_KEY_p )
                {
                    m_Sg_show_next.emit();
                    return true;
                }
            }
            // else
                // return m_Sg_additional_shortcut_evaluate( keyval, state );
            return false;
        }

        T
        get_selected_from_LB()
        {
            auto&& row { m_LB->get_selected_row() };
            return( row ? m_items.at( m_item_indices[ row->get_index() ] ).id : T{} );
        }

        Glib::RefPtr< Gtk::Builder >
                                    m_builder;
        Gtk::Label*                 m_L_title;
        Gtk::ListBox*               m_LB;
        Gtk::Button*                m_B_add;
        Gtk::Button*                m_B_duplicate;
        Gtk::Button*                m_B_edit;
        Gtk::SearchEntry*           m_E_search;

        VecElems                    m_items;
        VecInts                     m_item_indices;

        int                         m_F_internal            { 0 };
        bool                        m_F_show_edit           { false };
        bool                        m_F_show_duplicate      { false };
        bool                        m_F_show_dismiss        { false };
        bool                        m_F_show_add            { false };
        bool                        m_F_select_only         { false };
        bool                        m_F_ignore_entered_text { false };
};

} // end of namespace LoG

#endif
