import React, { useEffect, useState, useMemo } from "react";
import { FollowerNode, ChannelEvents } from "@local-logic/channel";
import { useSearch, useNavigate, useLocation } from "@tanstack/react-location";
import once from "lodash.once";
import i18n from "../../i18n";
import defaultState from "./defaultState";
import { primitiveUtils } from "~/utils";
import useEffectOnce from "~/hooks/useEffectOnce";
import useDebounce from "~/hooks/useDebounce";
import errorTracker from "~/utils/errorTracker";
// Using once to get around generic Context issue: https://stackoverflow.com/a/61020816/4282290
const createStateManagerContext = once(() => React.createContext({}));
const useStateManagerContext = () => {
    const context = React.useContext(createStateManagerContext());
    if (!context) {
        throw new Error("useStateManagerContext must be used under StateManagerProvider");
    }
    return context;
};
/**
 * Formats the state so that clientId is always extracted from the apiKey and
 * "V3 " prefix is optional in the "apiKey" itself when implementing the SDK.
 */
function formatSDKState(state) {
    var _a, _b, _c, _d, _e;
    let noPrefixApiKey = state.apiKey || "";
    if (noPrefixApiKey.startsWith("V3 ")) {
        noPrefixApiKey = noPrefixApiKey.slice(3);
    }
    const newState = Object.assign(Object.assign({}, state), { apiKey: `V3 ${noPrefixApiKey}`, clientId: noPrefixApiKey.split(".")[0] });
    if (typeof ((_a = state === null || state === void 0 ? void 0 : state.options) === null || _a === void 0 ? void 0 : _a.lat) === "string" ||
        typeof ((_b = state === null || state === void 0 ? void 0 : state.options) === null || _b === void 0 ? void 0 : _b.lng) === "string") {
        if (!newState.options) {
            newState.options = {};
        }
        newState.options.lat = Number((_c = state === null || state === void 0 ? void 0 : state.options) === null || _c === void 0 ? void 0 : _c.lat);
        newState.options.lng = Number((_d = state === null || state === void 0 ? void 0 : state.options) === null || _d === void 0 ? void 0 : _d.lng);
        // This patch is for LocalContent
        // @ts-expect-error marker exists in runtime but not in generic type
        if (typeof ((_e = state === null || state === void 0 ? void 0 : state.options) === null || _e === void 0 ? void 0 : _e.marker) !== "undefined") {
            let convertedMarkerToNumber = false;
            // @ts-expect-error marker exists in runtime but not in generic type
            if (typeof state.options.marker.lat === "string") {
                // @ts-expect-error marker exists in runtime but not in generic type
                newState.options.marker.lat = Number(state.options.marker.lat);
                convertedMarkerToNumber = true;
            }
            // @ts-expect-error marker exists in runtime but not in generic type
            if (typeof state.options.marker.lng === "string") {
                // @ts-expect-error marker exists in runtime but not in generic type
                newState.options.marker.lng = Number(state.options.marker.lng);
                convertedMarkerToNumber = true;
            }
            if (convertedMarkerToNumber) {
                console.warn("[Local Logic SDK] Converted lat/lng from string to number in sdkState.options.marker. Please ensure lat/lng are always numbers. This may break your SDK implementation in the future.");
            }
        }
        console.warn("[Local Logic SDK] Converted lat/lng from string to number in sdkState.options. Please ensure lat/lng are always numbers. This may break your SDK implementation in the future.");
    }
    return newState;
}
const StateManagerProvider = React.memo(({ children }) => {
    var _a;
    const StateManagerContext = createStateManagerContext();
    const search = useSearch();
    const sdkStateRef = React.useRef(search);
    const location = useLocation();
    const pathSegments = location.current.pathname.split("/");
    const currentRoute = pathSegments[pathSegments.length - 1];
    const defaultSDKState = defaultState[currentRoute];
    const followerNodeRef = React.useRef();
    const [isConnected, setIsConnected] = useState(false);
    const [sdkState, _setSdkState] = useState(formatSDKState(defaultSDKState));
    const debouncedSdkState = useDebounce(sdkState, 500);
    const navigate = useNavigate();
    const setSdkState = (data) => {
        sdkStateRef.current = formatSDKState(data);
        _setSdkState(formatSDKState(data));
    };
    const handleMessage = (e) => {
        var _a;
        if (e.name === ChannelEvents.GET_STATE) {
            (_a = followerNodeRef.current) === null || _a === void 0 ? void 0 : _a.message(ChannelEvents.GET_STATE_RESPONSE, sdkStateRef.current, e.eventId);
        }
        if (e.name === ChannelEvents.SET_STATE) {
            setSdkState(e.data);
        }
    };
    /**
     * The `.connect()` event should only every be triggered once.
     */
    useEffectOnce(() => {
        const parentOrigin = new URLSearchParams(window.location.search).get("parentOrigin");
        const followerNode = new FollowerNode(search.channelId, {
            targetOrigin: parentOrigin || document.referrer,
            window: window.parent,
        });
        followerNodeRef.current = followerNode;
        followerNode === null || followerNode === void 0 ? void 0 : followerNode.onMessage(handleMessage);
        followerNode === null || followerNode === void 0 ? void 0 : followerNode.connect().then((resp) => {
            const mergedState = primitiveUtils.object.mergeDeep(sdkState, resp.data);
            setSdkState(mergedState);
            setIsConnected(true);
            errorTracker.addBreadCrumb("stateManager", "useEffectOnce: Connected to sdks-js", {
                oldState: sdkState,
                newState: resp.data,
                mergedState,
            });
        });
    });
    const onChange = (e) => {
        var _a;
        (_a = followerNodeRef.current) === null || _a === void 0 ? void 0 : _a.message(ChannelEvents.ON_CHANGE, e);
        errorTracker.addBreadCrumb("stateManager", "onChange", {
            data: e,
        });
    };
    useEffect(() => {
        navigate({
            search: () => debouncedSdkState,
            replace: true,
        });
    }, [debouncedSdkState]);
    useEffect(() => {
        if (sdkState.options) {
            onChange({ type: "options", data: sdkState.options });
        }
    }, [sdkState.options]);
    const contextValue = useMemo(() => ({
        sdkState,
        setSdkState,
        onChange,
        followerNode: followerNodeRef.current,
    }), [sdkState, setSdkState, onChange]);
    useEffect(() => {
        var _a, _b;
        // Avoids unecessarily setting the language multiple times
        if (!isConnected || !((_a = sdkState.globalOptions) === null || _a === void 0 ? void 0 : _a.locale)) {
            return;
        }
        const language = ((_b = sdkState.globalOptions) === null || _b === void 0 ? void 0 : _b.locale) || "en";
        if (i18n.language !== language) {
            errorTracker.addBreadCrumb("stateManager", "i18n.changeLanguage", {
                language,
            });
            i18n.changeLanguage(language);
        }
    }, [(_a = sdkState.globalOptions) === null || _a === void 0 ? void 0 : _a.locale, isConnected]);
    if (!isConnected) {
        return null;
    }
    return (React.createElement(StateManagerContext.Provider, { value: contextValue }, children));
});
StateManagerProvider.displayName = "StateManagerProvider";
export { useStateManagerContext, StateManagerProvider };
//# sourceMappingURL=index.js.map