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

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

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


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "strings.hpp"
#include "logid.hpp"
#include "lifeograph.hpp"
#include "app_window.hpp"
#include "ui_diary.hpp"
#include "ui_entry.hpp"
#include "widgets/widget_textview.hpp"
#include "dialogs/dialog_smartadd.hpp"
#include "dialogs/dialog_password.hpp"


using namespace LoG;

using VStr = Glib::Variant< Ustring >;

UIDiary::UIDiary()
{
    Gtk::PopoverMenu* Po_diary;

    try
    {
        auto builder{ Lifeograph::get_builder() };

        m_MB_diary_close    = builder->get_widget< Gtk::MenuButton >( "MB_diary_close" );

        m_B_enable_edit     = builder->get_widget< Gtk::Button >( "B_enable_edit" );
        m_B_smart_add       = builder->get_widget< Gtk::Button >( "B_smart_add" );

        m_Bx_diary          = builder->get_widget< Gtk::Box >( "Bx_diary" );
        m_B_close           = builder->get_widget< Gtk::Button >( "B_close" );

        m_MB_diary          = builder->get_widget< Gtk::MenuButton >( "MB_diary" );
        m_MB_main           = builder->get_widget< Gtk::MenuButton >( "MB_tools" );

        m_B_search          = builder->get_widget< Gtk::Button >( "B_search" );
        m_B_toggle_popups   = builder->get_widget< Gtk::Button >( "B_toggle_popups" );

        Po_diary            = builder->get_widget< Gtk::PopoverMenu >( "Po_diary_menu" );

        m_DD_spellchk       = Gtk::Builder::get_widget_derived< DropDownAdvanced< String > >(
                                    builder, "DD_diary_spellchk" );

        m_WTP_completn_tag  = Gtk::Builder::get_widget_derived< WidgetTagPicker >(
                                    builder, "E_diary_completion_tag" );

        m_Bx_extra_options  = builder->get_widget< Gtk::Box >( "Bx_diary_extra_options" );
        m_DD_startup_type   = Gtk::Builder::get_widget_derived< DropDownDEID >(
                                    builder, "DD_diary_startup_type" );
        m_Bx_startup_elem   = builder->get_widget< Gtk::Box >( "Bx_diary_startup_entry" );
        m_WTP_startup_entry = Gtk::Builder::get_widget_derived< WidgetTagPicker >(
                                    builder, "E_diary_startup_entry" );

        m_WEL_main          = Gtk::Builder::get_widget_derived< WidgetEntryList >(
                                    builder, "LV_entries" );
        // m_Ol_list           = builder->get_widget< Gtk::Overlay >( "Ol_list" );
        m_Rv_list_filter    = builder->get_widget< Gtk::Revealer >( "Rv_list_filter" );
        m_WFP_list_filter   = Gtk::Builder::get_widget_derived< WidgetFilterPicker >(
                                    builder, "MB_list_filter" );
    }
    catch( ... )
    {
        throw LoG::Error( "Failed to create the diary panel" );
    }

    m_DD_startup_type->add( get_sstr_i( DEID::MOST_CURRENT_ENTRY ), DEID::MOST_CURRENT_ENTRY );
    m_DD_startup_type->add( get_sstr_i( DEID::LAST_ENTRY ), DEID::LAST_ENTRY );
    m_DD_startup_type->add( _( "Always Show a Fixed Entry" ), DEID::MIN_ACTUAL );

    m_WTP_startup_entry->set_entry_only( true );

    m_WFP_list_filter->set_clearable( true );
    m_WFP_list_filter->set_show_edit_button( true );
    m_WFP_list_filter->set_show_name( false );
    m_WFP_list_filter->set_hide_popover_during_edit( true );
    m_WFP_list_filter->set_obj_classes( FOC::ENTRIES );
    m_WFP_list_filter->set_diary( Diary::d );

    AppWindow::p->setup_contextual_help_for_Po( m_MB_diary->get_popover() );
    AppWindow::set_help_topic( m_MB_diary, "Diary menu" );
    AppWindow::set_help_topic( m_WTP_completn_tag, "Completion percentage" );

    AppWindow::set_help_topic( m_WEL_main, "Entry list" );
    AppWindow::set_help_topic( m_WFP_list_filter, "Filtering" );

    // ENTRY LIST CONTROLLERS
    auto controller_motion = Gtk::EventControllerMotion::create();
    controller_motion->signal_motion().connect(
            [ this ]( double x, double y )
            {
                auto&& alloc{ m_WEL_main->get_allocation() };
                m_Rv_list_filter->set_reveal_child( y < ( alloc.get_height() * 0.3 ) &&
                                                    x > ( alloc.get_width() * 0.6 ) );
            },
            false );
    m_WEL_main->add_controller( controller_motion );

    m_WFP_list_filter->Sg_changed.connect(
            [ this ]( const Filter* filter )
            {
                Diary::d->set_filter_list( filter );
                update_all_entries_filter_status();
                update_entry_list();
            } );

    // HEADERBAR SIGNALS
    // at some point we may go back to using the smart add dialog with the add button:
    m_B_smart_add->signal_clicked().connect(      [ this ](){ add_entry_free(); } );

    Po_diary->signal_show().connect(              [ this ](){ refresh_main_menu_2(); } );

    m_DD_spellchk->Sg_selection_changed.connect(
            sigc::mem_fun( *this, &UIDiary::handle_language_changed ) );

    m_WTP_completn_tag->m_Sg_updated.connect(
            []( DiaryElemTag* t )
            { Diary::d->set_completion_tag( t ? t->get_id() : DEID::UNSET ); } );

    m_DD_startup_type->Sg_selection_changed.connect(
            sigc::mem_fun( *this, &UIDiary::handle_startup_type_changed ) );

    m_WTP_startup_entry->m_Sg_updated_entry.connect(
            []( Entry* e ){ Diary::d->set_startup_entry( e ); } );

    // ACTIONS
    m_A_enable_editing = Lifeograph::p->add_action(
            "enable_editing",
            [ this ](){ enable_editing(); } );

    m_A_open_diary_folder = Lifeograph::p->add_action(
            "main_open_diary_folder",
            [ this ](){ open_diary_folder(); } );
    m_A_import_diary = Lifeograph::p->add_action(
            "main_import_diary",
            [ this ](){ start_dialog_import(); } );
    m_A_export_diary = Lifeograph::p->add_action(
            "main_export_diary",
            [ this ](){ start_dialog_export(); } );
    m_A_sync_diary = Lifeograph::p->add_action(
            "main_sync_diary",
            [ this ](){ start_dialog_sync(); } );
    m_A_encrypt_diary = Lifeograph::p->add_action(
            "main_encrypt_diary",
            [ this ](){ start_dialog_password(); } );
    m_A_change_password = Lifeograph::p->add_action(
            "main_change_password",
            [ this ](){ start_dialog_password(); } );
    m_A_empty_trash = Lifeograph::p->add_action(
            "main_empty_trash",
            [ this ](){ empty_trash(); } );

    Lifeograph::p->add_action( "add_entry_dated",     [ this ]() { add_entry_dated( false ); } );
    Lifeograph::p->add_action( "add_milestone",       [ this ]() { add_entry_dated( true ); } );
    Lifeograph::p->add_action( "add_entry_todo",
            [ this ]()
            {
                if( Diary::d->is_in_edit_mode() )
                {
                    Entry* entry = Diary::d->create_entry( nullptr, true, Date::get_today(), "",
                                                           VT::ETS::NAME_ONLY::I );
                    entry->set_todo_status( ES::TODO );
                    update_for_added_entry( entry );
                }
            } );
    Lifeograph::p->add_action( "add_entry_free",      [ this ](){ add_entry_free(); } );
    Lifeograph::p->add_action( "go_to_today",         [ this ](){ show_today(); } );
    Lifeograph::p->add_action( "smart_add",           [](){ DialogSmartAdd::create(); } );
    Lifeograph::p->add_action( "go_list_prev_all",    [](){ AppWindow::p->go_to_prev( true ); } );
    Lifeograph::p->add_action( "go_list_next_all",    [](){ AppWindow::p->go_to_next( true ); } );
    Lifeograph::p->add_action( "go_list_prev",        [](){ AppWindow::p->go_to_prev( false ); } );
    Lifeograph::p->add_action( "go_list_next",        [](){ AppWindow::p->go_to_next( false ); } );
    Lifeograph::p->add_action( "jump_to_current_entry",
            [ this ](){ show_in_list( AppWindow::p->UI_entry->get_cur_entry(), true ); } );
    Lifeograph::p->add_action( "go_to_prev_sess_entry",
            [ this ](){ show_prev_session_elem(); } );
    Lifeograph::p->add_action( "go_to_home_entry",    [ this ](){ show_home_elem(); } );

    // ENTRY CREATION ACTIONS
    // Lifeograph::p->add_action_with_parameter( "entry_duplicate",
    Lifeograph::p->add_action( "entry_duplicate",
            [ this ]()
            {
                if( !Diary::d->is_in_edit_mode() ) return;
                update_for_added_entry( Diary::d->duplicate_entry( get_entry_for_action() ) );
            } );

    Lifeograph::p->add_action( "entry_add_child",
            [ this ]()
            {
                if( !Diary::d->is_in_edit_mode() ) return;

                Entry*  p2entry_rel { get_entry_for_action() };
                p2entry_rel->set_expanded( true );
                bool    F_parent    { Diary::guarantee_add_last_for_parent( p2entry_rel ) };

                update_for_added_entry( Diary::d->create_entry( p2entry_rel,
                                                                F_parent,
                                                                Date::get_today(),
                                                                "",
                                                                VT::ETS::INHERIT::I ) );
            } );
    Lifeograph::p->add_action( "entry_add_sibling",
            [ this ]()
            {
                if( !Diary::d->is_in_edit_mode() ) return;
                update_for_added_entry( Diary::d->create_entry( get_entry_for_action(),
                                                                false,
                                                                Date::get_today(),
                                                                "",
                                                                VT::ETS::INHERIT::I ) );
            } );

    // ENTRY ACTIONS
    m_AG_entry = Gio::SimpleActionGroup::create();

    m_AG_entry->add_action( "print",        [](){ AppWindow::p->print(); } );
    m_AG_entry->add_action( "trash",        [ this ](){ trash_entries( true ); } )
                    ->set_enabled( false );
    m_AG_entry->add_action( "restore",      [ this ](){ trash_entries( false ); } )
                    ->set_enabled( false );
    m_AG_entry->add_action( "dismiss",      [ this ](){ dismiss_entries(); } )
                    ->set_enabled( false );

    m_AG_entry->add_action( "add" )->set_enabled( false );
    m_AG_entry->add_action( "add_parent",   [ this ](){ add_parent_to_entries(); } );
    m_AG_entry->add_action( "merge",        [ this ](){ merge_entries(); } )->set_enabled( false );

    m_AG_entry->add_action( "sort_siblings" )->set_enabled( false );
    m_AG_entry->add_action( "sort_sibl_az",
            [ this ]()
            {
                Diary::d->sort_entry_siblings( m_CPo_entry.m_p2entry, &Entry::compare_names, 1 );
                update_entry_list();
            } );
    m_AG_entry->add_action( "sort_sibl_za",
            [ this ]()
            {
                Diary::d->sort_entry_siblings( m_CPo_entry.m_p2entry, &Entry::compare_names, -1 );
                update_entry_list();
            } );
    m_AG_entry->add_action( "sort_sibl_earliest",
            [ this ]()
            {
                Diary::d->sort_entry_siblings( m_CPo_entry.m_p2entry, &Entry::compare_dates, 1 );
                update_entry_list();
            } );
    m_AG_entry->add_action( "sort_sibl_latest",
            [ this ]()
            {
                Diary::d->sort_entry_siblings( m_CPo_entry.m_p2entry, &Entry::compare_dates, -1 );
                update_entry_list();
            } );
    m_AG_entry->add_action( "sort_sibl_shortest",
            [ this ]()
            {
                Diary::d->sort_entry_siblings( m_CPo_entry.m_p2entry, &Entry::compare_sizes, 1 );
                update_entry_list();
            } );
    m_AG_entry->add_action( "sort_sibl_longest",
            [ this ]()
            {
                Diary::d->sort_entry_siblings( m_CPo_entry.m_p2entry, &Entry::compare_sizes, -1 );
                update_entry_list();
            } );
    m_AG_entry->add_action( "unpopup",
            []() { AppWindow::p->unpopup_active_entry(); } );
    m_AG_entry->add_action( "script_actions",
            [ this ]() { show_Po_script_entry(); } )->set_enabled( false );
    m_AG_entry->add_action( "script_actions_list",
            [ this ]() { show_Po_script_entry( true ); } )->set_enabled( false );

    AppWindow::p->insert_action_group( "entry", m_AG_entry );

    Lifeograph::p->set_accel_for_action( "entry.print",                 "<Ctrl>P" );

    Lifeograph::p->set_accel_for_action( "app.enable_editing",          "F2" );
    Lifeograph::p->set_accel_for_action( "app.go_to_today",             "<Ctrl>T" );
    Lifeograph::p->set_accel_for_action( "app.smart_add",               "F12" );
    Lifeograph::p->set_accel_for_action( "app.add_entry_todo",          "<Ctrl>D" );
    // all goes to next prev/next entry in the diary even if it is filtered out of the list atm:
    Lifeograph::p->set_accel_for_action( "app.go_list_prev_all",        "<Ctrl>Page_Up" );
    Lifeograph::p->set_accel_for_action( "app.go_list_next_all",        "<Ctrl>Page_Down" );
    Lifeograph::p->set_accel_for_action( "app.go_list_prev",            "<Ctrl><Shift>Page_Up" );
    Lifeograph::p->set_accel_for_action( "app.go_list_next",            "<Ctrl><Shift>Page_Down" );
    Lifeograph::p->set_accel_for_action( "app.jump_to_current_entry",   "<Ctrl>J" );
    Lifeograph::p->set_accel_for_action( "app.go_to_prev_sess_entry",   "<Ctrl>O" );
    Lifeograph::p->set_accel_for_action( "app.go_to_home_entry",        "<Ctrl><Shift>O" );
    // Lifeograph::p->set_accel_for_action( "app.entry_duplicate::KB",     "<Ctrl><Shift>R" );
    Lifeograph::p->set_accel_for_action( "app.entry_duplicate",         "<Ctrl><Shift>R" );
    Lifeograph::p->set_accel_for_action( "app.entry_add_child",         "<Ctrl><Shift>C" );
    Lifeograph::p->set_accel_for_action( "app.entry_add_sibling",       "<Ctrl><Shift>S" );
}
UIDiary::~UIDiary()
{
    DialogSmartAdd::disband();
}

