import React, { useState, useContext, useEffect, useCallback, useMemo } from 'react';

import { ResourceBrowserContext, ResourceBrowserContextProvider } from './ResourceBrowserContext/ResourceBrowserContext';
import {
    InlineType,
    PluginLaunchMode,
    ResourceBrowserUnresolvedResource,
    ResourceBrowserResource,
    ResourceBrowserSourceWithPlugin,
    ResourceBrowserPluginType,
} from './types';
import { useSources } from './Hooks/useSources';
import { PluginRender } from './Plugin/Plugin';
import { AuthProvider, useAuthContext, AuthContext } from './ResourceBrowserContext/AuthProvider';

import BrowseToSource from './BrowseToSource/BrowseToSource';
import SourceDropdown from './SourceDropdown/SourceDropdown';
import SourceDropdownContainer from './SourceDropdownContainer/SourceDropdownContainer';

export {
    ResourceBrowserContext,
    ResourceBrowserContextProvider,
    useAuthContext,
    AuthProvider,
    AuthContext,
    BrowseToSource,
    SourceDropdown,
    SourceDropdownContainer,
};
export * from './types';

export type ResourceBrowserProps = {
    modalTitle: string;
    allowedTypes?: string[];
    allowedPlugins?: ResourceBrowserPluginType[];
    isDisabled?: boolean;
    value: ResourceBrowserUnresolvedResource | null;
    inline?: boolean; // Will render open button only, no input / showing of existing selection
    inlineType?: InlineType; // Type of inline button to show
    onChange(resource: ResourceBrowserResource | null): void;
    onModalStateChange?(isOpen: boolean): void;
    onClear?(): void;
};

