package com.atlassian.webdriver.bitbucket.element;

import com.atlassian.pageobjects.PageBinder;
import com.atlassian.pageobjects.elements.ElementBy;
import com.atlassian.pageobjects.elements.PageElement;
import com.atlassian.pageobjects.elements.query.Queries;
import com.atlassian.pageobjects.elements.query.TimedCondition;
import com.atlassian.pageobjects.elements.query.TimedQuery;
import com.atlassian.pageobjects.elements.timeout.Timeouts;
import com.atlassian.bitbucket.util.Chainable;
import com.atlassian.webdriver.bitbucket.page.BaseRepositoryPage;
import com.atlassian.webdriver.bitbucket.page.PublicRepositoryListPage;
import com.google.common.base.Supplier;
import org.openqa.selenium.By;

import javax.annotation.Nonnull;
import javax.inject.Inject;

import static com.atlassian.pageobjects.elements.query.Conditions.and;
import static com.atlassian.pageobjects.elements.query.Conditions.not;
import static com.atlassian.pageobjects.elements.query.Poller.waitUntilTrue;
import static com.google.common.base.Preconditions.checkNotNull;

public class RecentReposDropdown {

    @Inject
    protected PageBinder pageBinder;

    @Inject
    protected Timeouts timeouts;

    // there's only one recent repos dropdown in stash, so we can control the locators here
    @ElementBy(id  = "com.atlassian.bitbucket.server.bitbucket-server-web-fragments-repositories-menu")
    protected PageElement container;

    @ElementBy(id  = "repositories-menu-trigger")
    protected PageElement trigger;

    @Nonnull
    public TimedQuery<Boolean> hasRecentRepositories() {
        return not(getNoRecentReposIndicator().timed().isPresent());
    }

    @Nonnull
    @SuppressWarnings("unchecked")
    public TimedCondition isOpen() {
        return and(
                container.timed().isVisible(),
                not(getLoadingRecentRepositoriesIndicator().timed().isVisible())
        );
    }

    /**
     * Note: the drop-down must be closed for this method to work, as it validates the outcome of the action.
     * If your intent is to close the drop-down, use {@link #toggle()} instead.
     *
     * @return this drop-down
     */
    @Nonnull
    public RecentReposDropdown open() {
        trigger.click();
        waitUntilTrue(isOpen());

        return this;
    }

    /**
     * Open the public repositories page by clicking the link in the drop-down. The drop-down must be open for this
     * method to succeed.
     *
     * @return the public repositories page object
     */
    @Nonnull
    public PublicRepositoryListPage openPublicRepositories() {
        waitUntilTrue(isOpen());
        getPublicRepositoriesListSection().find(By.tagName("a")).click();

        return pageBinder.bind(PublicRepositoryListPage.class);
    }

    /**
     * Toggle the drop-down on or off (depending on initial state) by clicking on the trigger.
     *
     * @return this dropdown
     */
    @Nonnull
    public RecentReposDropdown toggle() {
        trigger.click();

        return this;
    }

    @Nonnull
    public TimedQuery<Iterable<RecentRepo>> getRecentRepositories() {
        return Queries.forSupplier(timeouts, new Supplier<Iterable<RecentRepo>>() {
            @Override
            public Iterable<RecentRepo> get() {
                return Chainable.chain(getRecentRepositoriesSection().findAll(By.className("repository-menu-item")))
                        .transform(input -> pageBinder.bind(RecentRepo.class, input))
                        .toList();
            }
        });
    }

    @Nonnull
    protected PageElement getLoadingRecentRepositoriesIndicator() {
        return getRecentRepositoriesSection().find(By.className("loading-recent-repos"));
    }

    @Nonnull
    protected PageElement getNoRecentReposIndicator() {
        return getRecentRepositoriesSection().find(By.className("no-recent-repos"));
    }

    @Nonnull
    protected PageElement getRecentRepositoriesSection() {
        return container.find(By.className("recent-repositories-section"));
    }

    @Nonnull
    protected PageElement getPublicRepositoriesListSection() {
        return container.find(By.className("public-repo-list-link-section"));
    }

    public static class RecentRepo {

        @Inject
        private PageBinder pageBinder;

        private final PageElement repoListItem;

        public RecentRepo(@Nonnull PageElement repoListItem) {
            this.repoListItem = checkNotNull(repoListItem, "repoListItem");
        }

        @Nonnull
        public String getProjectKey() {
            return getRepoLink().getAttribute("data-proj-key");
        }

        @Nonnull
        public String getRepoSlug() {
            return getRepoLink().getAttribute("data-repo-slug");
        }

        /**
         * Open the repository page.
         *
         * @param expectedRepositoryPage expected page (most likely {@code FileBrowserPage} or
         * {@code EmptyRepositoryPage}
         * @param <RP> expected repository page type parameter
         * @return repository page object instance
         */
        @Nonnull
        public <RP extends BaseRepositoryPage> RP open(Class<RP> expectedRepositoryPage) {
            getRepoLink().click();

            return pageBinder.bind(expectedRepositoryPage, getProjectKey(), getRepoSlug());
        }

        private PageElement getRepoLink() {
            return repoListItem.find(By.tagName("a"));
        }
    }
}
