/*
 * Copyright 2020 Adobe. All rights reserved.
 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under
 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
 * OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

import React, { Component, ComponentType } from 'react';
import isEqual from 'react-fast-compare';
import { MappedComponentProperties } from '../ComponentMapping';
import { Constants } from '../Constants';
import { ContainerState } from './Container';

/**
 * Configuration object of the withEditable function.
 *
 * @property emptyLabel - Label to be displayed on the overlay when the component is empty
 * @property isEmpty - Callback function to determine if the component is empty
 * @property resourceType - AEM ResourceType to be added as an attribute on the editable component dom
 */
export interface EditConfig<P extends MappedComponentProperties> {
    emptyLabel?: string;
    isEmpty(props: P): boolean;
    resourceType?: string;
}

export interface EditableComponentProperties<P extends MappedComponentProperties>{
    componentProperties: P;
    wrappedComponent: React.ComponentType<P>;
    editConfig: EditConfig<P>;
    containerProps?: { [key: string]: string };
}

type EditableComponentModel<P extends MappedComponentProperties> = EditableComponentProperties<P>;

/**
 * The EditableComponent provides components with editing capabilities.
 */
class EditableComponent<P extends MappedComponentProperties, S extends ContainerState> extends Component<EditableComponentModel<P>, S> {
    constructor(props: EditableComponentModel<P>) {
        super(props);
        this.state = this.propsToState(props);
    }

    public propsToState(props: EditableComponentModel<P>): any {
        // Keep private properties from being passed as state
        /* eslint-disable @typescript-eslint/no-unused-vars */
        const { wrappedComponent, containerProps, editConfig, ...state } = props;

        return state;
    }

    public componentDidUpdate(prevProps: EditableComponentModel<P>) {
        if (!isEqual(prevProps, this.props)) {
            this.setState(this.propsToState(this.props));
        }
    }

    /**
     * Properties related to the editing of the component.
     */
    get editProps(): { [key: string]: string } {
        const eProps: { [key: string]: string } = {};
        const componentProperties: P = this.props.componentProperties;

        if (!componentProperties.isInEditor) {
            return eProps;
        }

        eProps[Constants.DATA_PATH_ATTR] = componentProperties.cqPath;

        if (this.props.editConfig.resourceType) {
            eProps[Constants.DATA_CQ_RESOURCE_TYPE_ATTR] = this.props.editConfig.resourceType;
        }

        return eProps;
    }

    /**
     * Properties related to styling of the component.
     */
    get styleProps(): { [key: string]: string } {
        const sProps: { [key: string]: string } = { className: '' };
        const componentProperties: P = this.props.componentProperties;

        const appliedCssClassNames = componentProperties[Constants.APPLIED_CLASS_NAMES];

        if (appliedCssClassNames)
            sProps.className += appliedCssClassNames + ' ';

        if (this.props?.containerProps?.className){
            sProps.className = sProps.className + ' ' + this.props.containerProps.className;
        }
    
        return sProps;
    }

    protected get emptyPlaceholderProps() {
        if (!this.useEmptyPlaceholder()) {
            return null;
        }

        return {
            'className': Constants._PLACEHOLDER_CLASS_NAMES,
            'data-emptytext': this.props.editConfig.emptyLabel
        };
    }

    /**
     * Should an empty placeholder be added.
     *
     * @return
     */
    public useEmptyPlaceholder() {
        return this.props.componentProperties.isInEditor
            && (typeof this.props.editConfig.isEmpty === 'function')
            && this.props.editConfig.isEmpty(this.props.componentProperties);
    }

    public render() {
        const WrappedComponent: React.ComponentType<any> = this.props.wrappedComponent;

        const componentProperties: P = this.props.componentProperties;
        let renderScript;

        if (!componentProperties.isInEditor && componentProperties.aemNoDecoration){
            renderScript = (
                  <WrappedComponent {...this.state}/>
              )
        } else {
            renderScript = (
                <div {...this.editProps} {...{ ...this.props.containerProps, ...this.styleProps }} >
                  <WrappedComponent {...this.state}/>
                  <div {...this.emptyPlaceholderProps}/>
                </div>
              )
        }

        return renderScript;
    }
}

/**
 * Returns a component wrapper that provides editing capabilities to the component.
 *
 * @param WrappedComponent
 * @param editConfig
 */
export function withEditable<P extends MappedComponentProperties>(WrappedComponent: ComponentType<P>, editConfig?: EditConfig<P>) {

    const defaultEditConfig: EditConfig<P> = editConfig ? editConfig : { isEmpty: (props: P) => false };

    return class CompositeEditableComponent extends Component<P> {
        public render(): JSX.Element {
            type TypeToUse = EditableComponentProperties<P> & P;

            const computedProps: TypeToUse = {
                ...this.props,
                componentProperties: this.props,
                editConfig: defaultEditConfig,
                wrappedComponent: WrappedComponent
            };

            return <EditableComponent {...computedProps} />;
        }
    };
}