/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/

import { type MatrixEvent, type Room, type RoomMember } from "matrix-js-sdk/src/matrix";

import { type ButtonEvent } from "../components/views/elements/AccessibleButton";
import { PillType } from "../components/views/elements/Pill";
import { parsePermalink } from "../utils/permalinks/Permalinks";
import dis from "../dispatcher/dispatcher";
import { Action } from "../dispatcher/actions";
import { type PermalinkParts } from "../utils/permalinks/PermalinkConstructor";
import { _t } from "../languageHandler";
import { usePermalinkTargetRoom } from "./usePermalinkTargetRoom";
import { usePermalinkEvent } from "./usePermalinkEvent";
import { usePermalinkMember } from "./usePermalinkMember";

interface Args {
    /** Room in which the permalink should be displayed. */
    room?: Room;
    /** When set forces the permalink type. */
    type?: PillType;
    /** Permalink URL. */
    url?: string;
}

interface HookResult {
    /**
     * Room member of a user mention permalink.
     * null for other links, if the profile was not found or not yet loaded.
     * This can change, for instance, from null to a RoomMember after the profile lookup completed.
     */
    member: RoomMember | null;
    /**
     * Displayable text of the permalink resource. Can for instance be a user or room name.
     * null here means that there is nothing to display. Most likely if the URL was not a permalink.
     */
    text: string | null;
    /**
     * Should be used for click actions on the permalink.
     * In case of a user permalink, a view profile action is dispatched.
     */
    onClick: (e: ButtonEvent) => void;
    /**
     * This can be for instance a user or room Id.
     * null here means that the resource cannot be detected. Most likely if the URL was not a permalink.
     */
    resourceId: string | null;
    /**
     * Target room of the permalink:
     * For an @room mention, this is the room where the permalink should be displayed.
     * For a room permalink, it is the room from the permalink.
     * null for other links or if the room cannot be found.
     */
    targetRoom: Room | null;
    /**
     * Type of the pill plus "space" for spaces.
     * null here means that the type cannot be detected. Most likely if the URL was not a permalink.
     */
    type: PillType | "space" | null;
    /**
     * Target event of the permalink.
     * Null if unable to load the event.
     */
    event: MatrixEvent | null;
}

/**
 * Tries to determine the pill type.
 *
 * If forcedType is present it will be returned.
 * If the parse result contains a room Id or alias and an event Id:
 * - Type is EventInSameRoom if the permalink room Id or alias equals the parsed room Id or alias
 * - Type is EventInOtherRoom if the permalink room Id or alias not equals the parsed room Id or alias
 * If the parse result contains a primary entity Id it will try to detect the type from it.
 * Otherwise returns null.
 *
 * @param forcedType - Forced pill type. Will be used if present and short-circuits all othe conditions.
 * @param parseResult - Permalink parser result
 * @param permalinkRoom - Room in which the permalink is displayed.
 * @returns Pill type or null if unable to determine.
 */
const determineType = (
    forcedType: PillType | undefined,
    parseResult: PermalinkParts | null,
    permalinkRoom: Room | undefined,
): PillType | null => {
    if (forcedType) return forcedType;

    if (parseResult?.roomIdOrAlias && parseResult?.eventId) {
        if (parseResult.roomIdOrAlias === permalinkRoom?.roomId) {
            return PillType.EventInSameRoom;
        }

        return PillType.EventInOtherRoom;
    }

    if (parseResult?.primaryEntityId) {
        const prefix = parseResult.primaryEntityId[0] || "";
        return (
            {
                "@": PillType.UserMention,
                "#": PillType.RoomMention,
                "!": PillType.RoomMention,
            }[prefix] || null
        );
    }

    return null;
};

/**
 * Can be used to retrieve all information needed to display a permalink.
 */
export const usePermalink: (args: Args) => HookResult = ({
    room: permalinkRoom,
    type: forcedType,
    url,
}): HookResult => {
    let resourceId: string | null = null;
    let parseResult: PermalinkParts | null = null;

    if (url) {
        parseResult = parsePermalink(url);

        if (parseResult?.primaryEntityId) {
            resourceId = parseResult.primaryEntityId;
        }
    }

    const type = determineType(forcedType, parseResult, permalinkRoom);
    const targetRoom = usePermalinkTargetRoom(type, parseResult, permalinkRoom);
    const event = usePermalinkEvent(type, parseResult, targetRoom);
    const member = usePermalinkMember(type, parseResult, targetRoom, event);

    let onClick: (e: ButtonEvent) => void = () => {};
    let text = resourceId;

    if (type === PillType.AtRoomMention && permalinkRoom) {
        text = "@room";
    } else if (type === PillType.UserMention && member) {
        text = member.name || resourceId;
        onClick = (e: ButtonEvent): void => {
            e.preventDefault();
            e.stopPropagation();
            dis.dispatch({
                action: Action.ViewUser,
                member: member,
            });
        };
    } else if (type === PillType.RoomMention) {
        if (targetRoom) {
            text = targetRoom.name || resourceId;
        }
    } else if (type === PillType.EventInSameRoom) {
        text = member?.name || _t("common|user");
    } else if (type === PillType.EventInOtherRoom) {
        text = targetRoom?.name || _t("common|room");
    }

    return {
        event,
        member,
        onClick,
        resourceId,
        targetRoom,
        text,
        type,
    };
};
