// *****************************************************************************
// Copyright (C) 2021 Ericsson and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { PreferenceLeafNodeRenderer, PreferenceNodeRenderer } from './preference-node-renderer';
import { injectable, interfaces } from '@theia/core/shared/inversify';
import { JSONValue } from '@theia/core/shared/@lumino/coreutils';
import { SelectComponent, SelectOption } from '@theia/core/lib/browser/widgets/select-component';
import { Preference } from '../../util/preference-types';
import { PreferenceLeafNodeRendererContribution } from './preference-node-renderer-creator';
import * as React from '@theia/core/shared/react';
import { createRoot } from '@theia/core/shared/react-dom/client';
import { escapeInvisibleChars } from '@theia/core/lib/common/strings';
import { PreferenceUtils } from '@theia/core';

@injectable()
export class PreferenceSelectInputRenderer extends PreferenceLeafNodeRenderer<JSONValue, HTMLDivElement> {

    protected readonly selectComponent = React.createRef<SelectComponent>();

    protected selectOptions: SelectOption[] = [];

    protected get enumValues(): JSONValue[] {
        return this.preferenceNode.preference.data.enum!;
    }

    protected updateSelectOptions(): void {
        const updatedSelectOptions: SelectOption[] = [];
        const values = this.enumValues;
        const preferenceData = this.preferenceNode.preference.data;
        const defaultValue = preferenceData.default;
        for (let i = 0; i < values.length; i++) {
            const value = values[i];
            const stringValue = `${value}`;
            const label = escapeInvisibleChars(preferenceData.enumItemLabels?.[i] ?? stringValue);
            const detail = PreferenceUtils.deepEqual(defaultValue, value) ? 'default' : undefined;
            let enumDescription = preferenceData.enumDescriptions?.[i];
            let markdown = false;
            const markdownEnumDescription = preferenceData.markdownEnumDescriptions?.[i];
            if (markdownEnumDescription) {
                enumDescription = this.markdownRenderer.renderInline(markdownEnumDescription);
                markdown = true;
            }
            updatedSelectOptions.push({
                label,
                value: stringValue,
                detail,
                description: enumDescription,
                markdown
            });
        }
        this.selectOptions = updatedSelectOptions;
    }

    protected createInteractable(parent: HTMLElement): void {
        this.updateSelectOptions();
        const interactable = document.createElement('div');
        const selectComponent = React.createElement(SelectComponent, {
            options: this.selectOptions,
            defaultValue: this.getDataValue(),
            onChange: (_, index) => this.handleUserInteraction(index),
            ref: this.selectComponent
        });
        this.interactable = interactable;
        const root = createRoot(interactable);
        root.render(selectComponent);
        parent.appendChild(interactable);
    }

    protected getFallbackValue(): JSONValue {
        const { default: schemaDefault, enum: enumValues } = this.preferenceNode.preference.data;
        return schemaDefault !== undefined ? schemaDefault : enumValues![0];
    }

    protected doHandleValueChange(): void {
        this.updateInspection();
        this.updateSelectOptions();
        const newValue = this.getDataValue();
        this.updateModificationStatus();
        if (document.activeElement !== this.interactable && this.selectComponent.current) {
            this.selectComponent.current.value = newValue;
        }
    }

    /**
     * Returns the stringified index corresponding to the currently selected value.
     */
    protected getDataValue(): number {
        const currentValue = this.getValue();
        let selected = this.enumValues.findIndex(value => PreferenceUtils.deepEqual(value, currentValue));
        if (selected === -1) {
            const fallback = this.getFallbackValue();
            selected = this.enumValues.findIndex(value => PreferenceUtils.deepEqual(value, fallback));
        }
        return Math.max(selected, 0);
    }

    protected handleUserInteraction(selected: number): void {
        const value = this.enumValues[selected];
        this.setPreferenceImmediately(value);
    }
}

@injectable()
export class PreferenceSelectInputRendererContribution extends PreferenceLeafNodeRendererContribution {
    static ID = 'preference-select-input-renderer';
    id = PreferenceSelectInputRendererContribution.ID;

    canHandleLeafNode(node: Preference.LeafNode): number {
        return node.preference.data.enum ? 3 : 0;
    }

    createLeafNodeRenderer(container: interfaces.Container): PreferenceNodeRenderer {
        return container.get(PreferenceSelectInputRenderer);
    }
}
