import type { FC } from 'react';
import React, { useState, useEffect } from 'react';
import {
    Button,
    Card,
    Checkbox,
    Label,
    LoadingIndicator,
    MultiSelect,
} from '@theorchard/suite-components';
import { N_A } from 'src/constants';
import { useUpsertTracksAudioAttributesEditsMutation } from 'src/data/mutations/upsertTracksAudioAttributesEdits';
import { useUpsertTracksRightsAttributesEditsMutation } from 'src/data/mutations/upsertTracksRightsAttributesEdits';
import { useAudioAttributesQuery } from 'src/data/queries/audioAttributes';
import { useRightsAttributesQuery } from 'src/data/queries/rightsAttributes';
import type {
    UpsertTrackAudioAttributeInput,
    UpsertTrackRightsAttributeInput,
} from 'src/data/globalTypes';
import type { TrackMetadata } from 'src/product/track';

export const CLASS_NAME = 'ProductTrackAttributesPopoverContent';
const INTL_PREFIX = 'contentReview.productTrackAttributesPopoverContent';

export interface Props {
    tracks: TrackMetadata[];
    selectedRow: TrackMetadata | undefined;
    close: () => void;
}

const attributesToOptions = (
    attributes: { id: string; description: string }[]
) => {
    const items: { label: string; value: string }[] = [];
    if (!Array.isArray(attributes) || !attributes.length) return items;

    const copy = [...attributes];
    copy.sort((a1, a2) => a1.description.localeCompare(a2.description)).forEach(
        attribute => {
            const item = {
                label: attribute.description,
                value: attribute.id,
            };

            if (attribute.description === N_A) items.unshift(item);
            else items.push(item);
        }
    );

    return items;
};

// Whether a track's attribute ids differ before vs. after a bulk apply,
// ignoring order. Used to flag only genuinely-affected tracks as revised.
const attributeIdsChanged = (before: string[], after: string[]) =>
    before.length !== after.length || before.some(id => !after.includes(id));