// Gtk::TreeModel::iterator
// UIDiary::add_entry_to_list( Entry* entry,
//                             std::map< DEID, Gtk::TreeRow >* map_rows,
//                             bool F_add_back_parent_mode )
// {
//     Gtk::TreeModel::iterator iter_elem;
//
//     if( !F_add_back_parent_mode )
//     {
//         entry->set_list_order( ++m_list_order_last );
//         entry->set_sibling_order( entry->get_prev() ?
//                                   entry->get_prev()->get_sibling_order() + 1 : 1 );
//     }
//
//     if( !F_add_back_parent_mode && entry->is_filtered_out() )
//     {
//     }
//     else
//     {
//         auto parent_entry{ entry->get_parent() };
//
//         if( !parent_entry ) // 1st level
//             iter_elem = m_TV_entries->m_treestore->append();
//         else
//         {
//             auto&& result{ map_rows->find( parent_entry->get_id() ) };
//             auto   iter_parent =
//                     ( result == map_rows->end() ) ? // parent is not in the list
//                             add_entry_to_list( parent_entry, map_rows, true ) : // recursion
//                             result->second.get_iter();
//             iter_elem = m_TV_entries->m_treestore->append( iter_parent->children() );
//         }
//
//         Gtk::TreeRow row{ *iter_elem };
//         row[ ListData::colrec->ptr ] = entry; // ptr must be first
//         row[ ListData::colrec->info ] = entry->get_list_str();
//         row[ ListData::colrec->icon ] = entry->get_icon();
//         map_rows->emplace( entry->get_id(), row );
//     }
//
//     if( !F_add_back_parent_mode && entry->get_child_1st() )
//         add_entry_siblings_to_list( entry->get_child_1st(), map_rows ); // indirect recursion
//
//     return iter_elem;
// }
//
// void
// UIDiary::add_entry_siblings_to_list( Entry* entry, std::map< DEID, Gtk::TreeRow >* map_rows )
// {
//     for( Entry* e = entry; e != nullptr; e = e->get_next() )
//         add_entry_to_list( e, map_rows, false );
// }

