// SPDX-License-Identifier: GPL-3.0-or-later

#include "itempinnedtests.h"

#include "tests/test_utils.h"

#include "common/commandstatus.h"

#include <QRegularExpression>

ItemPinnedTests::ItemPinnedTests(const TestInterfacePtr &test, QObject *parent)
    : QObject(parent)
    , m_test(test)
{
    setProperty("CopyQ_test_id", QStringLiteral("itempinned"));
}

void ItemPinnedTests::initTestCase()
{
    TEST(m_test->initTestCase());
}

void ItemPinnedTests::cleanupTestCase()
{
    TEST(m_test->cleanupTestCase());
}

void ItemPinnedTests::init()
{
    TEST(m_test->init());
}

void ItemPinnedTests::cleanup()
{
    TEST( m_test->cleanup() );
}

void ItemPinnedTests::isPinned()
{
    RUN("add" << "b" << "a", "");
    RUN("-e" << "plugins.itempinned.isPinned(0)", "false\n");
    RUN("-e" << "plugins.itempinned.isPinned(1)", "false\n");
}

void ItemPinnedTests::pin()
{
    RUN("add" << "b" << "a", "");
    RUN("-e" << "plugins.itempinned.pin(1)", "");
    RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");
    RUN("-e" << "plugins.itempinned.isPinned(0)", "false\n");

    RUN("-e" << "plugins.itempinned.pin(0)", "");
    RUN("-e" << "plugins.itempinned.isPinned(0)", "true\n");
    RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");
}

void ItemPinnedTests::pinMultiple()
{
    RUN("add" << "d" << "c" << "b" << "a", "");
    RUN("-e" << "plugins.itempinned.pin(1, 2)", "");
    RUN("-e" << "plugins.itempinned.isPinned(0)", "false\n");
    RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");
    RUN("-e" << "plugins.itempinned.isPinned(2)", "true\n");
    RUN("-e" << "plugins.itempinned.isPinned(3)", "false\n");

    RUN("-e" << "plugins.itempinned.pin(1, 2 ,3)", "");
    RUN("-e" << "plugins.itempinned.isPinned(0)", "false\n");
    RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");
    RUN("-e" << "plugins.itempinned.isPinned(2)", "true\n");
    RUN("-e" << "plugins.itempinned.isPinned(3)", "true\n");
}

void ItemPinnedTests::unpin()
{
    RUN("add" << "b" << "a", "");
    RUN("-e" << "plugins.itempinned.pin(0, 1)", "");
    RUN("-e" << "plugins.itempinned.isPinned(0)", "true\n");
    RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");

    RUN("-e" << "plugins.itempinned.unpin(0)", "");
    RUN("-e" << "plugins.itempinned.isPinned(0)", "false\n");
    RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");

    RUN("-e" << "plugins.itempinned.unpin(1)", "");
    RUN("-e" << "plugins.itempinned.isPinned(0)", "false\n");
    RUN("-e" << "plugins.itempinned.isPinned(1)", "false\n");
}

void ItemPinnedTests::unpinMultiple()
{
    RUN("add" << "d" << "c" << "b" << "a", "");
    RUN("-e" << "plugins.itempinned.pin(0, 1, 2, 3)", "");
    RUN("-e" << "plugins.itempinned.isPinned(0)", "true\n");
    RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");
    RUN("-e" << "plugins.itempinned.isPinned(2)", "true\n");
    RUN("-e" << "plugins.itempinned.isPinned(3)", "true\n");

    RUN("-e" << "plugins.itempinned.unpin(1, 2)", "");
    RUN("-e" << "plugins.itempinned.isPinned(0)", "true\n");
    RUN("-e" << "plugins.itempinned.isPinned(1)", "false\n");
    RUN("-e" << "plugins.itempinned.isPinned(2)", "false\n");
    RUN("-e" << "plugins.itempinned.isPinned(3)", "true\n");

    RUN("-e" << "plugins.itempinned.unpin(2, 3)", "");
    RUN("-e" << "plugins.itempinned.isPinned(0)", "true\n");
    RUN("-e" << "plugins.itempinned.isPinned(1)", "false\n");
    RUN("-e" << "plugins.itempinned.isPinned(2)", "false\n");
    RUN("-e" << "plugins.itempinned.isPinned(3)", "false\n");
}

void ItemPinnedTests::removePinnedThrows()
{
    RUN("add" << "b" << "a", "");
    RUN("-e" << "plugins.itempinned.pin(0, 1)", "");

    RUN_EXPECT_ERROR("remove" << "0" << "1", CommandException);
    RUN("separator" << " " << "read" << "0" << "1", "a b");
}

