/**
 * This file monitors changes to settings and syncs them to the server.
 */
import isEqual from "lodash.isequal";
import { UserSettings } from "revolt-api/types/Sync";
import { ClientboundNotification } from "revolt.js/dist/websocket/notifications";

import { useContext, useEffect } from "preact/hooks";

import { dispatch } from "../../redux";
import { connectState } from "../../redux/connector";
import { Notifications } from "../../redux/reducers/notifications";
import { Settings } from "../../redux/reducers/settings";
import {
    DEFAULT_ENABLED_SYNC,
    SyncData,
    SyncKeys,
    SyncOptions,
} from "../../redux/reducers/sync";

import { Language } from "../Locale";
import { AppContext, ClientStatus, StatusContext } from "./RevoltClient";

type Props = {
    settings: Settings;
    locale: Language;
    sync: SyncOptions;
    notifications: Notifications;
};

const lastValues: { [key in SyncKeys]?: any } = {};

export function mapSync(
    packet: UserSettings,
    revision?: Record<string, number>,
) {
    const update: { [key in SyncKeys]?: [number, SyncData[key]] } = {};
    for (const key of Object.keys(packet)) {
        const [timestamp, obj] = packet[key];
        if (timestamp < (revision ?? {})[key] ?? 0) {
            continue;
        }

        let object;
        if (obj[0] === "{") {
            object = JSON.parse(obj);
        } else {
            object = obj;
        }

        lastValues[key as SyncKeys] = object;
        update[key as SyncKeys] = [timestamp, object];
    }

    return update;
}

function SyncManager(props: Props) {
    const client = useContext(AppContext);
    const status = useContext(StatusContext);

    useEffect(() => {
        if (status === ClientStatus.ONLINE) {
            client
                .syncFetchSettings(
                    DEFAULT_ENABLED_SYNC.filter(
                        (x) => !props.sync?.disabled?.includes(x),
                    ),
                )
                .then((data) => {
                    dispatch({
                        type: "SYNC_UPDATE",
                        update: mapSync(data),
                    });
                });

            client
                .syncFetchUnreads()
                .then((unreads) => dispatch({ type: "UNREADS_SET", unreads }));
        }
    }, [status]);

    function syncChange(key: SyncKeys, data: any) {
        const timestamp = +new Date();
        dispatch({
            type: "SYNC_SET_REVISION",
            key,
            timestamp,
        });

        client.syncSetSettings(
            {
                [key]: data,
            },
            timestamp,
        );
    }

    const disabled = props.sync.disabled ?? [];
    for (const [key, object] of [
        ["appearance", props.settings.appearance],
        ["theme", props.settings.theme],
        ["locale", props.locale],
        ["notifications", props.notifications],
    ] as [SyncKeys, any][]) {
        useEffect(() => {
            if (disabled.indexOf(key) === -1) {
                if (typeof lastValues[key] !== "undefined") {
                    if (!isEqual(lastValues[key], object)) {
                        syncChange(key, object);
                    }
                }
            }

            lastValues[key] = object;
        }, [disabled, object]);
    }

    useEffect(() => {
        function onPacket(packet: ClientboundNotification) {
            if (packet.type === "UserSettingsUpdate") {
                const update: { [key in SyncKeys]?: [number, SyncData[key]] } =
                    mapSync(packet.update, props.sync.revision);

                dispatch({
                    type: "SYNC_UPDATE",
                    update,
                });
            }
        }

        client.addListener("packet", onPacket);
        return () => client.removeListener("packet", onPacket);
    }, [disabled, props.sync]);

    return null;
}

export default connectState(SyncManager, (state) => {
    return {
        settings: state.settings,
        locale: state.locale,
        sync: state.sync,
        notifications: state.notifications,
    };
});