/*
 * @Copyright 2018-2026 HardBackNutter
 * @License GNU General Public License
 *
 * This file is part of NeverTooManyBooks.
 *
 * NeverTooManyBooks 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.
 *
 * NeverTooManyBooks 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 NeverTooManyBooks. If not, see <http://www.gnu.org/licenses/>.
 */

package com.hardbacknutter.nevertoomanybooks.bookedit;

import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Checkable;
import android.widget.CompoundButton;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;

import java.util.Locale;
import java.util.Objects;

import com.hardbacknutter.nevertoomanybooks.R;
import com.hardbacknutter.nevertoomanybooks.ServiceLocator;
import com.hardbacknutter.nevertoomanybooks.core.widgets.adapters.ExtArrayAdapter;
import com.hardbacknutter.nevertoomanybooks.databinding.DialogEditBookAuthorContentBinding;
import com.hardbacknutter.nevertoomanybooks.dialogs.DialogType;
import com.hardbacknutter.nevertoomanybooks.dialogs.FlexDialogDelegate;
import com.hardbacknutter.nevertoomanybooks.dialogs.entities.EditAction;
import com.hardbacknutter.nevertoomanybooks.dialogs.entities.EditParcelableInput;
import com.hardbacknutter.nevertoomanybooks.dialogs.entities.EditParcelableOutput;
import com.hardbacknutter.nevertoomanybooks.dialogs.entities.author.EditAuthorViewModel;
import com.hardbacknutter.nevertoomanybooks.entities.Author;
import com.hardbacknutter.nevertoomanybooks.entities.AuthorRole;
import com.hardbacknutter.nevertoomanybooks.widgets.TilUtil;
import com.hardbacknutter.util.insets.InsetsListenerBuilder;
import com.hardbacknutter.util.insets.Side;

/**
 * Add/Edit a single {@link Author} from the book's author list.
 * <p>
 * Can already exist (i.e. have an id) or can be a previously added/new one (id==0).
 * <p>
 * {@link EditAction#Add}:
 * <ul>
 * <li>List-dialogs ADD a NEW item</li>
 * <li>The new item is <strong>NOT stored</strong> in the database</li>
 * <li>Returns the new item</li>
 * </ul>
 * <p>
 * {@link EditAction#Edit}:
 * <ul>
 * <li>List-dialogs EDIT an EXISTING item</li>
 * <li>Modifications are <strong>NOT STORED</strong> in the database</li>
 * <li>Returns the original + a new instance/copy with the modifications</li>
 * </ul>
 * <p>
 * In both of the above cases, the 'real' Author <strong>IS STORED</strong>
 * when applicable and explicitly allowed by the user.
 */
