/**
 * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
 */
/**
 * @module find-and-replace/findandreplaceui
 */
import { Plugin } from 'ckeditor5/src/core.js';
import { ButtonView, MenuBarMenuListItemButtonView, Dialog, DialogViewPosition, createDropdown, DropdownView, FormHeaderView, CssTransitionDisablerMixin } from 'ckeditor5/src/ui.js';
import FindAndReplaceFormView from './ui/findandreplaceformview.js';
import loupeIcon from '../theme/icons/find-replace.svg';
/**
 * The default find and replace UI.
 *
 * It registers the `'findAndReplace'` UI button in the editor's {@link module:ui/componentfactory~ComponentFactory component factory}.
 * that uses the {@link module:find-and-replace/findandreplace~FindAndReplace FindAndReplace} plugin API.
 */
export default class FindAndReplaceUI extends Plugin {
    /**
     * @inheritDoc
     */
    static get requires() {
        return [Dialog];
    }
    /**
     * @inheritDoc
     */
    static get pluginName() {
        return 'FindAndReplaceUI';
    }
    /**
     * @inheritDoc
     */
    constructor(editor) {
        super(editor);
        editor.config.define('findAndReplace.uiType', 'dialog');
        this.formView = null;
    }
    /**
     * @inheritDoc
     */
    init() {
        const editor = this.editor;
        const isUiUsingDropdown = editor.config.get('findAndReplace.uiType') === 'dropdown';
        const findCommand = editor.commands.get('find');
        const t = this.editor.t;
        // Register the toolbar component: dropdown or button (that opens a dialog).
        editor.ui.componentFactory.add('findAndReplace', () => {
            let view;
            if (isUiUsingDropdown) {
                view = this._createDropdown();
                // Button should be disabled when in source editing mode. See #10001.
                view.bind('isEnabled').to(findCommand);
            }
            else {
                view = this._createDialogButtonForToolbar();
            }
            editor.keystrokes.set('Ctrl+F', (data, cancelEvent) => {
                if (!findCommand.isEnabled) {
                    return;
                }
                if (view instanceof DropdownView) {
                    const dropdownButtonView = view.buttonView;
                    if (!dropdownButtonView.isOn) {
                        dropdownButtonView.fire('execute');
                    }
                }
                else {
                    if (view.isOn) {
                        // If the dialog is open, do not close it. Instead focus it.
                        // Unfortunately we can't simply use:
                        // 	this.formView!.focus();
                        // because it would always move focus to the first input field, which we don't want.
                        editor.plugins.get('Dialog').view.focus();
                    }
                    else {
                        view.fire('execute');
                    }
                }
                cancelEvent();
            });
            return view;
        });
        if (!isUiUsingDropdown) {
            editor.ui.componentFactory.add('menuBar:findAndReplace', () => {
                return this._createDialogButtonForMenuBar();
            });
        }
        // Add the information about the keystroke to the accessibility database.
        editor.accessibility.addKeystrokeInfos({
            keystrokes: [
                {
                    label: t('Find in the document'),
                    keystroke: 'CTRL+F'
                }
            ]
        });
    }
    /**
     * Creates a dropdown containing the find and replace form.
     */
    _createDropdown() {
        const editor = this.editor;
        const t = editor.locale.t;
        const dropdownView = createDropdown(editor.locale);
        dropdownView.once('change:isOpen', () => {
            this.formView = this._createFormView();
            this.formView.children.add(new FormHeaderView(editor.locale, {
                label: t('Find and replace')
            }), 0);
            dropdownView.panelView.children.add(this.formView);
        });
        // Every time a dropdown is opened, the search text field should get focused and selected for better UX.
        // Note: Using the low priority here to make sure the following listener starts working after
        // the default action of the drop-down is executed (i.e. the panel showed up). Otherwise,
        // the invisible form/input cannot be focused/selected.
        //
        // Each time a dropdown is closed, move the focus back to the find and replace toolbar button
        // and let the find and replace editing feature know that all search results can be invalidated
        // and no longer should be marked in the content.
        dropdownView.on('change:isOpen', (event, name, isOpen) => {
            if (isOpen) {
                this._setupFormView();
            }
            else {
                this.fire('searchReseted');
            }
        }, { priority: 'low' });
        dropdownView.buttonView.set({
            icon: loupeIcon,
            label: t('Find and replace'),
            keystroke: 'CTRL+F',
            tooltip: true
        });
        return dropdownView;
    }
    /**
     * Creates a button that opens a dialog with the find and replace form.
     */
    _createDialogButtonForToolbar() {
        const editor = this.editor;
        const buttonView = this._createButton(ButtonView);
        const dialog = editor.plugins.get('Dialog');
        buttonView.set({
            tooltip: true
        });
        // Button should be on when the find and replace dialog is opened.
        buttonView.bind('isOn').to(dialog, 'id', id => id === 'findAndReplace');
        // Every time a dialog is opened, the search text field should get focused and selected for better UX.
        // Each time a dialog is closed, move the focus back to the find and replace toolbar button
        // and let the find and replace editing feature know that all search results can be invalidated
        // and no longer should be marked in the content.
        buttonView.on('execute', () => {
            if (buttonView.isOn) {
                dialog.hide();
            }
            else {
                this._showDialog();
            }
        });
        return buttonView;
    }
    /**
     * Creates a button for for menu bar that will show find and replace dialog.
     */
    _createDialogButtonForMenuBar() {
        const buttonView = this._createButton(MenuBarMenuListItemButtonView);
        const dialogPlugin = this.editor.plugins.get('Dialog');
        const dialog = this.editor.plugins.get('Dialog');
        buttonView.set({
            role: 'menuitemcheckbox',
            isToggleable: true
        });
        // Button should be on when the find and replace dialog is opened.
        buttonView.bind('isOn').to(dialog, 'id', id => id === 'findAndReplace');
        buttonView.on('execute', () => {
            if (dialogPlugin.id === 'findAndReplace') {
                dialogPlugin.hide();
                return;
            }
            this._showDialog();
        });
        return buttonView;
    }
    /**
     * Creates a button for find and replace command to use either in toolbar or in menu bar.
     */
    _createButton(ButtonClass) {
        const editor = this.editor;
        const findCommand = editor.commands.get('find');
        const buttonView = new ButtonClass(editor.locale);
        const t = editor.locale.t;
        // Button should be disabled when in source editing mode. See #10001.
        buttonView.bind('isEnabled').to(findCommand);
        buttonView.set({
            icon: loupeIcon,
            label: t('Find and replace'),
            keystroke: 'CTRL+F'
        });
        return buttonView;
    }
    /**
     * Shows the find and replace dialog.
     */
    _showDialog() {
        const editor = this.editor;
        const dialog = editor.plugins.get('Dialog');
        const t = editor.locale.t;
        if (!this.formView) {
            this.formView = this._createFormView();
        }
        dialog.show({
            id: 'findAndReplace',
            title: t('Find and replace'),
            content: this.formView,
            position: DialogViewPosition.EDITOR_TOP_SIDE,
            onShow: () => {
                this._setupFormView();
            },
            onHide: () => {
                this.fire('searchReseted');
            }
        });
    }
    /**
     * Sets up the form view for the find and replace.
     *
     * @param formView A related form view.
     */
    _createFormView() {
        const editor = this.editor;
        const formView = new (CssTransitionDisablerMixin(FindAndReplaceFormView))(editor.locale);
        const commands = editor.commands;
        const findAndReplaceEditing = this.editor.plugins.get('FindAndReplaceEditing');
        const editingState = findAndReplaceEditing.state;
        formView.bind('highlightOffset').to(editingState, 'highlightedOffset');
        // Let the form know how many results were found in total.
        formView.listenTo(editingState.results, 'change', () => {
            formView.matchCount = editingState.results.length;
        });
        // Command states are used to enable/disable individual form controls.
        // To keep things simple, instead of binding 4 individual observables, there's only one that combines every
        // commands' isEnabled state. Yes, it will change more often but this simplifies the structure of the form.
        const findNextCommand = commands.get('findNext');
        const findPreviousCommand = commands.get('findPrevious');
        const replaceCommand = commands.get('replace');
        const replaceAllCommand = commands.get('replaceAll');
        formView.bind('_areCommandsEnabled').to(findNextCommand, 'isEnabled', findPreviousCommand, 'isEnabled', replaceCommand, 'isEnabled', replaceAllCommand, 'isEnabled', (findNext, findPrevious, replace, replaceAll) => ({ findNext, findPrevious, replace, replaceAll }));
        // The UI plugin works as an interface between the form and the editing part of the feature.
        formView.delegate('findNext', 'findPrevious', 'replace', 'replaceAll').to(this);
        // Let the feature know that search results are no longer relevant because the user changed the searched phrase
        // (or options) but didn't hit the "Find" button yet (e.g. still typing).
        formView.on('change:isDirty', (evt, data, isDirty) => {
            if (isDirty) {
                this.fire('searchReseted');
            }
        });
        return formView;
    }
    /**
     * Clears the find and replace form and focuses the search text field.
     */
    _setupFormView() {
        this.formView.disableCssTransitions();
        this.formView.reset();
        this.formView._findInputView.fieldView.select();
        this.formView.enableCssTransitions();
    }
}