void
UIDiary::update_entry_list( /*const Entry* entry2show*/ )
{
    PRINT_DEBUG( "update_entry_list()" );
    Lifeograph::START_INTERNAL_OPERATIONS();

    // store the current scroll position
    // Gtk::TreePath path_bgn, path_end;
    // m_TV_entries->get_visible_range( path_bgn, path_end );
    // TODO: 4.0: an equal of this is not available in Gtk4 now.
    // see: https://gitlab.gnome.org/GNOME/gtk/-/issues/4688

    // entries:
    m_WEL_main->populate();

    // restore scroll position
    // if( path_bgn.empty() == false )
    //     m_TV_entries->scroll_to_row( path_bgn, 0.0 );

    // update current elem if last one is not in the list anymore:
    Entry* entry { AppWindow::p->UI_entry->get_cur_entry() };
    if( entry )
        m_WEL_main->present_entry( entry );

    // if ( entry2show )
    //     m_WEL_main->scroll_to_entry( entry2show );
    // else if( entry )
    //     m_WEL_main->scroll_to_entry( entry );

    Lifeograph::FINISH_INTERNAL_OPERATIONS();
}

void
UIDiary::update_all_entries_filter_status()
{
    m_WFP_list_filter->set_no_of_filtered_in_items( Diary::d->update_all_entries_filter_status() );
}

