"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouteAccessProvider = RouteAccessProvider;
exports.useRouteAccessRouters = useRouteAccessRouters;
exports.default = RouteAccessBoundary;
const tslib_1 = require("tslib");
const react_1 = tslib_1.__importStar(require("react"));
const react_router_dom_1 = require("react-router-dom");
const routeAccess_1 = require("../../features/routeAccess");
const features_1 = require("./features");
const routeAccess_2 = require("./routeAccess");
const NamespaceConfigProvider_1 = require("./NamespaceConfigProvider");
const RouteAccessContext = react_1.default.createContext({
    routers: [],
});
function RouteAccessProvider(props) {
    const { routers, children } = props;
    return (<RouteAccessContext.Provider value={{ routers }}>
            {children}
        </RouteAccessContext.Provider>);
}
function useRouteAccessRouters() {
    return (0, react_1.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] = (0, react_1.useState)(0);
    (0, react_1.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('|')]);
}
function RouteAccessBoundary(props) {
    const { namespace, routers: propsRouters, children, requireLogin = false, unconfiguredAccess = 'allow', resolveUnconfiguredAccess, renderFallback, renderLogin, renderDenied, renderContextMismatch, renderPending, renderError, } = props;
    const features = (0, features_1.useFeatures)();
    const location = (0, react_router_dom_1.useLocation)();
    const contextRouters = useRouteAccessRouters();
    const routers = propsRouters || contextRouters;
    const namespaceConfig = (0, NamespaceConfigProvider_1.useNamespaceConfig)(namespace);
    const consoleContextEntities = namespaceConfig?.features?.console?.contextEntities;
    useFeatureVersion(features, ['token', 'console', 'cache', 'navigator']);
    const pathname = location.pathname;
    const route = (0, react_1.useMemo)(() => (0, routeAccess_2.getRoute)(routers, namespace, pathname), [routers, namespace, pathname]);
    const url = (0, react_1.useMemo)(() => `/${(0, routeAccess_2.normalizeUrlPath)(pathname, namespace)}`, [pathname, namespace]);
    const access = route?.meta?.access;
    const resolveRefAccess = (0, react_1.useMemo)(() => (0, routeAccess_2.createRouteRefAccessResolver)(routers, namespace, consoleContextEntities), [routers, namespace, consoleContextEntities]);
    const resolvedAccess = (0, routeAccess_1.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,
                })}</>;
        }
    }
}