export const ResourceBrowser = React.forwardRef<HTMLButtonElement, ResourceBrowserProps>((props, forwardRef) => {
    const { value, inline, inlineType, allowedPlugins, onModalStateChange: onModalStateChangeExternalNotification } = props;
    const [error, setError] = useState<Error | null>(null);
    const { onRequestSources, searchEnabled, plugins: allPlugins } = useContext(ResourceBrowserContext);

    const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
    const [source, setSource] = useState<ResourceBrowserSourceWithPlugin | null>(null);
    const plugins = useMemo(() => {
        // Allow some usages e.g. plugin specific like MatrixAssetWidget restrict what plugins show
        if (!allowedPlugins) return allPlugins; // No restrictions, return all
        return allPlugins.filter((plugin) => {
            return allowedPlugins.includes(plugin.type); // Restrict based on plugin type
        });
    }, [allowedPlugins, allPlugins]);
    const [mode, setMode] = useState<PluginLaunchMode | null>(null);
    const { data: sources, isLoading, error: sourcesError, reload: reloadSources } = useSources({ onRequestSources, plugins });
    const plugin = source?.plugin || null;

    // Find source by its id or alias
    const findSourceById = (value: ResourceBrowserUnresolvedResource, sources: ResourceBrowserSourceWithPlugin[]) => {
        let newSource = sources.find((source) => source.id === value?.sourceId) || null;
        if (!newSource) {
            newSource =
                sources.find((source) => {
                    if (!source.aliases) return false;
                    return source.aliases.includes(value.sourceId);
                }) || null;
        }
        return newSource;
    };

    // Check if the value is for the source
    const isValueForSource = (value: ResourceBrowserUnresolvedResource, source: ResourceBrowserSourceWithPlugin) => {
        return value?.sourceId === source.id || (source.aliases && source.aliases.includes(value?.sourceId));
    };

    // MainContainer will render a list of sources of one is not provided to it, callback to allow it to set the source once a user selects
    const handleSourceSelect = useCallback(
        (source: ResourceBrowserSourceWithPlugin, mode?: PluginLaunchMode) => {
            setSource(source);
            setMode(mode || null);
        },
        [setSource, setMode],
    );

    // If an existing resource is passed in auto select its source
    useEffect(() => {
        let newSource: ResourceBrowserSourceWithPlugin | null = null;
        setError(null);
        if (source !== null) {
            return;
        }

        // If there is a provided value try to use its source
        if (value) {
            // Search the sources for it matching against the value.source property
            newSource = findSourceById(value, sources);

            // If the source is null and we arent loading the sources
            if (newSource === null && !isLoading) {
                // Set an error as the passed in value's source wasnt returned by onRequestSources
                setError(new Error('Unable to find resource source.'));
            }
        } else if (sources?.length === 1 && !searchEnabled) {
            // If only one source is passed and search is not enabled select it automatically
            newSource = sources[0];
        }

        setSource(newSource);
        setMode(null); // Passed in resource will always use the default mode
    }, [value, isLoading, sources, source]);

    // The modal has some control over it own open/closed state (for WCAG reasons) so keep this in sync with our state
    const handleModalStateChange = useCallback(
        (isOpen: boolean) => {
            setIsModalOpen(isOpen);
            onModalStateChangeExternalNotification?.(isOpen);
        },
        [setIsModalOpen, onModalStateChangeExternalNotification],
    );

    // If the modal closes and we dont have a value clear the source state so it goes back to the launcher on re-open
    useEffect(() => {
        // If modal is closed and we dont have a value
        if (!isModalOpen && !value && (sources?.length > 1 || searchEnabled)) {
            setSource(null);
            setMode(null);
        } else if (!isModalOpen) {
            // Otherwise reset the RB mode
            setMode(null);

            // If there is a value passed in, reset the source so the preselected asset preview renders correctly
            if (value && value.sourceId !== source?.id) {
                setSource(findSourceById(value, sources));
            }
        }
    }, [sources, isModalOpen]);

    // Reset function to allow user manual reload if sources fail in inline usage
    const handleReset = useCallback(() => {
        reloadSources();
    }, [reloadSources]);

    // Render a default "plugin" and one for each item in the plugins array. They are conditionally rendered based on what is selected
    return (
        <div className="squiz-rb-scope">
            <PluginRender
                key="default"
                ref={forwardRef} // This is passed to every plugin but only actually render on one at a time
                type={null}
                render={plugin === null}
                inline={!!inline}
                inlineType={inlineType}
                {...props}
                source={source}
                sources={sources}
                setSource={handleSourceSelect}
                isLoading={isLoading}
                isOtherSourceValue={false}
                error={sourcesError || error}
                plugin={plugin}
                pluginMode={mode}
                searchEnabled={searchEnabled}
                useResource={() => {
                    return {
                        data: null,
                        error: null,
                        isLoading: false,
                    };
                }}
                isModalOpen={isModalOpen}
                onModalStateChange={handleModalStateChange}
                onRetry={handleReset}
            />
            {plugins.map((thisPlugin) => {
                return (
                    <PluginRender
                        key={thisPlugin.type}
                        ref={forwardRef} // This is passed to every plugin but only actually render on one at a time
                        type={thisPlugin.type}
                        render={thisPlugin.type === plugin?.type}
                        inline={!!inline}
                        inlineType={inlineType}
                        {...props}
                        value={value && source ? (isValueForSource(value, source) ? value : null) : null} // Don't pass value if value isn't for selected source
                        isOtherSourceValue={value && source ? (!isValueForSource(value, source) ? true : false) : false} // Block render of existing asset if not from this source
                        source={source}
                        sources={sources}
                        setSource={handleSourceSelect}
                        isLoading={isLoading}
                        error={sourcesError || error}
                        plugin={plugin}
                        pluginMode={mode}
                        searchEnabled={searchEnabled}
                        useResource={thisPlugin.useResolveResource}
                        isModalOpen={isModalOpen}
                        onModalStateChange={handleModalStateChange}
                        onRetry={handleReset}
                    />
                );
            })}
        </div>
    );
});
