/**
 * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.com). All Rights Reserved.
 *
 * This software is the property of WSO2 Inc. and its suppliers, if any.
 * Dissemination of any information or reproduction of any material contained
 * herein in any form is strictly forbidden, unless permitted by WSO2 expressly.
 * You may not alter or remove any copyright or other notice from copies of this content."
 */

import { TestableComponentInterface } from "@wso2is/core/models";
import { FormValue } from "@wso2is/forms";
import { EmptyPlaceholder } from "@wso2is/react-components";
import differenceWith from "lodash/differenceWith";
import isEqual from "lodash/isEqual";
import React, { FunctionComponent, ReactElement, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Divider, Grid } from "semantic-ui-react";
import { ClaimEventClickItem, ClaimsList, ListType } from "../../../../../../features/claims";
import { getEmptyPlaceholderIllustrations } from "../../../../../../features/core";
import { ClaimManagementConstants } from "../../../../../../features/claims/constants";
import { AddExternalClaim } from "../../../../../../features/claims/models";
import { resolveType } from "../../../../../../features/claims/utils";
import { AddExternalClaims } from "../../add";

/**
 * Prop types of `ExternalClaims` component.
 */
interface ExternalClaimsPropsInterface extends TestableComponentInterface {
    /**
     * Calls onSubmit.
     */
    onSubmit: (claims: AddExternalClaim[]) => void;
    /**
     * Triggers submit.
     */
    submitState: boolean;
    /**
     * Saved add external claims
     */
    values: AddExternalClaim[];
    /**
     * A delegated event handler to pass the current
     * selected/active claims to the parent.
     *
     * @see methods onExternalClaimAdd, onExternalClaimDelete, onExternalClaimEdit
     * @param {AddExternalClaim[]} claims
     */
    onExternalClaimsChanged?: (claims: AddExternalClaim[]) => void;
    /**
     * Specifies if the initial values passed should be shown.
     */
    shouldShowInitialValues?: boolean;
    /**
     * Specifies the attribute type.
     */
    attributeType?: string;
    /**
     * Specifies if this is to be rendered in a wizard.
     */
    wizard?: boolean;
}

/**
 * This component renders the Add External Claims step of the wizard.
 *
 * @param {ExternalClaimsPropsInterface} props - Props injected to the component.
 *
 * @return {React.ReactElement}
 */
