import React, { useContext, useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { resolveRouteAccess, } from '../../features/routeAccess';
import { useFeatures } from './features';
import { createRouteRefAccessResolver, getRoute, normalizeUrlPath, } from './routeAccess';
import { useNamespaceConfig } from './NamespaceConfigProvider';
const RouteAccessContext = React.createContext({
    routers: [],
});
export function RouteAccessProvider(props) {
    const { routers, children } = props;
    return (<RouteAccessContext.Provider value={{ routers }}>
            {children}
        </RouteAccessContext.Provider>);
}
export function useRouteAccessRouters() {
    return useContext(RouteAccessContext).routers;
}
function toBoundaryResult(result) {
    if (result === true) {
        return 'allow';
    }
    if (result === false) {
        return 'deny';
    }
    if (result === null) {
        return 'contextMismatch';
    }
    return result;
}
function resolveUnconfiguredResult(context, policy, resolver) {
    const resolved = resolver ? toBoundaryResult(resolver(context)) : undefined;
    const result = resolved || policy;
    if (result === 'login') {
        return context.features.token?.getTokenValue?.() ? 'allow' : 'login';
    }
    return result;
}
function resolveBoundaryResult(accessResult, context, policy, resolver, hasConfiguredAccess) {
    if (accessResult.type === 'unchecked') {
        return resolveUnconfiguredResult(context, policy, hasConfiguredAccess ? undefined : resolver);
    }
    return accessResult.type;
}
function renderNode(node, context) {
    return typeof node === 'function' ? node(context) : node ?? null;
}
function renderSpecificOrFallback(specific, fallback, context, fallbackContext) {
    return specific !== undefined
        ? renderNode(specific, context)
        : renderNode(fallback, fallbackContext);
}
function useFeatureVersion(features, featureNames) {
    const [, setVersion] = useState(0);
    useEffect(() => {
        const unsubscribes = featureNames
            .map((featureName) => features[featureName])
            .filter((feature) => !!feature && typeof feature.subscribe === 'function')
            .map((feature) => feature.subscribe(() => setVersion((version) => version + 1)));
        return () => {
            unsubscribes.forEach((unsubscribe) => unsubscribe());
        };
    }, [features, featureNames.join('|')]);
}
export default function RouteAccessBoundary(props) {
    const { namespace, routers: propsRouters, children, requireLogin = false, unconfiguredAccess = 'allow', resolveUnconfiguredAccess, renderFallback, renderLogin, renderDenied, renderContextMismatch, renderPending, renderError, } = props;
    const features = useFeatures();
    const location = useLocation();
    const contextRouters = useRouteAccessRouters();
    const routers = propsRouters || contextRouters;
    const namespaceConfig = useNamespaceConfig(namespace);
    const consoleContextEntities = namespaceConfig?.features?.console?.contextEntities;
    useFeatureVersion(features, ['token', 'console', 'cache', 'navigator']);
    const pathname = location.pathname;
    const route = useMemo(() => getRoute(routers, namespace, pathname), [routers, namespace, pathname]);
    const url = useMemo(() => `/${normalizeUrlPath(pathname, namespace)}`, [pathname, namespace]);
    const access = route?.meta?.access;
    const resolveRefAccess = useMemo(() => createRouteRefAccessResolver(routers, namespace, consoleContextEntities), [routers, namespace, consoleContextEntities]);
    const resolvedAccess = resolveRouteAccess(access, features, {
        resolveRefAccess,
        consoleContextEntities,
    });
    const accessIsUnresolved = resolvedAccess.type === 'pending' || resolvedAccess.type === 'error';
    const needsLogin = !accessIsUnresolved && requireLogin && !features.token?.getTokenValue?.();
    const accessResult = needsLogin ? undefined : resolvedAccess;
    const context = {
        url,
        pathname,
        route,
        access,
        accessResult,
        features,
    };
    const result = accessResult
        ? resolveBoundaryResult(accessResult, context, unconfiguredAccess, resolveUnconfiguredAccess, !!access)
        : 'login';
    const renderContext = {
        ...context,
        result,
    };
    switch (result) {
        case 'allow': {
            return <>{children}</>;
        }
        case 'login': {
            return <>{renderSpecificOrFallback(renderLogin, renderFallback, renderContext, {
                    ...context,
                    result,
                })}</>;
        }
        case 'deny': {
            return <>{renderSpecificOrFallback(renderDenied, renderFallback, renderContext, {
                    ...context,
                    result,
                })}</>;
        }
        case 'contextMismatch': {
            return <>{renderSpecificOrFallback(renderContextMismatch, renderFallback, renderContext, {
                    ...context,
                    result,
                })}</>;
        }
        case 'pending': {
            return <>{renderSpecificOrFallback(renderPending, renderFallback, renderContext, {
                    ...context,
                    result,
                })}</>;
        }
        case 'error': {
            return <>{renderSpecificOrFallback(renderError, renderFallback, renderContext, {
                    ...context,
                    result,
                })}</>;
        }
        default: {
            return <>{renderNode(renderFallback, {
                    ...context,
                    result,
                })}</>;
        }
    }
}