void
UIDiary::handle_login()
{
    m_WTP_startup_entry->set_diary( Diary::d );

    Lifeograph::START_INTERNAL_OPERATIONS();

    update_entry_list(); // this used to be done in UI_extra when there was a filter panel
    m_WFP_list_filter->set_active( Diary::d->m_p2filter_list );

    m_B_enable_edit->set_visible( !Lifeograph::settings.rtflag_read_only );
    m_B_enable_edit->set_sensitive( Diary::d->can_enter_edit_mode() );
    m_MB_diary_close->set_visible( Diary::d->is_encrypted() );
    m_MB_diary->set_visible( true );

    m_MB_diary->set_tooltip_text( Gio::File::create_for_uri( Diary::d->get_uri() )
                                        ->get_parse_name() );

    refresh_main_menu( Diary::LOGGED_IN_RO );

    Lifeograph::FINISH_INTERNAL_OPERATIONS();
}

void
UIDiary::enable_editing()
{
    if( !Diary::d->is_open() || Diary::d->is_in_edit_mode() )
        return;

    if( Diary::d->is_old() )
    {
        DialogMessage::init( AppWindow::p, _( "Are You Sure You Want to Upgrade The Diary?" ) )
                    ->add_extra_info( _( STRING::UPGRADE_DIARY_CONFIRM ) )
                    ->add_button( _( "Upgrade The Diary" ),
                                  std::bind( &UIDiary::enable_editing2, this ) )
                    ->show();
    }
    else
        enable_editing2();
}

void
UIDiary::enable_editing2()
{
    switch( Diary::d->enable_editing() )
    {
        case LoG::SUCCESS:
            break;
        case LoG::FILE_READ_ONLY: // this should never occur?
            AppWindow::p->show_info( _( "Cannot edit a read-only diary" ) );
            return;
        case FILE_NOT_WRITABLE:
            AppWindow::p->show_info( _( STRING::DIARY_NOT_WRITABLE ) );
            return;
        default:
            AppWindow::p->show_info( _( STRING::FAILED_TO_OPEN_DIARY ) );
            return;
    }

    m_B_smart_add->set_visible( true );
    m_MB_diary_close->set_visible( true );
    m_B_enable_edit->set_visible( false );
    m_Bx_extra_options->set_visible( true );

    m_WEL_main->set_editable();

    refresh_main_menu( Diary::LOGGED_IN_EDIT );

    AppWindow::p->handle_edit_enabled();
    AppWindow::p->UI_entry->handle_edit_enabled();
    AppWindow::p->UI_extra->handle_edit_enabled();
}

