import * as Solid from 'solid-js';
import { mergeRefs } from '@solid-primitives/refs';
import { deepEqual, functionalUpdate, getUrlScheme, hasKeys, isDangerousProtocol, preloadWarning, removeTrailingSlash, } from '@tanstack/router-core';
import { isServer } from '@tanstack/router-core/isServer';
import { Dynamic } from 'solid-js/web';
import { useRouter } from './useRouter';
import { useIntersectionObserver } from './utils';
import { useHydrated } from './ClientOnly';
const timeoutMap = new WeakMap();
const cancelPreload = (eventTarget) => {
    clearTimeout(timeoutMap.get(eventTarget));
    timeoutMap.delete(eventTarget);
};
export function useLinkProps(options) {
    const router = useRouter();
    const [local, rest] = Solid.splitProps(Solid.mergeProps({
        activeProps: STATIC_ACTIVE_PROPS_GET,
        inactiveProps: STATIC_INACTIVE_PROPS_GET,
    }, options), [
        'activeProps',
        'inactiveProps',
        'activeOptions',
        'to',
        'preload',
        'preloadDelay',
        'preloadIntentProximity',
        'hashScrollIntoView',
        'replace',
        'startTransition',
        'resetScroll',
        'viewTransition',
        'target',
        'disabled',
        'style',
        'class',
        'onClick',
        'onBlur',
        'onFocus',
        'onMouseEnter',
        'onMouseLeave',
        'onMouseOver',
        'onMouseOut',
        'onTouchStart',
        'ignoreBlocker',
    ]);
    // const {
    //   // custom props
    //   activeProps = () => ({ class: 'active' }),
    //   inactiveProps = () => ({}),
    //   activeOptions,
    //   to,
    //   preload: userPreload,
    //   preloadDelay: userPreloadDelay,
    //   hashScrollIntoView,
    //   replace,
    //   startTransition,
    //   resetScroll,
    //   viewTransition,
    //   // element props
    //   children,
    //   target,
    //   disabled,
    //   style,
    //   class,
    //   onClick,
    //   onFocus,
    //   onMouseEnter,
    //   onMouseLeave,
    //   onTouchStart,
    //   ignoreBlocker,
    //   ...rest
    // } = options
    const [_, propsSafeToSpread] = Solid.splitProps(rest, [
        'params',
        'search',
        'hash',
        'state',
        'mask',
        'reloadDocument',
        'unsafeRelative',
        'from',
        'href',
    ]);
    const currentLocation = Solid.createMemo(() => router.stores.location.get(), undefined, { equals: (prev, next) => prev.href === next.href });
    const next = Solid.createMemo(() => {
        // Rebuild when inherited search/hash or the current route context changes.
        const _fromLocation = currentLocation();
        const nextOptions = { _fromLocation, ...options };
        // untrack because router-core will also access stores, which are signals in solid
        return Solid.untrack(() => router.buildLocation(nextOptions));
    });
    const hrefOption = Solid.createMemo(() => {
        if (options.disabled)
            return undefined;
        // Use publicHref - it contains the correct href for display
        // When a rewrite changes the origin, publicHref is the full URL
        // Otherwise it's the origin-stripped path
        // This avoids constructing URL objects in the hot path
        const location = next().maskedLocation ?? next();
        const publicHref = location.publicHref;
        const external = location.external;
        const href = external
            ? publicHref
            : router.history.createHref(publicHref) || '/';
        if ((external || href !== publicHref) &&
            isDangerousProtocol(href, router.protocolAllowlist)) {
            if (process.env.NODE_ENV !== 'production') {
                console.warn(`Blocked Link with dangerous protocol: ${href}`);
            }
            return undefined;
        }
        return href;
    });
    const externalLink = Solid.createMemo(() => {
        const to = options.to;
        const scheme = typeof to === 'string' && getUrlScheme(to);
        if (scheme) {
            if (!router.protocolAllowlist.has(scheme)) {
                if (process.env.NODE_ENV !== 'production') {
                    console.warn(`Blocked Link with dangerous protocol: ${to}`);
                }
                return null;
            }
            return to;
        }
        const _href = hrefOption();
        if (!_href && !options.disabled) {
            return null;
        }
        return _href && getUrlScheme(_href) ? _href : undefined;
    });
    const shouldHydrateHash = !isServer && !!router.options.ssr;
    const hasHydrated = (isServer ?? router.isServer) ? undefined : useHydrated();
    const isActive = Solid.createMemo(() => {
        if (externalLink() !== undefined) {
            return false;
        }
        const activeOptions = local.activeOptions;
        const current = currentLocation();
        const nextLocation = next();
        const currentPath = removeTrailingSlash(current.pathname, router.basepath);
        const nextPath = removeTrailingSlash(nextLocation.pathname, router.basepath);
        // Both modes compare normalized paths; fuzzy matches need a segment boundary.
        if (activeOptions?.exact
            ? currentPath !== nextPath
            : !(currentPath.startsWith(nextPath) &&
                (currentPath.length === nextPath.length ||
                    currentPath[nextPath.length] === '/'))) {
            return false;
        }
        if (activeOptions?.includeSearch ?? true) {
            const searchTest = deepEqual(current.search, nextLocation.search, {
                partial: !activeOptions?.exact,
                ignoreUndefined: !activeOptions?.explicitUndefined,
            });
            if (!searchTest) {
                return false;
            }
        }
        if (activeOptions?.includeHash) {
            const currentHash = shouldHydrateHash && !hasHydrated?.() ? '' : current.hash;
            return currentHash === nextLocation.hash;
        }
        return true;
    });
    const simpleStyling = Solid.createMemo(() => local.activeProps === STATIC_ACTIVE_PROPS_GET &&
        local.inactiveProps === STATIC_INACTIVE_PROPS_GET &&
        local.class === undefined &&
        local.style === undefined);
    const resolveLinkStateProps = (base) => {
        const active = isActive();
        if (simpleStyling()) {
            return {
                ...base,
                ...(active && STATIC_DEFAULT_ACTIVE_ATTRIBUTES),
            };
        }
        // Active and inactive props are mutually exclusive.
        const stateProps = active
            ? (functionalUpdate(local.activeProps, {}) ?? EMPTY_OBJECT)
            : functionalUpdate(local.inactiveProps, {});
        const style = {
            ...local.style,
            ...stateProps.style,
        };
        const className = [local.class, stateProps.class].filter(Boolean).join(' ');
        return {
            ...stateProps,
            ...base,
            ...(hasKeys(style) ? { style } : undefined),
            ...(className ? { class: className } : undefined),
            ...(active && STATIC_ACTIVE_ATTRIBUTES),
        };
    };
    // Keep the guard inline so browser builds can drop the server return.
    if (isServer ?? router.isServer) {
        const external = externalLink();
        const disabled = local.disabled || external === null;
        const props = resolveLinkStateProps({
            onClick: local.onClick,
            onBlur: local.onBlur,
            onFocus: local.onFocus,
            onMouseEnter: local.onMouseEnter,
            onMouseLeave: local.onMouseLeave,
            onMouseOut: local.onMouseOut,
            onMouseOver: local.onMouseOver,
            onTouchStart: local.onTouchStart,
            href: external === null ? undefined : external || hrefOption(),
            ref: options.ref,
            disabled,
            target: local.target,
            ...(disabled && STATIC_DISABLED_PROPS),
        });
        // Avoid creating merged-prop getters for absent server event handlers.
        for (const key of STATIC_EVENT_PROPS) {
            if (props[key] === undefined) {
                delete props[key];
            }
        }
        return Solid.mergeProps(propsSafeToSpread, props);
    }
    let hasRenderFetched = false;
    const preload = Solid.createMemo(() => {
        if (options.reloadDocument ||
            externalLink() !== undefined ||
            local.disabled) {
            return false;
        }
        return local.preload ?? router.options.defaultPreload;
    });
    const preloadDelay = () => local.preloadDelay ?? router.options.defaultPreloadDelay ?? 0;
    const doPreload = () => router
        .preloadRoute(options)
        .catch((err) => {
        console.warn(err);
        console.warn(preloadWarning);
    });
    const [ref, setRef] = Solid.createSignal(null);
    const enqueuePreload = (e) => {
        if (!e) {
            cancelPreload(ref);
            return;
        }
        if (!(e.isIntersecting ??
            preload() === 'intent')) {
            if (e.isIntersecting === false) {
                cancelPreload(ref);
            }
            return;
        }
        if (!preloadDelay()) {
            doPreload();
            return;
        }
        if (!timeoutMap.has(ref)) {
            timeoutMap.set(ref, setTimeout(() => {
                timeoutMap.delete(ref);
                doPreload();
            }, preloadDelay()));
        }
    };
    useIntersectionObserver(ref, enqueuePreload, () => preload() !== 'viewport');
    Solid.createEffect(() => {
        if (hasRenderFetched) {
            return;
        }
        if (preload() === 'render') {
            doPreload();
            hasRenderFetched = true;
        }
    });
    // The click handler
    const handleClick = (e) => {
        // Check actual element's target attribute as fallback
        const elementTarget = e.currentTarget.getAttribute('target');
        const effectiveTarget = local.target !== undefined ? local.target : elementTarget;
        if (!local.disabled &&
            externalLink() === undefined &&
            !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) &&
            !e.defaultPrevented &&
            (!effectiveTarget || effectiveTarget === '_self') &&
            e.button === 0) {
            e.preventDefault();
            // All is well? Navigate!
            // N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
            router.navigate({
                ...options,
                replace: local.replace,
                resetScroll: local.resetScroll,
                hashScrollIntoView: local.hashScrollIntoView,
                startTransition: local.startTransition,
                viewTransition: local.viewTransition,
                ignoreBlocker: local.ignoreBlocker,
            });
        }
    };
    const handleTouchStart = () => {
        if (preload() !== 'intent')
            return;
        doPreload();
    };
    const handleLeave = () => {
        if (preload() === 'intent') {
            cancelPreload(ref);
        }
    };
    const onClick = createComposedHandler(() => local.onClick, handleClick);
    const onBlur = createComposedHandler(() => local.onBlur, handleLeave);
    const onFocus = createComposedHandler(() => local.onFocus, enqueuePreload);
    const onMouseEnter = createComposedHandler(() => local.onMouseEnter, enqueuePreload);
    const onMouseOver = createComposedHandler(() => local.onMouseOver, enqueuePreload);
    const onMouseLeave = createComposedHandler(() => local.onMouseLeave, handleLeave);
    const onMouseOut = createComposedHandler(() => local.onMouseOut, handleLeave);
    const onTouchStart = createComposedHandler(() => local.onTouchStart, handleTouchStart);
    const resolvedProps = Solid.createMemo(() => {
        const external = externalLink();
        const disabled = local.disabled || external === null;
        const base = {
            href: external === null ? undefined : external || hrefOption(),
            ref: mergeRefs(setRef, options.ref),
            onClick,
            onBlur,
            onFocus,
            onMouseEnter,
            onMouseOver,
            onMouseLeave,
            onMouseOut,
            onTouchStart,
            disabled,
            target: local.target,
            ...(disabled && STATIC_DISABLED_PROPS),
        };
        return resolveLinkStateProps(base);
    });
    return Solid.mergeProps(propsSafeToSpread, resolvedProps);
}
const STATIC_EVENT_PROPS = [
    'onClick',
    'onBlur',
    'onFocus',
    'onMouseEnter',
    'onMouseLeave',
    'onMouseOut',
    'onMouseOver',
    'onTouchStart',
];
const STATIC_ACTIVE_PROPS = { class: 'active' };
const STATIC_ACTIVE_PROPS_GET = () => STATIC_ACTIVE_PROPS;
const EMPTY_OBJECT = {};
const STATIC_INACTIVE_PROPS_GET = () => EMPTY_OBJECT;
const STATIC_DEFAULT_ACTIVE_ATTRIBUTES = {
    class: 'active',
    'data-status': 'active',
    'aria-current': 'page',
};
const STATIC_DISABLED_PROPS = {
    role: 'link',
    'aria-disabled': true,
};
const STATIC_ACTIVE_ATTRIBUTES = {
    'data-status': 'active',
    'aria-current': 'page',
};
/** Call a JSX.EventHandlerUnion with the event. */
function callHandler(event, handler) {
    if (typeof handler === 'function') {
        handler(event);
    }
    else {
        handler[0](handler[1], event);
    }
    return event.defaultPrevented;
}
function createComposedHandler(getHandler, fallback) {
    return (event) => {
        const handler = getHandler();
        if (!handler || !callHandler(event, handler))
            fallback(event);
    };
}
export function createLink(Comp) {
    return (props) => <Link {...props} _asChild={Comp}/>;
}
export const Link = (props) => {
    const [local, rest] = Solid.splitProps(props, ['_asChild', 'children']);
    const [_, linkProps] = Solid.splitProps(useLinkProps(rest), ['type']);
    const children = Solid.createMemo(() => {
        const ch = local.children;
        if (typeof ch === 'function') {
            return ch({
                get isActive() {
                    return linkProps['data-status'] === 'active';
                },
            });
        }
        return ch;
    });
    if (local._asChild === 'svg') {
        const [_, svgLinkProps] = Solid.splitProps(linkProps, ['class']);
        return (<svg>
        <a {...svgLinkProps}>{children()}</a>
      </svg>);
    }
    if (!local._asChild) {
        return <a {...linkProps}>{children()}</a>;
    }
    return (<Dynamic component={local._asChild} {...linkProps}>
      {children()}
    </Dynamic>);
};
export const linkOptions = (options) => {
    return options;
};
//# sourceMappingURL=link.jsx.map