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

    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/>.

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


#include "dialog_popupnote.hpp"
#include "../app_window.hpp"
#include "../widgets/widget_textview.hpp"
#include "../widgets/widget_textviewpopup.hpp"

namespace LoG
{

// DIALOGPOPUPNOTE ================================================================================
DialogPopupNote*
DialogPopupNote::create( DiaryElemTag* p2tag, bool F_help_mode )
{
    Entry*  p2entry { p2tag->is_entry() ? dynamic_cast< Entry* >( p2tag )
                                        : dynamic_cast< Paragraph* >( p2tag )->m_host };
    Paragraph* p2para
                    { p2tag->is_entry() ? nullptr : dynamic_cast< Paragraph* >( p2tag ) };
    auto&&  builder { Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/dlg_popupnote.ui" ) };
    auto    ptr     { Gtk::Builder::get_widget_derived< DialogPopupNote >( builder,
                                                                           "D_popupnote" ) };
    Diary*  p2diary { p2entry->get_diary() };
    auto    css_cls { p2entry->get_theme()->get_css_class_name() };

    if( p2entry->is_popup_note() )
    {
        int w, h;
        p2entry->get_popup_size( w, h );
        ptr->set_default_size( w, h );
        // ptr->move( x, y ); <-- not possible on Gtk4!
    }
    else
        p2entry->set_popup_note( true );

    ptr->set_application( Lifeograph::p );
    if( Diary::d->is_open() ) // otherwise UI_diary is not ready
        ptr->insert_action_group( "entry", AppWindow::p->UI_diary->m_AG_entry );

    ptr->signal_close_request().connect(
            [ ptr ]()
            {
                Entry* p2entry { ptr->m_TvD->get_entry() };
                int w, h;
                ptr->get_default_size( w, h );
                p2entry->store_popup_size( w, h );
                AppWindow::p->remove_popup_dlg( p2entry->get_id_full() );
                delete ptr;
                return true; // prevent default hide behavior
            },
            false );

    if( p2para )
    {
        if( !p2para->is_visible() )
            p2para->make_accessible();
        p2entry->set_cursor_pos( p2para->get_bgn_offset_in_host() );
    }

    ptr->m_TvD->set_entry( p2entry );
    ptr->refresh_editability();

    ptr->m_L_title->set_name( css_cls );
    ptr->m_L_info->set_name( css_cls );
    ptr->m_I_lock->set_name( css_cls );

    ptr->m_L_title->set_markup( p2entry->get_title_ancestral() );

    Ustring info;
    if( F_help_mode )
        info = p2diary->get_name();
    else
    {
        info = STR::compose( Date::format_string_adv( p2entry->get_date(), "F, d  |  " ) );
        if( p2entry->get_workload() )
        {
            info += p2entry->get_completion_str();
            info += "  |  ";
        }
        info += p2diary ? p2diary->get_name() : "-";
    }
    ptr->m_L_info->set_text( info );

    if( F_help_mode )
        ptr->m_I_entry->set_from_icon_name( "help-browser-symbolic" );
    else
        ptr->m_I_entry->set( p2entry->get_icon() );

    ptr->m_CPo_entry.set( p2entry, *ptr->m_B_entry, nullptr );

    ptr->set_transient_for( *AppWindow::p );
    ptr->show();

    return ptr;
}

DialogPopupNote::DialogPopupNote( BaseObjectType* cobject,
                                  const Glib::RefPtr< Gtk::Builder >& builder )
:   Gtk::Window( cobject )
{
    m_L_title       = builder->get_widget< Gtk::Label >( "L_title" );
    m_L_info        = builder->get_widget< Gtk::Label >( "L_info" );
    m_I_lock        = builder->get_widget< Gtk::Image >( "I_lock" );
    m_B_entry       = builder->get_widget< Gtk::Button >( "B_entry" );
    m_I_entry       = builder->get_widget< Gtk::Image >( "I_entry" );
    m_Bx_search     = builder->get_widget< Gtk::Box >( "Bx_search" );
    m_E_search      = builder->get_widget< Gtk::SearchEntry >( "SE_search" );

    auto B_search_prev = builder->get_widget< Gtk::Button >( "B_search_prev" );
    auto B_search_next = builder->get_widget< Gtk::Button >( "B_search_next" );

    m_TvD           = Gtk::Builder::get_widget_derived< TextviewDiaryPopup >( builder, "TvD" );

    m_TvD->set_helper_widgets( builder->get_widget< Gtk::Window >( "D_popupnote" ),
                               builder->get_widget< Gtk::ScrolledWindow >( "SW" ) );
    m_TvD->signal_changed().connect(
            [ this ]()
            {
                AppWindow::p->add_entry_to_be_refreshed( m_TvD->get_entry()->get_id_full(),
                                                         EditorType::POPUP );
            } );

    m_TvD->property_has_focus().signal_changed().connect(
            [ this ]()
            {
                if( m_TvD->has_focus() )
                {
                    if( AppWindow::p->handle_entry_need_for_refresh( m_TvD->get_entry()->get_id(),
                                                                     EditorType::POPUP ) )
                        m_TvD->refresh_entry_simple();
                }
            } );

    m_B_entry->signal_clicked().connect( [ this ]() { m_CPo_entry.show(); } );

    m_Bx_search->set_visible( false );

    m_E_search->signal_search_changed().connect(
            sigc::mem_fun( *this, &DialogPopupNote::handle_search ) );
    m_E_search->signal_activate().connect(
            sigc::mem_fun( *this, &DialogPopupNote::go_search_next ) );
    B_search_prev->signal_clicked().connect(
            sigc::mem_fun( *this, &DialogPopupNote::go_search_prev ) );
    B_search_next->signal_clicked().connect(
            sigc::mem_fun( *this, &DialogPopupNote::go_search_next ) );

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

void
DialogPopupNote::refresh_editability()
{
    m_TvD->update_editability();

    if( m_TvD->get_editable() )
        m_I_lock->set_visible( false );
    else
    {
        if( m_TvD->get_entry()->is_trashed() )
            m_I_lock->set_from_icon_name( "user-trash-symbolic");
        else if( m_TvD->get_entry()->is_locked_raw() )
            m_I_lock->set_from_icon_name( "system-lock-screen-symbolic");
        // NOTE: we cannot cache symbolic icons for dynamic coloring to work
        m_I_lock->set_visible( true );
    }
}

void
DialogPopupNote::refresh_icon()
{
    m_I_entry->set( m_TvD->get_entry()->get_icon() );
}

void
DialogPopupNote::handle_search()
{
    Entry*      p2entry     { m_TvD->get_entry() };
    const auto  text_search { STR::lowercase( m_E_search->get_text() ) };
    const auto  length      { text_search.length() };

    m_matches.clear();

    if( length == 0 ) return;

    for( Paragraph* para = p2entry->get_paragraph_1st(); para; para = para->m_p2next )
    {
        const Ustring para_text { STR::lowercase( para->get_text() ) };
        const auto    pos_para  { para->get_bgn_offset_in_host() };

        UstringSize pos { 0 };
        while( ( pos = para_text.find( text_search, pos ) ) != Ustring::npos )
        {
            m_matches.push_back( pos_para + pos );
            pos += length;
        }
    }

    if( !m_matches.empty() )
    {
        auto it_bgn { m_TvD->get_buffer()->get_iter_at_offset( m_matches[ 0 ] ) };
        auto it_end { m_TvD->get_buffer()->get_iter_at_offset( m_matches[ 0 ] + length ) };
        m_TvD->get_buffer()->select_range( it_bgn, it_end );
        m_TvD->scroll_to_cursor_center();
    }
}
void
DialogPopupNote::go_search_prev()
{
    if( m_matches.empty() ) return;

    const auto  length      { m_E_search->get_text().length() };
    const auto  pos_cursor  { unsigned( m_TvD->get_pos_cursor() ) };
    int         pos_prev    { -1 };

    for( int pos_bgn : m_matches )
    {
        if( pos_bgn + length < pos_cursor )
            pos_prev = pos_bgn; // a pos_prev candiadate
        else
            break;
    }

    if( pos_prev >= 0 )
    {
        auto it_bgn { m_TvD->get_buffer()->get_iter_at_offset( pos_prev ) };
        auto it_end { m_TvD->get_buffer()->get_iter_at_offset( pos_prev + length ) };
        m_TvD->get_buffer()->select_range( it_bgn, it_end );
        m_TvD->scroll_to_cursor_center();
    }
}
void
DialogPopupNote::go_search_next()
{
    if( m_matches.empty() ) return;

    const auto  length      { m_E_search->get_text().length() };
    const auto  pos_cursor  { m_TvD->get_pos_cursor() };

    for( int pos_bgn : m_matches )
    {
        if( pos_bgn > pos_cursor )
        {
            auto it_bgn { m_TvD->get_buffer()->get_iter_at_offset( pos_bgn ) };
            auto it_end { m_TvD->get_buffer()->get_iter_at_offset( pos_bgn + length ) };
            m_TvD->get_buffer()->select_range( it_bgn, it_end );
            m_TvD->scroll_to_cursor_center();
            break;
        }
    }
}

bool
DialogPopupNote::on_key_press_event( guint keyval, guint, Gdk::ModifierType state )
{
    switch( int( state ) & CTRL_ALT_SHIFT_MASK )
    {
        case 0:
            switch( keyval )
            {
                case GDK_KEY_Escape:
                    if( m_Bx_search->get_visible() )
                    {
                        m_E_search->set_text( "" );
                        m_Bx_search->set_visible( false );
                    }
                    else
                        close();
                    return true;
                case GDK_KEY_F3:
                    if( m_Bx_search->get_visible() )
                        go_search_next();
                    return true;
            }
            break;
        case int( Gdk::ModifierType::SHIFT_MASK ):
            switch( keyval )
            {
                case GDK_KEY_F3:
                    if( m_Bx_search->get_visible() )
                        go_search_prev();
                    return true;
            }
            break;
        case int( Gdk::ModifierType::CONTROL_MASK ):
            switch( keyval )
            {
                case GDK_KEY_f:
                case GDK_KEY_F:
                    if( m_Bx_search->get_visible() )
                    {
                        m_E_search->set_text( "" );
                        m_Bx_search->set_visible( false );
                    }
                    else
                    {
                        m_Bx_search->set_visible( true );
                        m_E_search->grab_focus();
                    }
                    return true;
            }
            break;
    }
    return false;
}

} // end of namespace LoG
