import { AcceptFriendMessageComposer, DeclineFriendMessageComposer, FollowFriendFailedEvent, FollowFriendMessageComposer, FriendListFragmentEvent, FriendListUpdateComposer, FriendListUpdateEvent, FriendParser, FriendRequestsEvent, GetFriendRequestsComposer, GetSessionDataManager, MessengerInitComposer, MessengerInitEvent, NewFriendRequestEvent, RequestFriendComposer, SetRelationshipStatusComposer } from '@nitrots/nitro-renderer';
import { useEffect, useMemo, useState } from 'react';
import { useBetween } from 'use-between';
import { CloneObject, LocalizeText, MessengerFriend, MessengerRequest, MessengerSettings, NotificationAlertType, SendMessageComposer } from '../../api';
import { useMessageEvent } from '../events';
import { useNotification } from '../notification';

/**
 * Internal singleton store for friend-list state + actions. Public
 * consumers should use useFriendsState (read-only — friends arrays,
 * settings, derived online/offline split) or useFriendsActions
 * (imperative — request / response / follow / update). useFriends is
 * the legacy shim that composes both.
 */
const useFriendsStore = () =>
{
    const [ friends, setFriends ] = useState<MessengerFriend[]>([]);
    const [ requests, setRequests ] = useState<MessengerRequest[]>([]);
    const [ sentRequests, setSentRequests ] = useState<number[]>([]);
    const [ dismissedRequestIds, setDismissedRequestIds ] = useState<number[]>([]);
    const [ settings, setSettings ] = useState<MessengerSettings>(null);
    const { simpleAlert = null } = useNotification();

    const onlineFriends = useMemo(() =>
    {
        const onlineFriends = friends.filter(friend => friend.online);

        onlineFriends.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));

        return onlineFriends;
    }, [ friends ]);

    const offlineFriends = useMemo(() =>
    {
        const offlineFriends = friends.filter(friend => !friend.online);

        offlineFriends.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));

        return offlineFriends;
    }, [ friends ]);

    const followFriend = (friend: MessengerFriend) => SendMessageComposer(new FollowFriendMessageComposer(friend.id));

    const updateRelationship = (friend: MessengerFriend, type: number) => ((type !== friend.relationshipStatus) && SendMessageComposer(new SetRelationshipStatusComposer(friend.id, type)));

    const getFriend = (userId: number) =>
    {
        for(const friend of friends)
        {
            if(friend.id === userId) return friend;
        }

        return null;
    };

    const canRequestFriend = (userId: number) =>
    {
        if(userId === GetSessionDataManager().userId) return false;

        if(getFriend(userId)) return false;

        if(requests.find(request => (request.requesterUserId === userId))) return false;

        if(sentRequests.indexOf(userId) >= 0) return false;

        return true;
    };

    const requestFriend = (userId: number, userName: string) =>
    {
        if(!canRequestFriend(userId)) return false;

        setSentRequests(prevValue =>
        {
            const newSentRequests = [ ...prevValue ];

            newSentRequests.push(userId);

            return newSentRequests;
        });

        SendMessageComposer(new RequestFriendComposer(userName));
    };

    const requestResponse = (requestId: number, flag: boolean) =>
    {
        if(requestId === -1 && !flag)
        {
            SendMessageComposer(new DeclineFriendMessageComposer(true));

            setRequests([]);
        }
        else
        {
            setRequests(prevValue =>
            {
                const newRequests = [ ...prevValue ];
                const index = newRequests.findIndex(request => (request.id === requestId));

                if(index === -1) return prevValue;

                if(flag)
                {
                    SendMessageComposer(new AcceptFriendMessageComposer(newRequests[index].id));
                }
                else
                {
                    SendMessageComposer(new DeclineFriendMessageComposer(false, newRequests[index].id));
                }

                newRequests.splice(index, 1);

                return newRequests;
            });
        }
    };

    useMessageEvent<MessengerInitEvent>(MessengerInitEvent, event =>
    {
        const parser = event.getParser();

        setSettings(new MessengerSettings(
            parser.userFriendLimit,
            parser.normalFriendLimit,
            parser.extendedFriendLimit,
            parser.categories));

        SendMessageComposer(new GetFriendRequestsComposer());
    });

    useMessageEvent<FriendListFragmentEvent>(FriendListFragmentEvent, event =>
    {
        const parser = event.getParser();

        setFriends(prevValue =>
        {
            const newValue = [ ...prevValue ];

            for(const friend of parser.fragment)
            {
                const index = newValue.findIndex(existingFriend => (existingFriend.id === friend.id));
                const newFriend = new MessengerFriend();
                newFriend.populate(friend);

                if(index > -1) newValue[index] = newFriend;
                else newValue.push(newFriend);
            }

            return newValue;
        });
    });

    useMessageEvent<FriendListUpdateEvent>(FriendListUpdateEvent, event =>
    {
        const parser = event.getParser();

        setFriends(prevValue =>
        {
            const newValue = [ ...prevValue ];

            const processUpdate = (friend: FriendParser) =>
            {
                const index = newValue.findIndex(existingFriend => (existingFriend.id === friend.id));
                const newFriend = new MessengerFriend();
                newFriend.populate(friend);

                if(index === -1)
                {
                    newValue.unshift(newFriend);
                }
                else
                {
                    newValue[index] = newFriend;
                }
            };

            for(const friend of parser.addedFriends) processUpdate(friend);

            for(const friend of parser.updatedFriends) processUpdate(friend);

            for(const removedFriendId of parser.removedFriendIds)
            {
                const index = newValue.findIndex(existingFriend => (existingFriend.id === removedFriendId));

                if(index > -1) newValue.splice(index, 1);
            }

            return newValue;
        });
    });

    useMessageEvent<FriendRequestsEvent>(FriendRequestsEvent, event =>
    {
        const parser = event.getParser();

        setRequests(prevValue =>
        {
            const newValue = [ ...prevValue ];

            for(const request of parser.requests)
            {
                const index = newValue.findIndex(existing => (existing.requesterUserId === request.requesterUserId));

                if(index !== -1)
                {
                    newValue[index] = CloneObject(newValue[index]);
                    newValue[index].populate(request);
                }
                else
                {
                    const newRequest = new MessengerRequest();
                    newRequest.populate(request);

                    newValue.push(newRequest);
                }
            }

            return newValue;
        });
    });

    useMessageEvent<FollowFriendFailedEvent>(FollowFriendFailedEvent, () =>
    {
        simpleAlert(LocalizeText('friendlist.followerror.hotelview'), NotificationAlertType.DEFAULT, null, null, LocalizeText('friendlist.alert.title'));
    });

    useMessageEvent<NewFriendRequestEvent>(NewFriendRequestEvent, event =>
    {
        const parser = event.getParser();
        const request = parser.request;

        setRequests(prevValue =>
        {
            const newRequests = [ ...prevValue ];

            const index = newRequests.findIndex(existing => (existing.requesterUserId === request.requesterUserId));

            if(index === -1)
            {
                const newRequest = new MessengerRequest();
                newRequest.populate(request);

                newRequests.push(newRequest);
            }

            return newRequests;
        });
    });

    useEffect(() =>
    {
        SendMessageComposer(new MessengerInitComposer());

        const interval = setInterval(() => SendMessageComposer(new FriendListUpdateComposer()), 120000);

        return () =>
        {
            clearInterval(interval);
        };
    }, []);

    return { friends, requests, sentRequests, dismissedRequestIds, setDismissedRequestIds, settings, onlineFriends, offlineFriends, getFriend, canRequestFriend, requestFriend, requestResponse, followFriend, updateRelationship };
};