void
UIDiary::handle_logout()
{
    Lifeograph::START_INTERNAL_OPERATIONS();

    m_B_smart_add->set_visible( false );
    m_Bx_diary->set_visible( false );
    m_B_search->set_visible( false );
    m_B_toggle_popups->set_visible( false );
    m_B_enable_edit->set_visible( false );
    m_MB_diary->set_visible( false );
    m_Bx_extra_options->set_visible( false );

    m_WEL_main->clear_and_disable();

    refresh_main_menu( Diary::LOGGED_OUT );

    m_CPo_entry.handle_logout();

    Lifeograph::FINISH_INTERNAL_OPERATIONS();
}

void
UIDiary::refresh_main_menu( int level )
{
    // const bool F_diary_cls { level == Diary::LOGGED_OUT };
    const bool F_diary_opn { level > Diary::LOGGED_OUT };
    const bool F_diary_edt { level == Diary::LOGGED_IN_EDIT };

    m_A_sync_diary->set_enabled( F_diary_edt );
    m_A_import_diary->set_enabled( F_diary_edt );
    m_A_export_diary->set_enabled( F_diary_opn );
    m_A_encrypt_diary->set_enabled( F_diary_edt && !Diary::d->is_encrypted() );
    m_A_change_password->set_enabled( F_diary_edt && Diary::d->is_encrypted() );
    m_A_open_diary_folder->set_enabled( F_diary_opn );

    m_B_search->set_visible( true );
    m_Bx_diary->set_visible( true );
    m_B_close->set_visible( !Lifeograph::settings.rtflag_single_file );

    if( !F_diary_opn ) return;

    // SPELL CHECKING
    m_DD_spellchk->clear();
    m_DD_spellchk->add( get_sstr_i( CSTR::OFF ), get_sstr( CSTR::OFF ) );

    for( auto& lang : Lifeograph::s_lang_list )
        m_DD_spellchk->add( lang, lang );

    if( Diary::d->m_language.empty() )
        m_DD_spellchk->select_by_id( get_sstr( CSTR::OFF ) );
    else
    {
        if( Lifeograph::s_lang_list.find( Diary::d->m_language ) == Lifeograph::s_lang_list.end() )
            m_DD_spellchk->add( "⚠ " + Diary::d->m_language, Diary::d->m_language );

        m_DD_spellchk->select_by_id( Diary::d->m_language );
    }

    // COMPLETION TAG
    m_WTP_completn_tag->set_diary( Diary::d );
    m_WTP_completn_tag->set_elem( Diary::d->get_completion_tag() );

    // STARTUP ELEMENT
    if( Diary::d->m_startup_entry_id <= DEID::LAST_ENTRY )
        m_DD_startup_type->select_by_id( Diary::d->m_startup_entry_id );
    else
        m_DD_startup_type->set_selected( 2 );

    refresh_startup_elem();
}
// below method is for things that are hard to track so are updated on show:
void
UIDiary::refresh_main_menu_2()
{
    m_A_empty_trash->set_enabled( Diary::d->is_in_edit_mode() && !Diary::d->is_trash_empty() );
}

void
UIDiary::show_in_list( const Entry* entry, bool F_hard )
{
    if( !entry )
        m_WEL_main->select( nullptr ); // deselect all
    else
    if( F_hard ) // i.e. expand to the row
        m_WEL_main->present_entry_hard( entry );
    else
        m_WEL_main->present_entry( entry );
}

Entry*
UIDiary::add_today()
{
    return Diary::d->create_entry_dated( AppWindow::p->UI_entry->get_cur_entry(),
                                         Date::get_today(),
                                         false );
}

void
UIDiary::show_today()
{
    if( !Diary::d->is_open() ) return;

    Entry*  entry_today { Diary::d->get_entry_today() };
    bool    F_add       { false };

    if( !entry_today )   // add new entry if no entry exists on selected date
    {
        if( Diary::d->is_in_edit_mode() )
            F_add = true;
        else
            return;
    }
    // or current entry is already at today
    else if( Date::isolate_YMD( entry_today->get_date() ) ==
             Date::isolate_YMD( AppWindow::p->UI_entry->get_cur_entry()->get_date() ) )
        F_add = true;

    if( F_add )
    {
        entry_today = add_today();
        update_entry_list();
    }

    AppWindow::p->show_entry( entry_today );
}

void
UIDiary::show_Po_entry( Entry* entry, Gdk::Rectangle* rect )
{
    m_CPo_entry.set( entry, *AppWindow::p, rect );
    m_CPo_entry.show();
}