export const ExternalClaims: FunctionComponent<ExternalClaimsPropsInterface> = (
    props: ExternalClaimsPropsInterface
): ReactElement => {

    const {
        onSubmit,
        submitState,
        values,
        onExternalClaimsChanged,
        shouldShowInitialValues,
        attributeType,
        wizard,
        [ "data-testid" ]: testId
    } = props;

    const [ claims, setClaims ] = useState<AddExternalClaim[]>([]);
    const [ initialList, setInitialList ] = useState<AddExternalClaim[]>([]);

    const ref = useRef(true);
    const firstTimeValueChanges = useRef(true);

    const { t } = useTranslation();

    useEffect(() => {
        if (ref.current) {
            ref.current = false;
        } else {
            onSubmit(getAttributesList());
        }
    }, [ submitState ]);

    useEffect(() => {
        if (values) {
            setClaims(values);
            if (firstTimeValueChanges.current) {
                setInitialList(values);
                firstTimeValueChanges.current = false;
            }
        }
    }, [ values ]);

    /**
     * Handles the event when a new external claim has been submitted via
     * the form {@link AddExternalClaims}. We delegate this change to
     * to the above parent component {@link AddDialect} because it has the
     * state to manage the user selected {@link ExternalClaim} mappings.
     *
     * @see AddExternalClaims
     * @param {Map<string, FormValue>} values {claimURI, localClaim}
     */
    const onExternalClaimAdd = (values: Map<string, FormValue>): void => {
        const newClaim = {
            claimURI: values.get("claimURI").toString(),
            mappedLocalClaimURI: values.get("localClaim").toString()
        };
        const newState = [ ...claims, newClaim ];
        setClaims(newState);
        onExternalClaimsChanged && onExternalClaimsChanged(newState);
    };

    /**
     * This function handles the event when a added claim is removed from the
     * {@link ClaimsList} component. In here what we do is remove the item
     * from the local state {@link claims} and delegate the change to the
     * parent component {@link AddDialect} as well.
     *
     * Clarification Note: -
     * In this function the param `claim` is always {@link AddExternalClaim}
     *
     * @see {@link ClaimsList}
     * @param {ClaimEventClickItem} editingClaim
     */
    const onExternalClaimDelete = (editingClaim: ClaimEventClickItem): void => {
        const filteredClaims = claims.filter((claim: AddExternalClaim) => !isEqual(editingClaim, claim));
        setClaims(filteredClaims);
        onExternalClaimsChanged && onExternalClaimsChanged(filteredClaims);
    };

    /**
     * This function handles the event when the user edits a already added
     * claim in the {@link ClaimsList}
     *
     * @see ClaimsList
     * @param {ClaimEventClickItem} editingClaim
     * @param {Map<string, FormValue>} values {claimURI, localClaim}
     */
    const onExternalClaimEdit = (editingClaim: ClaimEventClickItem, values: Map<string, FormValue>): void => {
        const existingClaims = [ ...claims ];
        for (const claim of existingClaims) {
            // If its not the claim then continue
            if (!isEqual(editingClaim, claim)) continue;
            // `ClaimDialect` interface doesn't have `claimURI` key which results
            // in TS error due to the usage of union type.
            if (!( ClaimManagementConstants.CLAIM_URI_ATTRIBUTE_KEY in editingClaim )) continue;
            claim.claimURI = values.get("claimURI").toString();
            claim.mappedLocalClaimURI = values.get("localClaim").toString();
        }
        setClaims(existingClaims);
        onExternalClaimsChanged && onExternalClaimsChanged(existingClaims);
    };

    /**
     * Returns either the whole list or the newly added attributes based on the `shouldShowInitialValues` flag.
     */
    const getAttributesList = (): AddExternalClaim[] => {
        if (shouldShowInitialValues) {
            return claims;
        } else {
            return differenceWith(claims, initialList, isEqual);
        }
    };

    return (
        <Grid>
            <Grid.Row columns={ 2 }>
                <Grid.Column width={ 16 }>
                    <AddExternalClaims
                        wizard={ true }
                        onSubmit={ onExternalClaimAdd }
                        externalClaims={ claims }
                        data-testid={ `${ testId }-add-external-claims` }
                        attributeType={ attributeType }
                    />
                    <Divider hidden />
                    {
                        getAttributesList().length>0
                            ? (
                                <ClaimsList
                                    isLoading={ false }
                                    list={ getAttributesList() }
                                    localClaim={ ListType.ADD_EXTERNAL }
                                    onEdit={ onExternalClaimEdit }
                                    onDelete={ onExternalClaimDelete }
                                    data-testid={ `${ testId }-list` }
                                    attributeType={ attributeType }
                                />
                            )
                            : wizard && (
                                <EmptyPlaceholder
                                    title={ t("console:manage.features.claims.external.placeholders.empty.title",
                                        { type: resolveType(attributeType, true) }) }
                                    subtitle={ [ t("console:manage.features.claims.external." +
                                        "placeholders.empty.subtitle", { type: resolveType(attributeType) }) ] }
                                    image={ getEmptyPlaceholderIllustrations().emptyList }
                                    imageSize="tiny"
                                    data-testid={ `${ testId }-empty-placeholder` }
                                />
                            )
                    }
                </Grid.Column>
            </Grid.Row>
        </Grid>
    );
};

/**
 * Default props for the application creation wizard.
 */
ExternalClaims.defaultProps = {
    attributeType: ClaimManagementConstants.OTHERS,
    "data-testid": "external-claims",
    shouldShowInitialValues: true,
    wizard: true
};