/**
 * Read-only slice of the friends store: the friend list itself
 * (friends, requests, sentRequests, dismissedRequestIds, settings)
 * plus the derived online/offline splits and the lookup helpers
 * (getFriend, canRequestFriend) that don't mutate state.
 *
 * setDismissedRequestIds is exposed here because most consumers that
 * read dismissedRequestIds also need to mutate it (it's UI-local
 * "I've already hidden this banner" state, not server-driven).
 */
export const useFriendsState = () =>
{
    const {
        friends,
        requests,
        sentRequests,
        dismissedRequestIds,
        setDismissedRequestIds,
        settings,
        onlineFriends,
        offlineFriends,
        getFriend,
        canRequestFriend
    } = useBetween(useFriendsStore);

    return {
        friends,
        requests,
        sentRequests,
        dismissedRequestIds,
        setDismissedRequestIds,
        settings,
        onlineFriends,
        offlineFriends,
        getFriend,
        canRequestFriend
    };
};

/**
 * Imperative slice of the friends store: request a new friendship,
 * respond to an incoming request, follow a friend, update an existing
 * relationship.
 */
export const useFriendsActions = () =>
{
    const {
        requestFriend,
        requestResponse,
        followFriend,
        updateRelationship
    } = useBetween(useFriendsStore);

    return {
        requestFriend,
        requestResponse,
        followFriend,
        updateRelationship
    };
};

/**
 * @deprecated Prefer `useFriendsState` (read-only friends list) and
 * `useFriendsActions` (request / follow / update) directly. This shim
 * composes both into the historical `useFriends()` shape so the 16
 * existing consumers keep working unchanged.
 */
export const useFriends = () => useBetween(useFriendsStore);