void
UIDiary::show_Po_script_entry( bool F_list_mode )
{
    Gtk::Widget*    p2W_parent;
    Gdk::Rectangle  rect;

    if( F_list_mode )
    {
        auto    entries { get_selected_entries() };
        Entry*  entry   { entries.size() > 0 ? *entries.begin()
                                             : AppWindow::p->UI_entry->get_cur_entry() };
        auto    row     { m_WEL_main->get_entry_row( entry ) };

        if( !row ) return;

        auto    gentry  { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };

        if( !gentry ) return;

        auto    rg      { gentry->m_Bx->get_parent()->compute_bounds( *AppWindow::p ) };

        p2W_parent = AppWindow::p;
        rect = Gdk::Rectangle( int( rg->get_x() ), int( rg->get_y() ),
                               int( rg->get_width() ), int( rg->get_height() ) );
    }
    else
    {
        auto TvDE { AppWindow::p->UI_entry->get_textview() };
        p2W_parent = TvDE;
        rect = Gdk::Rectangle( TvDE->PAGE_MARGIN - TextviewDiary::MAIN_ICON_OFFSET_X,
                               TextviewDiary::MAIN_ICON_OFFSET_Y, 32.0, 0.0 );
    }

    show_Po_script( ScriptCategory::ENTRY_MODIFIER,
                    _( "Entry Modifiers" ),
                    *p2W_parent,
                    rect,
                    [ this ]() { show_Po_script_word(); },
                    [ this ]( const Ustring& name )
                    {
                        PyBindings::run_script_name< const EntrySelection& >(
                                name, get_selected_entries() );
                    } );
}
void
UIDiary::show_Po_script_para()
{
    show_Po_script( ScriptCategory::PARA_MODIFIER,
                    _( "Paragraph Modifiers" ),
                    *AppWindow::p->UI_entry->get_textview(),
                    std::move( AppWindow::p->UI_entry->get_textview()->get_para_menu_rect() ),
                    [ this ]() { show_Po_script_entry(); },
                    []( const Ustring& name )
                    {
                        const auto& paras { AppWindow::p->UI_entry->get_textview()
                                                ->get_selected_paras() };
                        PyBindings::run_script_name( name, paras );
                    } );
}
void
UIDiary::show_Po_script_word()
{
    Gdk::Rectangle rect;
    AppWindow::p->UI_entry->get_textview()->get_selection_rect( rect );
    show_Po_script( ScriptCategory::WORD_MODIFIER,
                    _( "Word Modifiers" ),
                    *AppWindow::p->UI_entry->get_textview(),
                    rect,
                    [ this ]() { show_Po_script_para(); },
                    []( const Ustring& name )
                    {
                        auto        tv  { AppWindow::p->UI_entry->get_textview() };
                        Paragraph*  p   { tv->get_para_cursor() };
                        const int   ost { p->get_bgn_offset_in_host() };
                        int p_bgn, p_end;
                        tv->get_selection_bounds( p_bgn, p_end );
                        PyBindings::run_script_name< Paragraph*&,
                                                     const int&,
                                                     const int& >( name, p, p_bgn - ost,
                                                                            p_end - ost );
                    } );
}

bool
UIDiary::show_Po_script( ScriptCategory ctg, const Ustring& title, Gtk::Widget& parent,
                         const Gdk::Rectangle& rect, const FuncVoid& show_next,
                         const FuncVoidUstring& handle_activate )
{
    if( m_Po_script )
    {
        m_Po_script->popdown();
        delete m_Po_script;
        m_Po_script = nullptr;
    }

    VecUstrings vec_scripts;
    PyBindings::update_scripts_in_entries( true ); // update dirty only
    PyBindings::get_scripts( ctg, vec_scripts );

    if( show_next &&
        ( ( vec_scripts.empty() && ctg != ScriptCategory::ENTRY_MODIFIER ) ||
          ( ctg == ScriptCategory::WORD_MODIFIER &&
            AppWindow::p->UI_entry->has_multipara_selection() ) ) )
    // NOTE: do not go beyond entries to prevent infinite loop
    {
        show_next();
        return false;
    }
    else
    {
        m_Po_script = new PoPicker< Ustring >( parent, title );
        m_Po_script->set_show_edit_button( true );

        m_Po_script->m_Sg_edit.connect(
                []( const Ustring& name ) { PyBindings::edit_script( name ); } );
        m_Po_script->m_Sg_show_next.connect( show_next );
        m_Po_script->m_Sg_item_activated.connect( handle_activate );

        for( auto label : vec_scripts )
            m_Po_script->add_item_to_map( label, label, "python-symbolic" );

        m_Po_script->popup( rect );
        return true;
    }
}

void
UIDiary::show_prev_session_elem()
{
    Entry* entry{ Diary::d->get_prev_session_entry() };
    if( entry )
        AppWindow::p->show_entry( entry );
    else
        print_info( "Previous sesion element cannot be found" );
}

void
UIDiary::show_home_elem()
{
    Entry* entry{ Diary::d->get_home_entry() };
    if( entry )
        AppWindow::p->show_entry( entry );
    else
        print_info( "No home entry defined" );
}

Entry*
UIDiary::update_for_added_entry( Entry* entry )
{
    if( ! Diary::d->is_in_edit_mode() )
        return nullptr;

    if( entry )
    {
        update_entry_list();

        if( entry->is_empty() )
            AppWindow::p->show_entry( entry );
        else
            AppWindow::p->UI_entry->show( entry->get_paragraph_1st() );
    }

    return entry;
}

Entry*
UIDiary::get_entry_for_action()
{
    // const Ustring source { Glib::VariantBase::cast_dynamic< VStr >( value ).get() };
    // PRINT_DEBUG( "parameter:", source );
    // return ( source == "MENU" ? m_CPo_entry.m_p2entry
    return( AppWindow::p->UI_entry->get_textview()->has_focus() ?
            AppWindow::p->UI_entry->get_cur_entry() :
            m_CPo_entry.m_p2entry );
}

