/*
 * SPDX-FileCopyrightText: 2019-2026 Mattia Basaglia <dev@dragon.best>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#include "compound_timeline_widget.hpp"
#include "glaxnimate/command/clipboard.hpp"
#include "ui_compound_timeline_widget.h"

#include <QMenu>
#include <QScrollBar>
#include <QClipboard>
#include <QMimeData>
#include <QSignalBlocker>
#include <QActionGroup>

#include "glaxnimate/command/animation_commands.hpp"

#include "item_models/property_model_full.hpp"
#include "item_models/comp_filter_model.hpp"

#include "style/property_delegate.hpp"
#include "style/fixed_height_delegate.hpp"
#include "widgets/dialogs/keyframe_editor_dialog.hpp"

#include "widgets/menus/node_menu.hpp"
#include "widgets/menus/animated_property_menu.hpp"
#include "widgets/timeline/keyframe_transition_data.hpp"
#include "widgets/timeline/value_drag_event_filter.hpp"

using namespace glaxnimate::gui;
using namespace glaxnimate;

class CompoundTimelineWidget::Private
{
public:
    void setupUi(CompoundTimelineWidget* parent)
    {
        ui.setupUi(parent);
        comp_model.setSourceModel(&property_model);

        QPalette prop_pal = ui.properties->palette();
        prop_pal.setBrush(
            QPalette::Inactive,
            QPalette::Highlight,
            prop_pal.brush(QPalette::Active, QPalette::Highlight)
        );
        prop_pal.setBrush(
            QPalette::Inactive,
            QPalette::HighlightedText,
            prop_pal.brush(QPalette::Active, QPalette::HighlightedText)
        );
        ui.properties->setPalette(prop_pal);

        ui.properties->setModel(&comp_model);
        new ValueDragEventFilter(ui.properties, item_models::PropertyModelFull::ColumnValue);
        ui.timeline->set_model(&comp_model, &property_model, ui.properties);
        connect(&property_model, &QAbstractItemModel::dataChanged,
                ui.properties->viewport(), (void (QWidget::*)())&QWidget::update);

        ui.properties->setItemDelegate(&fixed_height_delegate);
        ui.properties->setItemDelegateForColumn(item_models::PropertyModelFull::ColumnValue, &property_delegate);
        ui.properties->setItemDelegateForColumn(item_models::PropertyModelFull::ColumnColor, &color_delegate);

        ui.properties->header()->setSectionResizeMode(item_models::PropertyModelFull::ColumnName, QHeaderView::ResizeToContents);
        ui.properties->header()->setSectionResizeMode(item_models::PropertyModelFull::ColumnValue, QHeaderView::Stretch);
        ui.properties->header()->setSectionResizeMode(item_models::PropertyModelFull::ColumnColor, QHeaderView::ResizeToContents);
        ui.properties->header()->setSectionResizeMode(item_models::PropertyModelFull::ColumnLocked, QHeaderView::ResizeToContents);
        ui.properties->header()->setSectionResizeMode(item_models::PropertyModelFull::ColumnVisible, QHeaderView::ResizeToContents);
        ui.properties->header()->setSectionResizeMode(item_models::PropertyModelFull::ColumnMask, QHeaderView::ResizeToContents);
        ui.properties->header()->moveSection(item_models::PropertyModelFull::ColumnColor, 0);
        ui.properties->header()->moveSection(item_models::PropertyModelFull::ColumnVisible, 1);
        ui.properties->header()->moveSection(item_models::PropertyModelFull::ColumnLocked, 2);
        ui.properties->header()->moveSection(item_models::PropertyModelFull::ColumnMask, 3);

        ui.properties->setUniformRowHeights(true);
        ui.properties->header()->setFixedHeight(ui.timeline->header_height());
        property_delegate.set_forced_height(ui.timeline->header_height());
        fixed_height_delegate.set_height(ui.timeline->row_height());
        ui.properties->setRootIsDecorated(true);

        ui.properties->verticalScrollBar()->installEventFilter(parent);
        ui.properties->verticalScrollBar()->setSingleStep(ui.scrollbar->singleStep());
        ui.properties->verticalScrollBar()->setPageStep(ui.scrollbar->pageStep());
        connect(ui.properties->verticalScrollBar(), &QScrollBar::valueChanged, ui.scrollbar, &QScrollBar::setValue);
        connect(ui.scrollbar, &QScrollBar::valueChanged, ui.properties->verticalScrollBar(), &QScrollBar::setValue);
        connect(ui.properties->verticalScrollBar(), &QScrollBar::rangeChanged, ui.scrollbar, &QScrollBar::setRange);
        connect(ui.scrollbar, &QScrollBar::valueChanged, parent, &CompoundTimelineWidget::on_scroll);

        connect(ui.timeline, &TimelineWidget::line_clicked, parent, &CompoundTimelineWidget::select_line);
        connect(ui.properties->selectionModel(), &QItemSelectionModel::currentChanged, parent, &CompoundTimelineWidget::select_index);
        connect(ui.properties->selectionModel(), &QItemSelectionModel::selectionChanged, parent, &CompoundTimelineWidget::_on_selection_changed);

        ui.action_add_keyframe->setIcon(QIcon::fromTheme("keyframe-add"));
        connect(ui.action_add_keyframe, &QAction::triggered, parent, &CompoundTimelineWidget::add_keyframe);

        connect(parent, &QWidget::customContextMenuRequested, parent, &CompoundTimelineWidget::custom_context_menu);

        setup_menu(parent);

    }

    void setup_menu(CompoundTimelineWidget* parent)
    {
        for ( int i = 0; i < KeyframeTransitionData::count; i++ )
        {
            auto data = KeyframeTransitionData::from_index(i, KeyframeTransitionData::Full);
            actions_enter[i].setIcon(data.icon());
            actions_enter[i].setActionGroup(&enter);
            actions_enter[i].setData(data.variant());

            actions_leave[i].setIcon(data.icon());
            actions_leave[i].setActionGroup(&exit);
            actions_leave[i].setData(data.variant());

            if ( i == model::KeyframeTransition::Custom )
            {
                actions_enter[i].setText(i18n("Custom..."));
                actions_leave[i].setText(actions_enter[i].text());
            }
            else if ( i == model::KeyframeTransition::NoValue )
            {
                actions_enter[i].setVisible(false);
                actions_leave[i].setVisible(false);
            }
            else
            {
                actions_leave[i].setText(KeyframeTransitionData::from_index(i, KeyframeTransitionData::Start).name);
                actions_enter[i].setText(KeyframeTransitionData::from_index(i, KeyframeTransitionData::Finish).name);
            }
        }

        menu_keyframe.addAction(&action_kf_remove);
        action_kf_remove.setIcon(QIcon::fromTheme("keyframe-remove"));
        connect(&action_kf_remove, &QAction::triggered, parent, &CompoundTimelineWidget::remove_keyframe);


        menu_keyframe.addAction(&action_kf_copy);
        action_kf_copy.setIcon(QIcon::fromTheme("keyframe-duplicate"));
        connect(&action_kf_copy, &QAction::triggered, parent, &CompoundTimelineWidget::copy_keyframe);

        action_kf_paste.setIcon(QIcon::fromTheme("edit-paste"));
        menu_keyframe.addAction(&action_kf_paste);
        connect(&action_kf_paste, &QAction::triggered, parent, &CompoundTimelineWidget::paste_keyframes);

        menu_keyframe.addSeparator();

        menu_keyframe.addAction(&action_kf_remove_all);
        action_kf_remove_all.setIcon(QIcon::fromTheme("edit-clear-all"));
        connect(&action_kf_remove_all, &QAction::triggered, &menu_property, &AnimatedPropertyMenu::remove_all_keyframes);

        action_enter = menu_keyframe.addSeparator();
        for ( QAction* ac : enter.actions() )
        {
            ac->setCheckable(true);
            menu_keyframe.addAction(ac);
            connect(ac, &QAction::triggered, parent, &CompoundTimelineWidget::keyframe_action_enter);
        }

        action_exit = menu_keyframe.addSeparator();
        for ( QAction* ac : exit.actions() )
        {
            ac->setCheckable(true);
            menu_keyframe.addAction(ac);
            connect(ac, &QAction::triggered, parent, &CompoundTimelineWidget::keyframe_action_exit);
        }

        action_enter->setText(i18n("Transition From Previous"));
        action_exit->setText(i18n("Transition To Next"));

        action_kf_remove.setText(i18n("Remove Keyframe"));
        action_kf_remove_all.setText(i18n("Clear Animations"));

        action_kf_copy.setText(i18n("Copy Keyframe"));
        action_kf_paste.setText(i18n("Paste Keyframe"));
    }

    void clear_menu_data()
    {
        menu_property.set_property(nullptr);
        menu_kf_enter = nullptr;
        menu_kf_exit = nullptr;
    }

    void keyframe_action(CompoundTimelineWidget* parent, model::KeyframeBase* keyframe, bool before_transition, QAction* action)
    {

        if ( !keyframe || !menu_property.property() )
            return;

        QVariant data = action->data();
        if ( data.isValid() && data.toInt() != model::KeyframeTransition::Custom )
        {
            property_model.document()->push_command(
                menu_property.property()->command_set_transition_side(
                    keyframe->time(), data.value<model::KeyframeTransition::Descriptive>(),
                    before_transition ? keyframe->transition().before() : keyframe->transition().after(),
                    before_transition
                )
            );
        }
        else
        {
            KeyframeEditorDialog keyframe_editor(keyframe->transition(), parent);
            keyframe_editor.setWindowModality(Qt::ApplicationModal);
            if ( keyframe_editor.exec() )
            {
                property_model.document()->push_command(
                    menu_property.property()->command_set_transition(
                        keyframe->time(),
                        keyframe_editor.transition()
                    )
                );
            }
        }
    }

    void toggle_paste()
    {
        action_kf_paste.setEnabled(menu_property.can_paste());
    }

    void emit_click(CompoundTimelineWidget* parent, QModelIndex index)
    {
        model::VisualNode* node = nullptr;
        do
        {
            node = property_model.visual_node(comp_model.mapToSource(index));
            index = index.parent();
        }
        while ( !node && index.isValid() );

        if ( node )
            Q_EMIT parent->current_node_changed(node);
    }

    model::VisualNode* index_node_or_parent(QModelIndex property_index)
    {
        while ( property_index.isValid() )
        {
            auto item = property_model.item(property_index);
            model::Object* obj = item.object;
            if ( !obj && item.property )
            {
                obj = item.property->object();
                if ( !obj )
                    return nullptr;
            }

            if ( auto visual = obj->cast<model::VisualNode>() )
                return visual;

            if ( obj->is_instance<model::Asset>() )
                return nullptr;

            property_index = property_index.parent();
        }

        return nullptr;
    }

    Ui_CompoundTimelineWidget ui;
    item_models::PropertyModelFull property_model;
    style::PropertyDelegate property_delegate;
    color_widgets::ColorDelegate color_delegate;
    style::FixedHeightDelegate fixed_height_delegate;
    item_models::CompFilterModel comp_model;

    AnimatedPropertyMenu menu_property;

    QAction* action_enter;
    QAction* action_exit;
    QAction action_kf_copy;
    QAction action_kf_paste;
    QAction action_kf_remove;
    QAction action_kf_remove_all;

    std::array<QAction, KeyframeTransitionData::count> actions_enter;
    std::array<QAction, KeyframeTransitionData::count> actions_leave;

    QMenu menu_keyframe;
    QActionGroup enter{&menu_keyframe};
    QActionGroup exit{&menu_keyframe};

    model::KeyframeBase* menu_kf_enter = nullptr;
    model::KeyframeBase* menu_kf_exit = nullptr;

    SelectionManager* window = nullptr;
};

CompoundTimelineWidget::CompoundTimelineWidget(QWidget* parent)
    : QWidget(parent), d(std::make_unique<Private>())
{
    d->setupUi(this);
    connect(d->ui.tab_bar, &CompositionTabBar::switch_composition, this, &CompoundTimelineWidget::switch_composition);
    connect(&d->property_model, &QAbstractItemModel::rowsRemoved, this, &CompoundTimelineWidget::rows_removed);
}


CompoundTimelineWidget::~CompoundTimelineWidget() = default;

void CompoundTimelineWidget::set_composition(model::Composition* comp)
{
    QSignalBlocker g(d->ui.tab_bar);
    d->ui.tab_bar->set_current_composition(comp);
    d->comp_model.set_composition(comp);

    on_scroll(d->ui.scrollbar->value());
}

void CompoundTimelineWidget::set_current_node(model::DocumentNode* node)
{
    QModelIndex index = d->comp_model.mapFromSource(d->property_model.object_index(node));
    d->ui.properties->expand(index);
    d->ui.properties->setCurrentIndex(index);
    d->clear_menu_data();
}

void CompoundTimelineWidget::set_document(model::Document* document)
{
    d->ui.timeline->set_document(document);
    d->property_model.set_document(document);
    d->clear_menu_data();
    d->ui.tab_bar->set_document(document);
    d->comp_model.set_composition(nullptr);
}

void CompoundTimelineWidget::clear_document()
{
    set_document(nullptr);
}

void CompoundTimelineWidget::select_index(const QModelIndex& index)
{
//     d->ui.timeline->select(index);
//     d->ui.properties->viewport()->update();
    d->emit_click(this, index);
}

void CompoundTimelineWidget::select_line(quintptr id, bool selected, bool replace_selection)
{
    QModelIndex index = d->comp_model.mapFromSource(d->property_model.index_by_id(id));
    if ( selected )
    {
        d->ui.properties->selectionModel()->setCurrentIndex(
            index,
            QItemSelectionModel::Rows | QItemSelectionModel::Select |
            ( replace_selection ? QItemSelectionModel::Clear : QItemSelectionModel::NoUpdate )
        );
    }
    else
    {
        d->ui.properties->selectionModel()->select(
            index,
            QItemSelectionModel::Rows | QItemSelectionModel::Deselect
        );
    }
    d->ui.properties->viewport()->update();
    d->emit_click(this, index);
}

void CompoundTimelineWidget::custom_context_menu(const QPoint& p)
{
    d->clear_menu_data();

    d->ui.timeline->keep_highlight();

    QPoint glob = static_cast<QWidget*>(sender())->mapToGlobal(p);

    item_models::PropertyModelFull::Item item;
    if ( d->ui.properties->rect().contains(p) )
    {
        item = d->property_model.item(d->comp_model.mapToSource(
            d->ui.properties->indexAt(
                d->ui.properties->viewport()->mapFromGlobal(glob)
            )
        ));

        if ( item.property && item.property->traits().flags & model::PropertyTraits::Animated )
            d->menu_property.set_property(static_cast<model::AnimatedPropertyBase*>(item.property));
    }
    else
    {
        std::tie(d->menu_kf_enter, d->menu_kf_exit) = d->ui.timeline->keyframe_at(
            d->ui.timeline->viewport()->mapFromGlobal(glob)
        );

        item = d->ui.timeline->item_at(
            d->ui.timeline->viewport()->mapFromGlobal(glob)
        );


        if ( item.property && !(item.property->traits().flags & model::PropertyTraits::List) )
        {
            if ( item.property->traits().flags & model::PropertyTraits::Animated )
                d->menu_property.set_property(static_cast<model::AnimatedPropertyBase*>(item.property));
            else if ( item.property->traits().type == model::PropertyTraits::Object )
                d->menu_property.set_object_group(static_cast<model::SubObjectPropertyBase*>(item.property)->sub_object());
            else if ( item.property->traits().type == model::PropertyTraits::ObjectReference )
                d->menu_property.set_object_group(static_cast<model::ReferencePropertyBase*>(item.property)->get_ref());
        }
        else if ( item.object )
        {
            d->menu_property.set_object_group(item.object);
        }
    }


    if ( d->menu_kf_exit )
    {
        d->enter.setEnabled(d->menu_kf_enter);
        if ( d->menu_kf_enter )
        {
            d->actions_enter[d->menu_kf_enter->transition().after_descriptive()].setChecked(true);
        }

        d->actions_leave[d->menu_kf_exit->transition().before_descriptive()].setChecked(true);

        d->toggle_paste();
        d->menu_keyframe.exec(glob);
    }
    else if ( d->menu_property.property() )
    {
        d->menu_property.refresh_actions();
        d->menu_property.exec(glob);
    }
    else if ( item.property && !(item.property->traits().flags & model::PropertyTraits::List) )
    {
        if ( item.property->traits().type == model::PropertyTraits::Object )
        {
            auto op = static_cast<model::SubObjectPropertyBase*>(item.property);
            NodeMenu(op->sub_object(), d->window, this, item.property->object()).exec(QCursor::pos());
        }
        else if ( item.property->traits().type ==
            model::PropertyTraits::ObjectReference )
        {
            auto op = static_cast<model::ReferencePropertyBase*>(item.property);
            if ( auto ref = op->get_ref() )
                NodeMenu(ref, d->window, this, item.property->object()).exec(QCursor::pos());
        }
    }
    else if ( item.object )
    {
        NodeMenu(item.object, d->window, this).exec(glob);
    }
}

void CompoundTimelineWidget::add_keyframe()
{
    if ( !d->menu_property.property() )
        return;

    d->property_model.document()->push_command(
        d->menu_property.property()->command_add_smooth_keyframe(
            d->ui.timeline->highlighted_time(), d->menu_property.property()->static_value()
        )
    );
}

void CompoundTimelineWidget::on_scroll(int amount)
{
    int scroll = -d->ui.timeline->header_height() + amount * d->ui.timeline->row_height();
    d->ui.timeline->verticalScrollBar()->setValue(scroll);
}

void CompoundTimelineWidget::keyframe_action_enter()
{
    d->keyframe_action(this, d->menu_kf_enter, false, static_cast<QAction*>(sender()));
}

void CompoundTimelineWidget::keyframe_action_exit()
{
    d->keyframe_action(this, d->menu_kf_exit, true, static_cast<QAction*>(sender()));
}

void CompoundTimelineWidget::remove_keyframe()
{
    if ( !d->menu_kf_exit || !d->menu_property.property() )
        return;

    d->property_model.document()->undo_stack().push(
        d->menu_property.property()->command_remove_keyframe(d->menu_kf_exit->time())
    );
}

void CompoundTimelineWidget::load_state(const QByteArray& state)
{
    d->ui.splitter->restoreState(state);
}

QByteArray CompoundTimelineWidget::save_state() const
{
    return d->ui.splitter->saveState();
}

void CompoundTimelineWidget::set_controller(SelectionManager* window)
{
    d->window = window;
    d->menu_property.set_controller(window);
}

void CompoundTimelineWidget::copy_keyframe()
{
    if ( !d->menu_kf_exit || !d->menu_property.property() )
        return;

    QMimeData* data = command::keyframe_to_mime_data(
        d->menu_property.property_type(),
        d->menu_kf_exit->value()
    );
    QGuiApplication::clipboard()->setMimeData(data);
}

void CompoundTimelineWidget::copy_keyframes() const
{
    auto selection = d->ui.timeline->selected_keyframes();
    if ( selection.empty() )
        return;

    command::ClipboardProperties clip_data;
    std::map<int, command::ClipboardProperty*> props;
    for ( const auto& kf : selection )
    {
        int type = kf.property->property_type();
        command::ClipboardProperty*& prop = props[type];
        if ( !prop )
        {
            clip_data.properties.push_back({type});
            prop = &clip_data.properties.back();
        }
        prop->keyframes.insert(kf.time, {kf.time, kf.property->value_at_time(kf.time)});
    }

    auto data = command::keyframes_to_mime_data(clip_data);
    QGuiApplication::clipboard()->setMimeData(data);
}

void CompoundTimelineWidget::paste_keyframes()
{
    int property_type;
    model::AnimatableBase* target_property = nullptr;
    model::FrameTime start_time;

    if ( d->menu_property.property() )
    {
        start_time = d->menu_kf_exit ? d->menu_kf_exit->time() : d->menu_property.object()->time();
        target_property = d->menu_property.property();
        property_type = d->menu_property.property_type();
    }
    else
    {
        auto item = d->property_model.item(current_index_raw());
        if ( item.animatable )
        {
            target_property = item.animatable;
            property_type = item.animatable->property_type();
            start_time = item.property ? item.property->object()->time() : item.object->time();
        }
    }

    if ( !target_property )
        return;

    const QMimeData* data = QGuiApplication::clipboard()->mimeData();
    auto cmd = command::keyframes_paste_command(data, target_property, property_type, start_time);
    if ( cmd )
        d->property_model.document()->push_command(cmd);
}

void CompoundTimelineWidget::collapse_index(const QModelIndex& index)
{
    d->ui.timeline->collapse(index);
}

void CompoundTimelineWidget::expand_index(const QModelIndex& index)
{
    d->ui.timeline->expand(index);
}


void CompoundTimelineWidget::click_index ( const QModelIndex& index )
{
    auto source_index = d->comp_model.mapToSource(index);
    if ( auto node = d->property_model.visual_node(source_index) )
    {
        if ( index.column() == item_models::PropertyModelFull::ColumnVisible )
            node->visible.set_undoable(!node->visible.get());
        else if ( index.column() == item_models::PropertyModelFull::ColumnLocked )
            node->locked.set_undoable(!node->locked.get());
        else if ( index.column() == item_models::PropertyModelFull::ColumnMask )
        {
            if ( auto layer = node->cast<model::Layer>() )
            {
                int mode = model::MaskSettings::next_mode(layer->mask->mask.get());
                layer->mask->mask.set_undoable(mode);
            }
        }
    }
    else if ( auto anprop = d->property_model.animatable(source_index) )
    {
        if ( index.column() == item_models::PropertyModelFull::ColumnToggleKeyframe )
        {
            auto time = d->property_model.document()->current_time();
            if ( anprop->has_keyframe(time) )
            {
                d->property_model.document()->push_command(anprop->command_remove_keyframe(time));
            }
            else
            {
                d->property_model.document()->push_command(anprop->command_add_smooth_keyframe(time, anprop->static_value()));
            }
        }
        else if ( index.column() == item_models::PropertyModelFull::ColumnPrevKeyframe )
        {
            if ( anprop->keyframe_count() < 2 )
                return;

            auto time = d->property_model.document()->current_time();
            auto kf = anprop->find(time);

            if ( qFuzzyCompare(kf->time(), time) || kf->time() > time )
            {
                auto range = anprop->keyframe_range();
                if ( kf == range.begin() )
                    kf = range.end();
                --kf;
            }

            d->property_model.document()->set_current_time(kf->time());
        }
        else if ( index.column() == item_models::PropertyModelFull::ColumnNextKeyframe )
        {
            if ( anprop->keyframe_count() < 2 )
                return;

            auto time = d->property_model.document()->current_time();
            auto kf = anprop->find(time);

            if ( qFuzzyCompare(kf->time(), time) || kf->time() < time )
            {
                kf++;
            }

            auto range = anprop->keyframe_range();
            if ( kf == range.end() )
                kf = range.begin();

            d->property_model.document()->set_current_time(kf->time());
        }
    }
}

void CompoundTimelineWidget::_on_selection_changed(const QItemSelection &selected, const QItemSelection &deselected)
{
    std::vector<model::VisualNode*> selected_nodes;
    std::vector<model::VisualNode*> deselected_nodes;

    for ( const auto& index : selected.indexes() )
    {
        if ( auto node = d->index_node_or_parent(d->comp_model.mapToSource(index)) )
            selected_nodes.push_back(node);
    }

    for ( const auto& index : deselected.indexes() )
    {
        if ( auto node = d->index_node_or_parent(d->comp_model.mapToSource(index)) )
            deselected_nodes.push_back(node);
    }

    d->ui.timeline->select(selected, deselected);

    Q_EMIT selection_changed(selected_nodes, deselected_nodes);
}

QAbstractItemModel * CompoundTimelineWidget::raw_model() const
{
    return &d->property_model;
}

QAbstractItemModel * CompoundTimelineWidget::filtered_model() const
{
    return &d->comp_model;
}

TimelineWidget * CompoundTimelineWidget::timeline() const
{
    return d->ui.timeline;
}

void CompoundTimelineWidget::rows_removed(const QModelIndex& index, int first, int last)
{
    if ( d->property_model.document() )
    {
        for ( int i = first; i <= last; i++ )
        {
            auto id = d->property_model.index(i, 0, index).internalId();
            if ( id == d->comp_model.get_root_id() )
            {
                set_composition(d->comp_model.composition());
                return;
            }
        }
    }
}

bool CompoundTimelineWidget::eventFilter(QObject*, QEvent* event)
{
    // For some reason scrolling on the tree view doesn't respect step size
    if ( event->type() == QEvent::Wheel )
    {
        QApplication::sendEvent(d->ui.scrollbar, event);
        return true;
    }

    return false;
}

void CompoundTimelineWidget::reset_view()
{
    d->ui.timeline->reset_view();
}

void CompoundTimelineWidget::select(const std::vector<model::VisualNode *>& selected, const std::vector<model::VisualNode *>& deselected)
{
    QItemSelection selected_indices;
    QItemSelection deselected_indices;

    for ( const auto& node : selected )
    {
        auto index = d->comp_model.mapFromSource(d->property_model.node_index(node));
        if ( index.isValid() )
            selected_indices.push_back(QItemSelectionRange(index));
    }

    for ( const auto& node : deselected )
    {
        auto index = d->comp_model.mapFromSource(d->property_model.node_index(node));
        if ( index.isValid() )
            deselected_indices.push_back(QItemSelectionRange(index));
    }

    d->ui.timeline->select(selected_indices, deselected_indices);


    d->ui.properties->selectionModel()->select(
        selected_indices,
        QItemSelectionModel::Rows | QItemSelectionModel::Select
    );

    d->ui.properties->selectionModel()->select(
        deselected_indices,
        QItemSelectionModel::Rows | QItemSelectionModel::Deselect
    );
}

model::DocumentNode * glaxnimate::gui::CompoundTimelineWidget::current_node() const
{
    return d->index_node_or_parent(d->comp_model.mapToSource(d->ui.properties->currentIndex()));
}

QModelIndex glaxnimate::gui::CompoundTimelineWidget::current_index_raw() const
{
    return d->comp_model.mapToSource(d->ui.properties->currentIndex());
}

QModelIndex glaxnimate::gui::CompoundTimelineWidget::current_index_filtered() const
{
    return d->ui.properties->currentIndex();
}

item_models::PropertyModelBase::Item CompoundTimelineWidget::item_at(const QPoint &glob) const
{
    QPoint pos = mapFromGlobal(glob);
    if ( !rect().contains(pos) )
        return {};

    if ( d->ui.properties->rect().contains(pos) )
    {
        return d->property_model.item(d->comp_model.mapToSource(
            d->ui.properties->indexAt(
                d->ui.properties->viewport()->mapFromGlobal(glob)
                )
            ));
    }
    else
    {
        return d->ui.timeline->item_at(
            d->ui.timeline->viewport()->mapFromGlobal(glob)
        );
    }
}