void ItemPinnedTests::pinToRow()
{
    const auto read = Args() << "separator" << " " << "read";

    RUN("add" << "a", "");
    RUN("-e" << "plugins.itempinned.pin(0)", "");
    RUN("add" << "b", "");
    RUN(read << "0" << "1", "a b");

    RUN("add" << "c", "");
    RUN(read << "0" << "1" << "2", "a c b");

    RUN("-e" << "plugins.itempinned.pin(1)", "");
    RUN("add" << "d", "");
    RUN(read << "0" << "1" << "2" << "3", "a c d b");

    RUN("-e" << "plugins.itempinned.pin(2)", "");
    RUN("-e" << "plugins.itempinned.unpin(1); remove(1)", "");
    RUN(read << "0" << "1" << "2", "a b d");
}

void ItemPinnedTests::fullTab()
{
    RUN("config" << "maxitems" << "3", "3\n");
    RUN("add" << "c" << "b" << "a", "");
    RUN("-e" << "plugins.itempinned.pin(0,1,2)", "");

    // Tab is full and no items can be removed.
    m_test->ignoreErrors(
        QRegularExpression(R"(Cannot add new items\. Tab "CLIPBOARD" reached the maximum number of items\.)") );
    RUN_EXPECT_ERROR("add" << "X", CommandException);
    RUN_EXPECT_ERROR("write" << "1" << "text/plain" << "X", CommandException);
    RUN("separator" << " " << "read" << "0" << "1" << "2", "a b c");
    RUN("size", "3\n");
}

void ItemPinnedTests::keepPinnedIfMaxItemsChanges()
{
    const auto read = Args() << "separator" << " " << "read";

    RUN("config" << "maxitems" << "3", "3\n");
    RUN("add" << "pin2" << "no-pin" << "pin1", "");
    RUN("-e" << "plugins.itempinned.pin(0,2)", "");

    RUN("config" << "maxitems" << "2", "2\n");
    RUN(read << "0" << "1" << "2", "pin1 pin2 ");

    RUN("config" << "maxitems" << "1", "1\n");
    RUN(read << "0" << "1" << "2", "pin1 pin2 ");
}

void ItemPinnedTests::noDuplicateWhenAllPinned()
{
    const auto read = Args() << "separator" << " " << "read";

    RUN("config" << "move" << "true", "true\n");

    // Add items and pin them all.
    RUN("add" << "c" << "b" << "a", "");
    RUN("-e" << "plugins.itempinned.pin(0,1,2)", "");
    RUN(read << "0" << "1" << "2", "a b c");
    RUN("size", "3\n");

    // Activate second pinned item by pressing ENTER.
    // This sets the clipboard but must not move or duplicate the item.
    RUN("show", "");
    KEYS(clipboardBrowserId << "DOWN" << "ENTER");
    WAIT_FOR_CLIPBOARD("b");

    // Wait for provideClipboard to take clipboard ownership before
    // setting clipboard externally — otherwise provideClipboard may
    // overwrite the external clipboard data.
    WAIT_ON_OUTPUT("str(clipboard(mimeOwner)).includes('provideClipboard')", "true\n");

    // Use a sentinel clipboard change to ensure all prior
    // async clipboard processing (provideClipboard exit) completed.
    TEST(m_test->setClipboard("d"));
    WAIT_FOR_CLIPBOARD("d");
    WAIT_ON_OUTPUT("read" << "0" << "1" << "2" << "3", "a\nb\nc\nd");

    // Pinned items must stay in place; only the sentinel "d" was added.
    RUN("size", "4\n");
    RUN(read << "0" << "1" << "2" << "3", "a b c d");

    // Set clipboard to same text as a pinned item externally.
    // This triggers addUnique() which must detect the duplicate
    // even though the pinned item's data contains mimePinned
    // but the clipboard data does not.
    TEST(m_test->setClipboard("a"));
    WAIT_FOR_CLIPBOARD("a");

    // Another sentinel to confirm the above was fully processed.
    TEST(m_test->setClipboard("e"));
    WAIT_FOR_CLIPBOARD("e");
    WAIT_ON_OUTPUT("read" << "0" << "1" << "2" << "3" << "4", "a\nb\nc\ne\nd");

    // Verify: 5 items total (a, b, c pinned + e, d new), no duplicates.
    // "e" appears before "d" because new unpinned items are inserted
    // at the top and shifted past pinned items.
    RUN("size", "5\n");
    RUN(read << "0" << "1" << "2" << "3" << "4", "a b c e d");
}