Entry*
UIDiary::add_entry_dated( bool F_milestone )
{
    if( ! Diary::d->is_in_edit_mode() ) return nullptr;

    return update_for_added_entry(
            Diary::d->create_entry_dated( AppWindow::p->UI_entry->get_cur_entry(),
                                          AppWindow::p->UI_extra->get_selected_date(),
                                          F_milestone ) );

}
Entry*
UIDiary::add_entry_free()
{
    if( ! Diary::d->is_in_edit_mode() ) return nullptr;

    return update_for_added_entry( Diary::d->create_entry( nullptr,
                                                           true,
                                                           Date::get_today(),
                                                           "",
                                                           VT::ETS::NAME_ONLY::I ) );
}

void
UIDiary::merge_entries()
{
    auto&&    entries2merge   { get_selected_entries() };
    Entry*    p2entry_target  { AppWindow::p->UI_entry->get_cur_entry() };

    // clear the list to get rid of pointers to the entries to be deleted:
    m_WEL_main->set_model( Gtk::SingleSelection::create() );

    for( Entry* e2m : entries2merge )
    {
        if( e2m->get_id() == p2entry_target->get_id() ) continue;

        p2entry_target->append_entry_as_paras( e2m );
        AppWindow::p->handle_dismiss_entry( e2m );
        Diary::d->dismiss_entry( e2m );
    }

    AppWindow::p->UI_extra->refresh_calendar();
    AppWindow::p->UI_extra->refresh_table();
    update_entry_list();
    AppWindow::p->UI_entry->refresh();
}

void
UIDiary::add_parent_to_entries()
{
    auto&&  entries       { get_selected_entries() };
    if( entries.empty() ) return;
    // apparently we can trust that the selected entries are ordered properly:
    Entry*  p2entry_first { *entries.begin() };

    auto    entry_parent  { Diary::d->create_entry( p2entry_first->get_prev_or_up(),
                                                    !p2entry_first->get_prev(),
                                                    Date::get_today(),
                                                    "",
                                                    VT::ETS::INHERIT::I ) };

    Diary::d->move_entries( &entries, entry_parent, DropPosition::INTO );

    update_for_added_entry( entry_parent );
}

void
UIDiary::trash_entries( bool F_trash )
{
    if( Lifeograph::is_internal_operations_ongoing() )
        return;

    const auto  es            { AppWindow::get_entries_per_context() };
    bool        F_update_cal  { false };

    for( Entry* e : es )
    {
        e->set_trashed( F_trash );

        if( e->is_popup_note() )
            AppWindow::p->update_popup_note_editability( e->get_id_full() );

        if( Diary::d->update_entry_filter_status( e ) && !F_update_cal )
            F_update_cal = true;

        // when trashing, process non-registering entries, too, as they may have registered...
        // ...scripts by direct run:
        if( F_trash || e->registers_scripts_raw() )
            PyBindings::update_for_entry( e, e->registers_scripts() );

        refresh_row( e );
        if( AppWindow::p->UI_entry->is_cur_entry( e ) )
        {
            auto tv { AppWindow::p->UI_entry->get_textview() };
            tv->refresh_entry_icons();
            tv->set_editable( !F_trash );
        }
    }

    if( F_update_cal )
        AppWindow::p->UI_extra->refresh_calendar();
}

void
UIDiary::dismiss_entries()
{
    m_entries2dismiss = AppWindow::get_entries_per_context();
    m_F_dismiss_children = false;

    if( m_entries2dismiss.empty() && Diary::d->get_entries().size() < 2 )
        return; // do not delete the last entry

    // only show when a single entry is selected; otherwise things get too much complicated:
    if( m_entries2dismiss.size() == 1 && ( * m_entries2dismiss.begin() )->has_children() )
    {
        m_ChB_dismiss_children =
                Gtk::make_managed< Gtk::CheckButton >( _( "Also dismiss child entries" ) );
        m_ChB_dismiss_children->signal_toggled().connect(
                [ & ](){ m_F_dismiss_children = m_ChB_dismiss_children->get_active(); } );
    }
    else
    {
        m_ChB_dismiss_children = nullptr;
    }

    AppWindow::p->confirm_dismiss_element( ( * m_entries2dismiss.begin() )->get_name(),
                                           std::bind( &UIDiary::dismiss_entries2, this ),
                                           m_ChB_dismiss_children );
}
void
UIDiary::dismiss_entries2()
{
    Entry* entry_replacing{ nullptr };

    // clear the list to get rid of pointers to the entries to be deleted:
    // m_WEL_main->set_model( Gtk::SingleSelection::create() );

    for( Entry* e : m_entries2dismiss )
    {
        if( m_F_dismiss_children )
            for( Entry* c = e->get_child_1st(); c; )
            {
                entry_replacing = c->get_next_straight( e );
                m_WEL_main->remove_entry( c );
                AppWindow::p->handle_dismiss_entry( c );
                Diary::d->dismiss_entry( c );
                c = entry_replacing;
            }

        m_WEL_main->remove_entry( e );
        AppWindow::p->handle_dismiss_entry( e );
        entry_replacing = Diary::d->dismiss_entry( e );

        // if( e == m_p2entry )
        //     m_p2entry = nullptr;
    }

    AppWindow::p->UI_extra->refresh_calendar();
    AppWindow::p->UI_extra->refresh_table();
    // AppWindow::p->UI_diary->update_entry_list();
    AppWindow::p->show_entry( entry_replacing );

    m_entries2dismiss.clear();
}