const ProductTrackAttributesPopoverContent: FC<Props> = ({
    tracks,
    selectedRow,
    close,
}) => {
    const [appendAudioToAll, setAppendAudioToAll] = useState<boolean>(false);
    const [appendRightsToAll, setAppendRightsToAll] = useState<boolean>(false);

    const { data: allAudioAttributes, loading: allAudioAttributesLoading } =
        useAudioAttributesQuery();
    const { data: allRightsAttributes, loading: allRightsAttributesLoading } =
        useRightsAttributesQuery();

    const [updateTracksAudioAttributesEdits] =
        useUpsertTracksAudioAttributesEditsMutation();
    const [updateTracksRightsAttributesEdits] =
        useUpsertTracksRightsAttributesEditsMutation();

    const loading = allAudioAttributesLoading || allRightsAttributesLoading;

    const [audioAttributeOptions, setAudioAttributeOptions] =
        useState<{ label: string; value: string }[]>();
    const [initialAudioSelection, setInitialAudioSelection] =
        useState<{ label: string; value: string }[]>();
    const [currentAudioAttributeSelection, setCurrentAudioAttributeSelection] =
        useState<
            {
                label: string;
                value: string;
            }[]
        >([]);
    const [rightsAttributeOptions, setRightsAttributeOptions] =
        useState<{ label: string; value: string }[]>();
    const [initialRightsSelection, setInitialRightsSelection] =
        useState<{ label: string; value: string }[]>();
    const [
        currentRightsAttributeSelection,
        setCurrentRightsAttributeSelection,
    ] = useState<
        {
            label: string;
            value: string;
        }[]
    >([]);

    enum AttributeType {
        Audio,
        Rights,
    }

    useEffect(() => {
        if (allAudioAttributes) {
            setAudioAttributeOptions(attributesToOptions(allAudioAttributes));
        }
        if (allRightsAttributes) {
            setRightsAttributeOptions(attributesToOptions(allRightsAttributes));
        }
    }, [allAudioAttributes, allRightsAttributes]);

    useEffect(() => {
        if (selectedRow) {
            setAppendAudioToAll(false);
            setAppendRightsToAll(false);
            const audioAttributesAlreadySelected =
                selectedRow.audioAttributesEdits.get();
            const rightsAttributesAlreadySelected =
                selectedRow.rightsAttributesEdits.get();
            setInitialAudioSelection(
                Array.isArray(audioAttributesAlreadySelected)
                    ? attributesToOptions(
                          audioAttributesAlreadySelected.map(
                              item => item.attribute
                          )
                      )
                    : []
            );
            setCurrentAudioAttributeSelection(
                Array.isArray(audioAttributesAlreadySelected)
                    ? attributesToOptions(
                          audioAttributesAlreadySelected.map(
                              item => item.attribute
                          )
                      )
                    : []
            );
            setInitialRightsSelection(
                Array.isArray(rightsAttributesAlreadySelected)
                    ? attributesToOptions(
                          rightsAttributesAlreadySelected.map(
                              item => item.attribute
                          )
                      )
                    : []
            );
            setCurrentRightsAttributeSelection(
                Array.isArray(rightsAttributesAlreadySelected)
                    ? attributesToOptions(
                          rightsAttributesAlreadySelected.map(
                              item => item.attribute
                          )
                      )
                    : []
            );
        }
    }, [selectedRow]);

    const formatAttributes = (
        attributes: {
            label: string;
            value: string;
        }[]
    ) => {
        const sorted = attributes.sort(
            (a, b) => parseInt(a.value, 10) - parseInt(b.value, 10)
        );
        const formatted = sorted.map(selection => ({
            attribute: {
                description: selection.label,
                id: selection.value,
            },
        }));

        return formatted;
    };

    const applyAttributesToAllTracks = (attributeType: AttributeType) => {
        if (attributeType == AttributeType.Audio) {
            let addedAudioAttributes = currentAudioAttributeSelection.filter(
                ({ value: curId }) =>
                    !initialAudioSelection?.some(
                        ({ value: initId }) => curId === initId
                    )
            );

            const removedAudioAttributes = initialAudioSelection?.filter(
                ({ value: initId }) =>
                    !currentAudioAttributeSelection.some(
                        ({ value: curId }) => curId === initId
                    )
            );

            if (
                addedAudioAttributes.length == 0 &&
                removedAudioAttributes?.length == 0
            ) {
                addedAudioAttributes = currentAudioAttributeSelection;
            }

            tracks.forEach(track => {
                let unpackedExistingTrackAudioAttributes: {
                    label: string;
                    value: string;
                }[] = [];

                const existingTrackAudioAttributes =
                    track.audioAttributesEdits.get();
                if (Array.isArray(existingTrackAudioAttributes)) {
                    existingTrackAudioAttributes.forEach(
                        existingTrackAttribute => {
                            unpackedExistingTrackAudioAttributes.push({
                                label: existingTrackAttribute.attribute
                                    .description,
                                value: existingTrackAttribute.attribute.id,
                            });
                        }
                    );
                }

                const existingAudioIds =
                    unpackedExistingTrackAudioAttributes.map(a => a.value);

                addedAudioAttributes.forEach(addedAttribute => {
                    if (
                        !unpackedExistingTrackAudioAttributes.some(
                            ({ value: value }) => value === addedAttribute.value
                        )
                    ) {
                        unpackedExistingTrackAudioAttributes.push(
                            addedAttribute
                        );
                    }
                });

                removedAudioAttributes?.forEach(removedAttribute => {
                    unpackedExistingTrackAudioAttributes =
                        unpackedExistingTrackAudioAttributes.filter(
                            element => element.value !== removedAttribute.value
                        );
                });

                const formattedAudioEdits = formatAttributes(
                    unpackedExistingTrackAudioAttributes
                );
                const finalAudioIds = formattedAudioEdits.map(
                    edit => edit.attribute.id
                );
                track.audioAttributesEdits.setCurrent(formattedAudioEdits);
                // Only flag as revised when this track's edits actually
                // changed, so apply-to-all doesn't force N/A onto untouched
                // tracks (which would discard their suggestion on Approve).
                if (attributeIdsChanged(existingAudioIds, finalAudioIds))
                    track.audioAttributesEdits.setRevision(formattedAudioEdits);
            });
        } else if (attributeType == AttributeType.Rights) {
            let addedRightsAttributes = currentRightsAttributeSelection.filter(
                ({ value: curId }) =>
                    !initialRightsSelection?.some(
                        ({ value: initId }) => curId === initId
                    )
            );

            const removedRightsAttributes = initialRightsSelection?.filter(
                ({ value: initId }) =>
                    !currentRightsAttributeSelection.some(
                        ({ value: curId }) => curId === initId
                    )
            );

            if (
                addedRightsAttributes.length == 0 &&
                removedRightsAttributes?.length == 0
            ) {
                addedRightsAttributes = currentRightsAttributeSelection;
            }

            tracks.forEach(track => {
                let unpackedExistingTrackRightsAttributes: {
                    label: string;
                    value: string;
                }[] = [];

                const existingTrackRightsAttributes =
                    track.rightsAttributesEdits.get();
                if (Array.isArray(existingTrackRightsAttributes)) {
                    existingTrackRightsAttributes.forEach(
                        existingTrackAttribute => {
                            unpackedExistingTrackRightsAttributes.push({
                                label: existingTrackAttribute.attribute
                                    .description,
                                value: existingTrackAttribute.attribute.id,
                            });
                        }
                    );
                }

                const existingRightsIds =
                    unpackedExistingTrackRightsAttributes.map(a => a.value);

                addedRightsAttributes.forEach(addedAttribute => {
                    if (
                        !unpackedExistingTrackRightsAttributes.some(
                            ({ value: value }) => value === addedAttribute.value
                        )
                    ) {
                        unpackedExistingTrackRightsAttributes.push(
                            addedAttribute
                        );
                    }
                });

                removedRightsAttributes?.forEach(removedAttribute => {
                    unpackedExistingTrackRightsAttributes =
                        unpackedExistingTrackRightsAttributes.filter(
                            element => element.value !== removedAttribute.value
                        );
                });

                const formattedRightsEdits = formatAttributes(
                    unpackedExistingTrackRightsAttributes
                );
                const finalRightsIds = formattedRightsEdits.map(
                    edit => edit.attribute.id
                );
                track.rightsAttributesEdits.setCurrent(formattedRightsEdits);
                // Only flag as revised when this track's edits actually
                // changed, so apply-to-all doesn't force N/A onto untouched
                // tracks (which would discard their suggestion on Approve).
                if (attributeIdsChanged(existingRightsIds, finalRightsIds))
                    track.rightsAttributesEdits.setRevision(
                        formattedRightsEdits
                    );
            });
        }
    };

    const handleUpdateTracksAudioAttributesEdits = () => {
        const input: UpsertTrackAudioAttributeInput[] = [];
        const audioAttributeIds = currentAudioAttributeSelection.map(
            at => at.value
        );

        if (appendAudioToAll) {
            const addedAudioAttributes = currentAudioAttributeSelection
                .filter(
                    ({ value: curId }) =>
                        !initialAudioSelection?.some(
                            ({ value: initId }) => curId === initId
                        )
                )
                .map(at => at.value);

            const removedAudioAttributes = initialAudioSelection
                ?.filter(
                    ({ value: initId }) =>
                        !currentAudioAttributeSelection.some(
                            ({ value: curId }) => curId === initId
                        )
                )
                .map(at => at.value);

            const isModified =
                !!addedAudioAttributes.length ||
                !!removedAudioAttributes?.length;

            input.push(
                ...tracks.map(t => {
                    const existingTrackAudioAttributes =
                        t.audioAttributesEdits.get();

                    let finalAttributesList: string[] =
                        isModified &&
                        Array.isArray(existingTrackAudioAttributes)
                            ? existingTrackAudioAttributes.map(
                                  a => a.attribute.id
                              )
                            : addedAudioAttributes;

                    if (isModified) {
                        finalAttributesList.push(
                            ...addedAudioAttributes.filter(
                                a => !finalAttributesList.includes(a)
                            )
                        );

                        finalAttributesList = !removedAudioAttributes?.length
                            ? finalAttributesList
                            : finalAttributesList.filter(
                                  a => !removedAudioAttributes.includes(a)
                              );
                    }

                    return {
                        tuid: t.tuid.toString(),
                        audioAttributeIds: finalAttributesList,
                    };
                })
            );
        } else {
            input.push({
                tuid: selectedRow?.tuid.toString() || '',
                audioAttributeIds,
            });
        }

        updateTracksAudioAttributesEdits({
            variables: {
                input,
            },
        }).catch(console.error);
    };

    const handleUpdateTracksRightsAttributesEdits = () => {
        const input: UpsertTrackRightsAttributeInput[] = [];
        const rightsAttributeIds = currentRightsAttributeSelection.map(
            at => at.value
        );

        if (appendRightsToAll) {
            const addedRightsAttributes = currentRightsAttributeSelection
                .filter(
                    ({ value: curId }) =>
                        !initialRightsSelection?.some(
                            ({ value: initId }) => curId === initId
                        )
                )
                .map(at => at.value);

            const removedRightsAttributes = initialRightsSelection
                ?.filter(
                    ({ value: initId }) =>
                        !currentRightsAttributeSelection.some(
                            ({ value: curId }) => curId === initId
                        )
                )
                .map(at => at.value);

            const isModified =
                !!addedRightsAttributes.length ||
                !!removedRightsAttributes?.length;

            input.push(
                ...tracks.map(t => {
                    const existingTrackRightsAttributes =
                        t.rightsAttributesEdits.get();

                    let finalAttributesList: string[] =
                        isModified &&
                        Array.isArray(existingTrackRightsAttributes)
                            ? existingTrackRightsAttributes.map(
                                  a => a.attribute.id
                              )
                            : addedRightsAttributes;

                    if (isModified) {
                        finalAttributesList.push(
                            ...addedRightsAttributes.filter(
                                a => !finalAttributesList.includes(a)
                            )
                        );

                        finalAttributesList = !removedRightsAttributes?.length
                            ? finalAttributesList
                            : finalAttributesList.filter(
                                  a => !removedRightsAttributes.includes(a)
                              );
                    }

                    return {
                        tuid: t.tuid.toString(),
                        rightsAttributeIds: finalAttributesList,
                    };
                })
            );
        } else {
            input.push({
                tuid: selectedRow?.tuid.toString() || '',
                rightsAttributeIds,
            });
        }

        updateTracksRightsAttributesEdits({
            variables: {
                input,
            },
        }).catch(console.error);
    };

    const confirm = () => {
        handleUpdateTracksAudioAttributesEdits();
        handleUpdateTracksRightsAttributesEdits();

        if (appendAudioToAll) {
            applyAttributesToAllTracks(AttributeType.Audio);
        } else {
            const formatted = formatAttributes(currentAudioAttributeSelection);
            selectedRow?.audioAttributesEdits?.setCurrent(formatted);
            selectedRow?.audioAttributesEdits?.setRevision(formatted);
        }

        if (appendRightsToAll) {
            applyAttributesToAllTracks(AttributeType.Rights);
        } else {
            const formatted = formatAttributes(currentRightsAttributeSelection);
            selectedRow?.rightsAttributesEdits?.setCurrent(formatted);
            selectedRow?.rightsAttributesEdits?.setRevision(formatted);
        }

        close();
    };

    return (
        <Card.Body
            data-testid={CLASS_NAME}
            className={`${CLASS_NAME}-popoverContent`}
        >
            <Card.Title>{$t(`${INTL_PREFIX}.modalTitle`)}</Card.Title>
            <div className={`${CLASS_NAME}-attributes-section`}>
                <Label text={$t(`${INTL_PREFIX}.multiSelectAudioTitle`)} />
                {loading || initialAudioSelection === undefined ? (
                    <LoadingIndicator />
                ) : (
                    <MultiSelect
                        className={`${CLASS_NAME}-multiselect-audio`}
                        options={audioAttributeOptions}
                        placeholder={$t(
                            `${INTL_PREFIX}.multiSelectPlaceholder`
                        )}
                        initialValue={initialAudioSelection}
                        keepOpenOnSelect={false}
                        menuMaxHeight="300px"
                        menuWidth="320px"
                        onChange={selection => {
                            setCurrentAudioAttributeSelection(selection);
                        }}
                    />
                )}
                <div className={`${CLASS_NAME}-append-to-all-checkbox`}>
                    <Checkbox
                        label={$t(`${INTL_PREFIX}.applyToAllCheckbox`)}
                        disabled={loading}
                        checked={appendAudioToAll}
                        onChange={() => {
                            setAppendAudioToAll(!appendAudioToAll);
                        }}
                    />
                </div>
            </div>
            <div className={`${CLASS_NAME}-attributes-section`}>
                <Label text={$t(`${INTL_PREFIX}.multiSelectRightsTitle`)} />
                {loading || initialRightsSelection === undefined ? (
                    <LoadingIndicator />
                ) : (
                    <MultiSelect
                        className={`${CLASS_NAME}-multiselect-rights`}
                        options={rightsAttributeOptions}
                        placeholder={$t(
                            `${INTL_PREFIX}.multiSelectPlaceholder`
                        )}
                        initialValue={initialRightsSelection}
                        keepOpenOnSelect={false}
                        menuMaxHeight="300px"
                        menuWidth="320px"
                        onChange={selection => {
                            setCurrentRightsAttributeSelection(selection);
                        }}
                    />
                )}
                <div className={`${CLASS_NAME}-append-to-all-checkbox`}>
                    <Checkbox
                        label={$t(`${INTL_PREFIX}.applyToAllCheckbox`)}
                        disabled={loading}
                        checked={appendRightsToAll}
                        onChange={() => {
                            setAppendRightsToAll(!appendRightsToAll);
                        }}
                    />
                </div>
            </div>
            <div className={`${CLASS_NAME}-footer`}>
                <div className={`${CLASS_NAME}-confirmButton`}>
                    <Button
                        size="sm"
                        onClick={() => confirm()}
                        disabled={loading}
                        data-testid={`${CLASS_NAME}-done`}
                    >
                        {$t(`${INTL_PREFIX}.confirmTitle`)}
                    </Button>
                </div>
            </div>
        </Card.Body>
    );
};

export default ProductTrackAttributesPopoverContent;
