/*
Copyright 2026 Element Creations Ltd.
Copyright 2024 New Vector Ltd.
Copyright 2022 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 { PureComponent, type SyntheticEvent } from "react";
import { type WebScreen as ScreenEvent } from "@matrix-org/analytics-events/types/typescript/WebScreen";
import { type Interaction as InteractionEvent } from "@matrix-org/analytics-events/types/typescript/Interaction";
import { type PinUnpinAction } from "@matrix-org/analytics-events/types/typescript/PinUnpinAction";
import { type RoomListSortingAlgorithmChanged } from "@matrix-org/analytics-events/types/typescript/RoomListSortingAlgorithmChanged";
import { type UrlPreviewRendered } from "@matrix-org/analytics-events/types/typescript/UrlPreviewRendered";

import PageType from "./PageTypes";
import Views from "./Views";
import { PosthogAnalytics } from "./PosthogAnalytics";
import { SortingAlgorithm } from "./stores/room-list-v3/skip-list/sorters";
import { LruCache } from "./utils/LruCache";

export type ScreenName = ScreenEvent["$current_url"];
export type InteractionName = InteractionEvent["name"];

const notLoggedInMap: Record<Exclude<Views, Views.LOGGED_IN>, ScreenName> = {
    [Views.LOADING]: "Loading",
    [Views.CONFIRM_LOCK_THEFT]: "ConfirmStartup",
    [Views.WELCOME]: "Welcome",
    [Views.LOGIN]: "Login",
    [Views.REGISTER]: "Register",
    [Views.FORGOT_PASSWORD]: "ForgotPassword",
    [Views.COMPLETE_SECURITY]: "CompleteSecurity",
    [Views.E2E_SETUP]: "E2ESetup",
    [Views.PENDING_CLIENT_START]: "Loading",
    [Views.SOFT_LOGOUT]: "SoftLogout",
    [Views.LOCK_STOLEN]: "SessionLockStolen",
};

const loggedInPageTypeMap: Record<PageType | string, ScreenName> = {
    [PageType.HomePage]: "Home",
    [PageType.RoomView]: "Room",
    [PageType.UserView]: "User",
};

const SortingAlgorithmMap: Record<SortingAlgorithm, RoomListSortingAlgorithmChanged["newAlgorithm"]> = {
    [SortingAlgorithm.Recency]: "Activity",
    [SortingAlgorithm.Unread]: "Unread",
    [SortingAlgorithm.Alphabetic]: "Alphabetic",
};

export default class PosthogTrackers {
    private static internalInstance: PosthogTrackers;

    private readonly previewedEventIds = new LruCache<string, true>(1000);

    public static get instance(): PosthogTrackers {
        if (!PosthogTrackers.internalInstance) {
            PosthogTrackers.internalInstance = new PosthogTrackers();
        }
        return PosthogTrackers.internalInstance;
    }

    private view: Views = Views.LOADING;
    private pageType?: PageType | string;
    private override?: ScreenName;

    public trackPageChange(view: Views, pageType: PageType | string | undefined, durationMs: number): void {
        this.view = view;
        this.pageType = pageType;
        if (this.override) return;
        this.trackPage(durationMs);
    }

    private trackPage(durationMs?: number): void {
        const screenName =
            this.view === Views.LOGGED_IN ? loggedInPageTypeMap[this.pageType!] : notLoggedInMap[this.view];
        PosthogAnalytics.instance.trackEvent<ScreenEvent>({
            eventName: "$pageview",
            $current_url: screenName,
            durationMs,
        });
    }

    public trackOverride(screenName: ScreenName): void {
        if (!screenName) return;
        this.override = screenName;
        PosthogAnalytics.instance.trackEvent<ScreenEvent>({
            eventName: "$pageview",
            $current_url: screenName,
        });
    }

    public clearOverride(screenName: ScreenName): void {
        if (screenName !== this.override) return;
        this.override = undefined;
        this.trackPage();
    }

    public static trackInteraction(name: InteractionName, ev?: SyntheticEvent | Event, index?: number): void {
        let interactionType: InteractionEvent["interactionType"];
        if (ev?.type === "click") {
            interactionType = "Pointer";
        } else if (ev?.type.startsWith("key")) {
            interactionType = "Keyboard";
        }

        PosthogAnalytics.instance.trackEvent<InteractionEvent>({
            eventName: "Interaction",
            interactionType,
            index,
            name,
        });
    }

    /**
     * Track a pin or unpin action on a message.
     * @param kind - Is pin or unpin.
     * @param from - From where the action is triggered.
     */
    public static trackPinUnpinMessage(kind: PinUnpinAction["kind"], from: PinUnpinAction["from"]): void {
        PosthogAnalytics.instance.trackEvent<PinUnpinAction>({
            eventName: "PinUnpinAction",
            kind,
            from,
        });
    }

    /**
     * Track when the user chooses a different sorting algorithm for the room-list.
     * @param oldAlgorithm - The old algorithm.
     * @param newAlgorithm - The new algorithm.
     */
    public static trackRoomListSortingAlgorithmChange(
        oldAlgorithm: SortingAlgorithm,
        newAlgorithm: SortingAlgorithm,
    ): void {
        PosthogAnalytics.instance.trackEvent<RoomListSortingAlgorithmChanged>({
            eventName: "RoomListSortingAlgorithmChanged",
            oldAlgorithm: SortingAlgorithmMap[oldAlgorithm],
            newAlgorithm: SortingAlgorithmMap[newAlgorithm],
        });
    }

    /**
     * Track if an event has had a previewed rendered in the client.
     * This function makes a best-effort attempt to prevent double counting.
     *
     * @param eventId EventID for deduplication.
     * @param isEncrypted Whether the event (and effectively the room) was encrypted.
     * @param previews The previews generated from the event.
     */
    public trackUrlPreview(eventId: string, isEncrypted: boolean, previews: { image?: unknown }[]): void {
        // Discount any previews that we have already tracked.
        if (this.previewedEventIds.get(eventId)) {
            return;
        }
        PosthogAnalytics.instance.trackEvent<UrlPreviewRendered>({
            eventName: "UrlPreviewRendered",
            previewKind: "LegacyCard",
            hasThumbnail: previews.some((p) => !!p.image),
            previewCount: previews.length,
            encryptedRoom: isEncrypted,
        });
        this.previewedEventIds.set(eventId, true);
    }
}

export class PosthogScreenTracker extends PureComponent<{ screenName: ScreenName }> {
    public componentDidMount(): void {
        PosthogTrackers.instance.trackOverride(this.props.screenName);
    }

    public componentDidUpdate(): void {
        // We do not clear the old override here so that we do not send the non-override screen as a transition
        PosthogTrackers.instance.trackOverride(this.props.screenName);
    }

    public componentWillUnmount(): void {
        PosthogTrackers.instance.clearOverride(this.props.screenName);
    }

    public render(): React.ReactNode {
        return null; // no need to render anything, we just need to hook into the React lifecycle
    }
}