class EditBookAuthorDelegate
        implements FlexDialogDelegate {

    /**
     * We create a list of all the {@link AuthorRole.Role} checkboxes for easy handling.
     * The key is the {@link AuthorRole.Role}.
     */
    private final SparseArray<CompoundButton> roleButtons = new SparseArray<>();

    @NonNull
    private final DialogFragment owner;
    @NonNull
    private final String requestKey;

    /** Book View model. Activity scope. */
    private final EditBookViewModel vm;
    /** Author View model. Fragment scope. */
    private final EditAuthorViewModel authorVm;
    /** Adding or Editing. */
    private final EditAction action;
    /** View Binding. */
    private DialogEditBookAuthorContentBinding vb;
    @Nullable
    private Toolbar toolbar;

    /**
     * Constructor.
     * <p>
     * Class output: {@link EditParcelableOutput}.
     *
     * @param owner hosting Fragment
     * @param args  all arguments
     */
    EditBookAuthorDelegate(@NonNull final DialogFragment owner,
                           @NonNull final EditParcelableInput<Author> args) {
        this.owner = owner;
        requestKey = args.getRequestKey();
        action = args.getAction();

        //noinspection DataFlowIssue
        vm = new ViewModelProvider(owner.getActivity()).get(EditBookViewModel.class);
        authorVm = new ViewModelProvider(owner).get(EditAuthorViewModel.class);
        authorVm.init(args);
    }

    @NonNull
    @Override
    public View onCreateView(@NonNull final LayoutInflater inflater,
                             @Nullable final ViewGroup container) {
        vb = DialogEditBookAuthorContentBinding.inflate(inflater, container, false);
        return vb.getRoot();
    }

    @Override
    @NonNull
    public View onCreateFullscreen(@NonNull final LayoutInflater inflater,
                                   @Nullable final ViewGroup container) {
        final View view = inflater.inflate(R.layout.dialog_edit_book_author, container, false);
        vb = DialogEditBookAuthorContentBinding.bind(view.findViewById(R.id.dialog_content));
        return view;
    }

    @NonNull
    public Toolbar getToolbar() {
        return Objects.requireNonNull(toolbar, "No toolbar set");
    }

    @Override
    public void setToolbar(@Nullable final Toolbar toolbar) {
        this.toolbar = toolbar;
    }

    /**
     * The menu is a set of buttons on an ActionView of a single MenuItem.
     *
     * @return parent view of the menu buttons
     */
    @NonNull
    private View getToolbarMenuActionView() {
        //noinspection DataFlowIssue
        return toolbar.getMenu().findItem(R.id.MENU_EDIT_BOOK_AUTHOR).getActionView();
    }

    @NonNull
    private CompoundButton getRolesSwitch() {
        return getToolbarMenuActionView().findViewById(R.id.toolbar_btn_roles);
    }

    @Override
    public void onViewCreated(@NonNull final DialogType dialogType) {

        new InsetsListenerBuilder(vb.dialogContent.getRootView())
                .padding(Side.Start, Side.End, Side.Top, Side.Bottom)
                .systemBars()
                .displayCutout()
                .ime()
                .systemGestures()
                .apply();

        if (toolbar != null) {
            if (dialogType == DialogType.BottomSheet) {
                toolbar.inflateMenu(R.menu.edit_book_author);
            } else if (dialogType == DialogType.Floating) {
                toolbar.inflateMenu(R.menu.edit_book_author);
                // Hide duplicate buttons in favour of the bottom button-bar
                getToolbarMenuActionView().findViewById(R.id.toolbar_btn_save)
                                          .setVisibility(View.GONE);
            }
            initToolbar(owner, dialogType, toolbar);
        }

        final Context context = vb.getRoot().getContext();

        final Author currentEdit = authorVm.getCurrentEdit();

        initNames(context, currentEdit);
        initRealAuthorField(context);
        initAuthorRoleField(currentEdit.getRole());
        vb.cbxIsComplete.setChecked(currentEdit.isComplete());

        vb.familyName.requestFocus();
    }

    private void initNames(@NonNull final Context context,
                           @NonNull final Author author) {
        final ExtArrayAdapter<String> familyNameAdapter = new ExtArrayAdapter<>(
                context, R.layout.popup_dropdown_menu_item,
                ExtArrayAdapter.FilterType.Diacritic,
                vm.getAllAuthorFamilyNames());
        vb.familyName.setText(author.getFamilyName());
        vb.familyName.setAdapter(familyNameAdapter);
        TilUtil.autoRemoveError(vb.familyName, vb.lblFamilyName);

        final ExtArrayAdapter<String> givenNameAdapter = new ExtArrayAdapter<>(
                context, R.layout.popup_dropdown_menu_item,
                ExtArrayAdapter.FilterType.Diacritic,
                vm.getAllAuthorGivenNames());
        vb.givenNames.setText(author.getGivenNames());
        vb.givenNames.setAdapter(givenNameAdapter);
    }

    private void initRealAuthorField(@NonNull final Context context) {
        if (authorVm.showRealAuthorName()) {
            vb.lblRealAuthorHeader.setVisibility(View.VISIBLE);
            vb.lblRealAuthor.setVisibility(View.VISIBLE);

            final ExtArrayAdapter<String> realNameAdapter = new ExtArrayAdapter<>(
                    context, R.layout.popup_dropdown_menu_item,
                    ExtArrayAdapter.FilterType.Diacritic,
                    vm.getAllAuthorNames());
            vb.realAuthor.setText(authorVm.getCurrentRealAuthorName(), false);
            vb.realAuthor.setAdapter(realNameAdapter);
            TilUtil.autoRemoveError(vb.realAuthor, vb.lblRealAuthor);

        } else {
            vb.lblRealAuthorHeader.setVisibility(View.GONE);
            vb.lblRealAuthor.setVisibility(View.GONE);
        }
    }

    private void initAuthorRoleField(@AuthorRole.Role final int currentRole) {
        final CompoundButton rolesSwitch = getRolesSwitch();

        if (!authorVm.showAuthorRole()) {
            // roles are globally disabled.
            rolesSwitch.setVisibility(View.GONE);
            showRoleButtons(false);
            return;
        }

        createRoleButtonMapping();

        rolesSwitch.setVisibility(View.VISIBLE);
        final boolean rolesAreShown = authorVm.isRolesAreShown();
        rolesSwitch.setChecked(rolesAreShown);
        showRoleButtons(rolesAreShown);
        // All roleButtons are unchecked at this point.
        // Quick check if we should bother setting any?
        if (currentRole != AuthorRole.UNKNOWN) {
            for (int i = 0; i < roleButtons.size(); i++) {
                roleButtons.valueAt(i).setChecked((currentRole & roleButtons.keyAt(i)) != 0);
            }
        }
    }

    private void createRoleButtonMapping() {
        // NEWTHINGS: author role: add layout button to the Map
        roleButtons.put(AuthorRole.WRITER, vb.cbxAuthorRoleWriter);
        roleButtons.put(AuthorRole.CONTRIBUTOR, vb.cbxAuthorRoleContributor);
        roleButtons.put(AuthorRole.INTRODUCTION, vb.cbxAuthorRoleIntro);
        roleButtons.put(AuthorRole.FOREWORD, vb.cbxAuthorRoleForeword);
        roleButtons.put(AuthorRole.AFTERWORD, vb.cbxAuthorRoleAfterword);
        roleButtons.put(AuthorRole.TRANSLATOR, vb.cbxAuthorRoleTranslator);
        roleButtons.put(AuthorRole.EDITOR, vb.cbxAuthorRoleEditor);
        roleButtons.put(AuthorRole.NARRATOR, vb.cbxAuthorRoleNarrator);

        roleButtons.put(AuthorRole.ARTIST, vb.cbxAuthorRoleArtist);
        roleButtons.put(AuthorRole.INKING, vb.cbxAuthorRoleInking);
        roleButtons.put(AuthorRole.COLORIST, vb.cbxAuthorRoleColorist);
        roleButtons.put(AuthorRole.STORYBOARD, vb.cbxAuthorRoleStoryboard);
        roleButtons.put(AuthorRole.LETTERING, vb.cbxAuthorRoleLettering);

        roleButtons.put(AuthorRole.COVER_ARTIST, vb.cbxAuthorRoleCoverArtist);
        roleButtons.put(AuthorRole.COVER_INKING, vb.cbxAuthorRoleCoverInking);
        roleButtons.put(AuthorRole.COVER_COLORIST, vb.cbxAuthorRoleCoverColorist);
    }

    /**
     * Enable or disable the role related fields.
     *
     * @param enable Flag
     */
    private void showRoleButtons(final boolean enable) {
        vb.authorRoleGroup.setVisibility(enable ? View.VISIBLE : View.GONE);
    }

    @Override
    public void onToolbarNavigationClick(@NonNull final View v) {
        owner.dismiss();
    }

    @Override
    public boolean onToolbarButtonClick(@Nullable final View button) {
        if (button != null) {
            final int id = button.getId();
            if (id == R.id.toolbar_btn_roles) {
                final boolean checked = ((Checkable) button).isChecked();
                authorVm.setRolesAreShown(checked);
                showRoleButtons(checked);
                return true;

            } else if (id == R.id.toolbar_btn_save || id == R.id.btn_positive) {
                if (saveChanges(false)) {
                    owner.dismiss();
                }
                return true;
            }
        }
        return false;
    }

    private boolean saveChanges(final boolean createRealAuthorIfNeeded) {
        viewToModel();

        final Context context = vb.getRoot().getContext();

        final Author currentEdit = authorVm.getCurrentEdit();
        // basic check only, we're doing more extensive checks later on.
        if (currentEdit.getFamilyName().isEmpty()) {
            vb.lblFamilyName.setError(context.getString(R.string.vldt_non_blank_required));
            return false;
        }

        // invalidate the role if needed
        if (!getRolesSwitch().isChecked()) {
            currentEdit.setRole(AuthorRole.UNKNOWN);
        }

        // Use the Locale from the book language.
        final String language = vm.getBook().getLanguage();
        final Locale userLocale = context.getResources().getConfiguration().getLocales().get(0);
        final Locale locale = ServiceLocator.getInstance().getLanguages()
                                            .toLocale(language, userLocale);

        // We let this call go ahead even if real-author is switched off by the user
        // so we can clean up as needed.
        if (!authorVm.validateAndSetRealAuthor(context, locale, createRealAuthorIfNeeded)) {
            warnThatRealAuthorMustBeValid(context);
            return false;
        }

        new EditParcelableOutput<>(action, authorVm.getOriginal(), currentEdit)
                .send(owner, requestKey);
        return true;
    }

    private void warnThatRealAuthorMustBeValid(@NonNull final Context context) {
        new MaterialAlertDialogBuilder(context)
                .setIcon(R.drawable.warning_24px)
                .setTitle(R.string.vldt_real_author_must_be_valid)
                .setMessage(context.getString(R.string.confirm_create_real_author,
                                              authorVm.getCurrentRealAuthorName()))
                .setNegativeButton(R.string.action_edit, (d, w) -> vb.lblRealAuthor.setError(
                        context.getString(R.string.vldt_real_author_must_be_valid)))
                .setPositiveButton(R.string.action_create, (d, w) -> {
                    if (saveChanges(true)) {
                        // finish the DialogFragment
                        owner.dismiss();
                    }
                })
                .create()
                .show();
    }

    @Override
    public void onPause(@NonNull final LifecycleOwner lifecycleOwner) {
        viewToModel();
    }

    private void viewToModel() {
        final Author author = authorVm.getCurrentEdit();

        author.setName(vb.familyName.getText().toString().strip(),
                            vb.givenNames.getText().toString().strip());
        if (authorVm.showAuthorRole()) {
            // Always set these when globally enabled
            // even when disabled for the current edit.
            // This is more user-friendly if the user flips the switch more than once.
            int role = AuthorRole.UNKNOWN;
            for (int i = 0; i < roleButtons.size(); i++) {
                if (roleButtons.valueAt(i).isChecked()) {
                    role |= roleButtons.keyAt(i);
                }
            }
            author.setRole(role);
        }

        if (authorVm.showRealAuthorName()) {
            authorVm.setCurrentRealAuthorName(vb.realAuthor.getText().toString().strip());
        }
        author.setComplete(vb.cbxIsComplete.isChecked());
    }
}