void
UIDiary::empty_trash()
{
    DialogMessage::init( AppWindow::p, _( "Are You Sure You Want to Empty The Trash?" ) )
                ->add_button( _( "Empty The Trash" ),
                              std::bind( &UIDiary::empty_trash2, this ),
                              "destructive-action" )
                ->show();
}
void
UIDiary::empty_trash2()
{
    AppWindow::p->show_entry( Diary::d->get_entry_first_untrashed() );

    VecEntries trashed_entries;
    for( auto& kv_entry : Diary::d->get_entries() )
        if( kv_entry.second->is_trashed() )
            trashed_entries.push_back( kv_entry.second );

    for( auto&& entry : trashed_entries )
    {
        AppWindow::p->UI_entry->remove_entry_from_history( entry );
        AppWindow::p->UI_extra->remove_entry_from_search( entry );
        Diary::d->dismiss_entry( entry );
    }

    update_entry_list();
}

void
UIDiary::update_date_separators()
{
    for( Entry* entry : *Diary::d )
    {
        for( Paragraph* p = entry->get_paragraph_1st(); p; p = p->m_p2next )
        {
            for( auto f : p->m_formats )
            {
                if( f->type == VT::HFT_DATE )
                {
                    p->replace_text( f->pos_bgn, f->pos_end - f->pos_bgn,
                                     // reformats using the preferred separator:
                                     Date::format_string( f->ref_id ), // ref_id holds the date
                                     nullptr );
                }
            }
        }
    }

    AppWindow::p->UI_entry->refresh();
}

// DIALOGS
void
UIDiary::start_dialog_password()
{
    AppWindow::p->UI_entry->hide_popover();

    auto            gphrect { m_MB_diary->compute_bounds( *AppWindow::p ) };
    Gdk::Rectangle  rect    { int( gphrect->get_x() ),      int( gphrect->get_y() ),
                              int( gphrect->get_width() ),  int( gphrect->get_height() ) };
    DialogPassword::launch(
            Diary::d->is_encrypted() ? DialogPassword::OT_CHANGE : DialogPassword::OT_ADD,
            Diary::d, &rect, AppWindow::p,
            []{}, []{} );
}

void
UIDiary::start_dialog_sync()
{
    AppWindow::p->UI_entry->hide_popover();

    auto&& builder { Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/dlg_sync.ui" ) };
    m_D_sync = Gtk::Builder::get_widget_derived< DialogSync >( builder, "D_synchronize" );

    m_D_sync->set_transient_for( *AppWindow::p );
    m_D_sync->show();
}

void
UIDiary::start_dialog_import()
{
    AppWindow::p->UI_entry->hide_popover();

    auto&& builder { Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/dlg_import.ui" ) };
    m_D_import = Gtk::Builder::get_widget_derived< DialogImport >( builder, "W_import" );

    m_D_import->set_transient_for( *AppWindow::p );
    m_D_import->show();
}

void
UIDiary::start_dialog_export()
{
    AppWindow::p->UI_entry->hide_popover();

    Gdk::Rectangle rect;

    auto do_start = [ this ]()
    {
        DialogPassword::finish( Diary::d->get_uri() );

        auto&& builder { Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/dlg_export.ui" ) };
        m_D_export = Gtk::Builder::get_widget_derived< DialogExport >( builder, "W_export" );

        m_D_export->set_transient_for( *AppWindow::p );
        m_D_export->show();
    };

    if( Diary::d->is_encrypted() )
    {
        auto            gphrect { m_MB_diary->compute_bounds( *AppWindow::p ) };
        Gdk::Rectangle  rect    { int( gphrect->get_x() ),      int( gphrect->get_y() ),
                                  int( gphrect->get_width() ),  int( gphrect->get_height() ) };
        DialogPassword::launch( DialogPassword::OT_AUTHENTICATE, Diary::d,
                                &rect, AppWindow::p, do_start, []{} );
    }
    else
        do_start();
}

void
UIDiary::open_diary_folder() const
{
    auto file           { Gio::File::create_for_uri( Diary::d->m_uri ) };
    AppWindow::open_uri_os( Diary::d, file->get_parent()->get_uri() );
}

// DIARY PROPERTIES
void
UIDiary::handle_language_changed( String lang )
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;
    Diary::d->set_lang( lang == get_sstr( CSTR::OFF ) ? "" : lang );

    // not having language means diary default:
    if( !AppWindow::p->UI_entry->get_cur_entry()->has_property( PROP::LANGUAGE ) )
        AppWindow::p->UI_entry->get_textview()->update_for_spell_check_change();
}

void
UIDiary::handle_startup_type_changed( const D::DEID id )
{
    if( Lifeograph::is_internal_operations_ongoing() == false )
        Diary::d->m_startup_entry_id = id;

   refresh_startup_elem();
}

void
UIDiary::refresh_startup_elem()
{
    const bool F_show_fixed_item { Diary::d->m_startup_entry_id >= DEID::MIN_ACTUAL };

    m_Bx_startup_elem->set_visible( F_show_fixed_item );
    if( F_show_fixed_item )
        m_WTP_startup_entry->set_elem( Diary::d->get_startup_entry( false ) );
}

bool
UIDiary::go_to_startup_elem( const Ustring& )
{
    Entry* entry{ Diary::d->get_startup_entry() };
    if( entry )
        AppWindow::p->show_entry( entry );

    return true;
}
