// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.toolbar;

import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.View.OnClickListener;

import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;

import org.chromium.base.FeatureList;
import org.chromium.base.ObserverList;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonVariant;
import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarFeatures;
import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogManagerObserver;
import org.chromium.ui.modelutil.PropertyModel;

/**
 * Base class for button data providers used on the adaptive toolbar.
 */
public abstract class BaseButtonDataProvider implements ButtonDataProvider, OnClickListener {
    protected final ButtonDataImpl mButtonData;
    protected final Supplier<Tab> mActiveTabSupplier;

    private final ObserverList<ButtonDataObserver> mObservers = new ObserverList<>();
    private final ModalDialogManager mModalDialogManager;
    private ModalDialogManagerObserver mModalDialogObserver;

    private boolean mShouldShowOnIncognitoTabs;

    /**
     * Creates a new instance of {@code BaseButtonDataProvider}.
     * @param activeTabSupplier Supplier for the current active tab.
     * @param modalDialogManager Modal dialog manager, used to disable the button when a dialog is
     *         visible. Can be null to disable this behavior.
     * @param buttonDrawable Drawable for the button icon.
     * @param contentDescription String for the button's content description.
     * @param supportsTinting Whether the button's icon should be tinted.
     * @param iphCommandBuilder An IPH command builder instance to show when the button is
     *         displayed, can be null.
     * @param adaptiveButtonVariant Enum value of {@link AdaptiveToolbarButtonVariant}, used for
     *         metrics.
     */
    public BaseButtonDataProvider(Supplier<Tab> activeTabSupplier,
            @Nullable ModalDialogManager modalDialogManager, Drawable buttonDrawable,
            String contentDescription, @StringRes int actionChipLabelResId, boolean supportsTinting,
            @Nullable IPHCommandBuilder iphCommandBuilder,
            @AdaptiveToolbarButtonVariant int adaptiveButtonVariant) {
        mActiveTabSupplier = activeTabSupplier;
        mModalDialogManager = modalDialogManager;
        if (mModalDialogManager != null) {
            mModalDialogObserver = new ModalDialogManagerObserver() {
                @Override
                public void onDialogAdded(PropertyModel model) {
                    mButtonData.setEnabled(false);
                    notifyObservers(mButtonData.canShow());
                }

                @Override
                public void onLastDialogDismissed() {
                    mButtonData.setEnabled(true);
                    notifyObservers(mButtonData.canShow());
                }
            };
            mModalDialogManager.addObserver(mModalDialogObserver);
        }

        if (!AdaptiveToolbarFeatures.isDynamicAction(adaptiveButtonVariant)) {
            assert actionChipLabelResId
                    == Resources.ID_NULL : "Action chip should only be used on dynamic actions";
        }

        mButtonData = new ButtonDataImpl(/*canShow=*/false, buttonDrawable,
                /* onClickListener= */ this, contentDescription, actionChipLabelResId,
                supportsTinting,
                /* iphCommandBuilder= */ iphCommandBuilder, /*isEnabled=*/true,
                adaptiveButtonVariant);
    }

    /**
     * Checks if the button should be shown for the current tab. Called every time {@code get()} is
     * invoked.
     * @param tab Current tab.
     * @return whether the button should be shown for the current tab.
     */
    @CallSuper
    protected boolean shouldShowButton(Tab tab) {
        if (tab == null) return false;

        if (tab.isIncognito() && !mShouldShowOnIncognitoTabs) return false;

        return true;
    }

    protected void notifyObservers(boolean hint) {
        for (ButtonDataObserver observer : mObservers) {
            observer.buttonDataChanged(hint);
        }
    }

    /**
     * Sets the button's {@link IPHCommandBuilder} if needed, called every time {@code get()} is
     * invoked.
     * @param tab Current tab.
     */
    private void maybeSetIphCommandBuilder(Tab tab) {
        if (mButtonData.getButtonSpec().getIPHCommandBuilder() != null || tab == null
                || !FeatureList.isInitialized() || !AdaptiveToolbarFeatures.isCustomizationEnabled()
                || AdaptiveToolbarFeatures.shouldShowActionChip(
                        mButtonData.getButtonSpec().getButtonVariant())) {
            return;
        }

        mButtonData.updateIPHCommandBuilder(getIphCommandBuilder(tab));
    }

    /**
     * Sets whether the button should be shown on incognito tabs, default is false.
     */
    protected void setShouldShowOnIncognitoTabs(boolean shouldShowOnIncognitoTabs) {
        mShouldShowOnIncognitoTabs = shouldShowOnIncognitoTabs;
    }

    /**
     * Gets an {@link IPHCommandBuilder} builder instance to use on this button. Only called when
     * native is initialized and when there's no IPHCommandBuilder set.
     * @param tab Current tab.
     * @return An {@link org.chromium.chrome.browser.user_education.IPHCommand} instance to set on
     *         this button, or null if no IPH should be used.
     */
    protected IPHCommandBuilder getIphCommandBuilder(Tab tab) {
        return null;
    }

    /** ButtonDataProvider implementation. */
    @Override
    public void addObserver(ButtonDataObserver obs) {
        mObservers.addObserver(obs);
    }

    @Override
    public void removeObserver(ButtonDataObserver obs) {
        mObservers.removeObserver(obs);
    }

    @Override
    public ButtonData get(Tab tab) {
        mButtonData.setCanShow(shouldShowButton(tab));
        maybeSetIphCommandBuilder(tab);

        return mButtonData;
    }

    @Override
    @CallSuper
    public void destroy() {
        mObservers.clear();
        if (mModalDialogManager != null) {
            mModalDialogManager.removeObserver(mModalDialogObserver);
        }
    }

    /* OnClickListener implementation. */
    @Override
    public abstract void onClick(View view);
}
