/*
 * Copyright 2026 Element Creations Ltd.
 *
 * 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 React, { type JSX, useCallback, useMemo, useRef } from "react";
import classNames from "classnames";
import {
    CollapseIcon,
    DeleteIcon,
    EditIcon,
    ExpandIcon,
    InlineCodeIcon,
    LinkIcon,
    PinIcon,
    ReplyIcon,
    RestartIcon,
    UnpinIcon,
    ThreadsIcon,
    VisibilityOnIcon,
    VisibilityOffIcon,
    DownloadIcon,
    OverflowHorizontalIcon,
    ReactionAddIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { InlineSpinner } from "@vector-im/compound-web";

import { RovingTabIndexProvider } from "../../../../../core/roving";
import { useI18n } from "../../../../../core/i18n/i18nContext";
import { Flex } from "../../../../../core/utils/Flex";
import { type ViewModel, useViewModel } from "../../../../../core/viewmodel";
import { ActionBarButton } from "./ActionBarButton";
import styles from "./ActionBarView.module.css";

/**
 * Snapshot state for the message action toolbar.
 *
 * The snapshot carries the resolved actions to render plus the small amount of
 * per-action state the view needs for labels, icons, and disabled state.
 */
export interface ActionBarViewSnapshot {
    /** Explicitly resolved actions to render, in order. */
    actions: ActionBarAction[];
    /** Whether actions should render as icon buttons or label buttons. */
    presentation?: "icon" | "label";
    /** Whether an in-progress download should be presented as decrypting rather than downloading. */
    isDownloadEncrypted: boolean;
    /** Whether a media download or decryption is currently in progress. */
    isDownloadLoading: boolean;
    /** Whether the message is currently pinned. */
    isPinned: boolean;
    /** Whether the reply chain is currently expanded. */
    isQuoteExpanded: boolean;
    /** Whether starting or replying in a thread is allowed for this event. */
    isThreadReplyAllowed: boolean;
}

/**
 * Event handlers for toolbar actions.
 *
 * Each callback receives the triggering button so menus can be positioned from
 * the action anchor when needed.
 */
export interface ActionBarViewActions {
    onCancelClick?: (anchor: HTMLElement | null) => void;
    onCopyLinkClick?: (anchor: HTMLElement | null) => void;
    onDownloadClick?: (anchor: HTMLElement | null) => void;
    onEditClick?: (anchor: HTMLElement | null) => void;
    onHideClick?: (anchor: HTMLElement | null) => void;
    onOptionsClick?: (anchor: HTMLElement | null) => void;
    onPinClick?: (anchor: HTMLElement | null) => void;
    onReactionsClick?: (anchor: HTMLElement | null) => void;
    onRemoveClick?: (anchor: HTMLElement | null) => void;
    onReplyClick?: (anchor: HTMLElement | null) => void;
    onReplyInThreadClick?: (anchor: HTMLElement | null) => void;
    onResendClick?: (anchor: HTMLElement | null) => void;
    onToggleThreadExpanded?: (anchor: HTMLElement | null) => void;
    onViewInRoomClick?: (anchor: HTMLElement | null) => void;
    onViewSourceClick?: (anchor: HTMLElement | null) => void;
}

export type ActionBarViewModel = ViewModel<ActionBarViewSnapshot, ActionBarViewActions>;

/**
 * Resolved actions that `ActionBarView` can render.
 *
 * The order of actions in `ActionBarViewSnapshot.actions` defines the visual
 * order of buttons in the toolbar.
 */
export enum ActionBarAction {
    Cancel = "cancel",
    CopyLink = "copyLink",
    Download = "download",
    Edit = "edit",
    Expand = "expand",
    Hide = "hide",
    Options = "options",
    Pin = "pin",
    React = "react",
    Remove = "remove",
    Reply = "reply",
    ReplyInThread = "replyInThread",
    Resend = "resend",
    ViewInRoom = "viewInRoom",
    ViewSource = "viewSource",
}

interface ToolbarButtonMeta {
    action: ActionBarAction;
    disabled?: boolean;
}

interface ActionBarViewProps {
    /** The view model for the component. */
    vm: ActionBarViewModel;
    /** Optional CSS class names to apply to the component container.*/
    className?: string;
}

/**
 * Compact toolbar for message-level actions such as reply, react, edit,
 * download, and overflow options.
 *
 * Use `className` for host-level container styling, following standard React patterns.
 *
 * @example
 * ```tsx
 * <ActionBarView vm={actionBarVm} className="mx_MessageActionBar" />
 * ```
 */
export function ActionBarView({ vm, className }: Readonly<ActionBarViewProps>): JSX.Element | null {
    const { translate: _t } = useI18n();
    const {
        actions,
        presentation = "icon",
        isThreadReplyAllowed,
        isDownloadEncrypted,
        isDownloadLoading,
        isPinned,
        isQuoteExpanded,
    } = useViewModel(vm);

    // Track the live button element for each action and keep the callback refs stable
    // so React does not detach and reattach them on every render.
    const actionButtonRefs = useRef<Partial<Record<ActionBarAction, HTMLButtonElement | null>>>({});
    const actionButtonRefSetters = useMemo(
        () =>
            Object.fromEntries(
                Object.values(ActionBarAction).map((action) => [
                    action,
                    (element: HTMLButtonElement | null) => {
                        actionButtonRefs.current[action] = element;
                    },
                ]),
            ) as Record<ActionBarAction, React.RefCallback<HTMLButtonElement>>,
        [],
    );

    const actionButtons: Partial<Record<ActionBarAction, JSX.Element>> = {};

    actionButtons[ActionBarAction.Edit] = (
        <ActionBarButton
            key={ActionBarAction.Edit}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.Edit]}
            label={_t("action|edit")}
            onActivate={vm.onEditClick}
            icon={EditIcon}
        />
    );

    const pinDescription = isPinned ? _t("action|unpin") : _t("action|pin");
    actionButtons[ActionBarAction.Pin] = (
        <ActionBarButton
            key={ActionBarAction.Pin}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.Pin]}
            label={pinDescription}
            onActivate={vm.onPinClick}
            icon={isPinned ? UnpinIcon : PinIcon}
            ariaPressed={isPinned}
        />
    );

    actionButtons[ActionBarAction.Cancel] = (
        <ActionBarButton
            key={ActionBarAction.Cancel}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.Cancel]}
            label={_t("action|delete")}
            onActivate={vm.onCancelClick}
            icon={DeleteIcon}
        />
    );

    actionButtons[ActionBarAction.CopyLink] = (
        <ActionBarButton
            key={ActionBarAction.CopyLink}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.CopyLink]}
            label={_t("timeline|mab|copy_link_thread")}
            onActivate={vm.onCopyLinkClick}
            icon={LinkIcon}
        />
    );

    actionButtons[ActionBarAction.Reply] = (
        <ActionBarButton
            key={ActionBarAction.Reply}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.Reply]}
            label={_t("action|reply")}
            onActivate={vm.onReplyClick}
            icon={ReplyIcon}
        />
    );

    actionButtons[ActionBarAction.React] = (
        <ActionBarButton
            key={ActionBarAction.React}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.React]}
            label={_t("action|react")}
            onActivate={vm.onReactionsClick}
            icon={ReactionAddIcon}
        />
    );

    let downloadTitle = _t("action|download");
    if (isDownloadLoading) {
        downloadTitle = isDownloadEncrypted
            ? _t("timeline|download_action_decrypting")
            : _t("timeline|download_action_downloading");
    }
    actionButtons[ActionBarAction.Download] = (
        <ActionBarButton
            key={ActionBarAction.Download}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.Download]}
            label={downloadTitle}
            onActivate={vm.onDownloadClick}
            icon={isDownloadLoading ? InlineSpinner : DownloadIcon}
            disabled={isDownloadLoading}
        />
    );

    actionButtons[ActionBarAction.Hide] = (
        <ActionBarButton
            key={ActionBarAction.Hide}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.Hide]}
            label={_t("action|hide")}
            onActivate={vm.onHideClick}
            icon={VisibilityOffIcon}
        />
    );

    const threadTooltipDescription = isThreadReplyAllowed
        ? _t("action|reply_in_thread")
        : _t("threads|error_start_thread_existing_relation");
    actionButtons[ActionBarAction.ReplyInThread] = (
        <ActionBarButton
            key={ActionBarAction.ReplyInThread}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.ReplyInThread]}
            label={_t("action|reply_in_thread")}
            tooltipDescription={threadTooltipDescription}
            onActivate={vm.onReplyInThreadClick}
            icon={ThreadsIcon}
            disabled={!isThreadReplyAllowed}
        />
    );

    actionButtons[ActionBarAction.Resend] = (
        <ActionBarButton
            key={ActionBarAction.Resend}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.Resend]}
            label={_t("action|retry")}
            onActivate={vm.onResendClick}
            icon={RestartIcon}
        />
    );

    const expandDescription = isQuoteExpanded
        ? _t("timeline|mab|collapse_reply_chain")
        : _t("timeline|mab|expand_reply_chain");
    actionButtons[ActionBarAction.Expand] = (
        <ActionBarButton
            key={ActionBarAction.Expand}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.Expand]}
            label={expandDescription}
            tooltipCaption={`${_t("keyboard|shift")} + ${_t("action|click")}`}
            onActivate={vm.onToggleThreadExpanded}
            icon={isQuoteExpanded ? CollapseIcon : ExpandIcon}
            ariaExpanded={isQuoteExpanded}
        />
    );

    actionButtons[ActionBarAction.Options] = (
        <ActionBarButton
            key={ActionBarAction.Options}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.Options]}
            label={_t("common|options")}
            onActivate={vm.onOptionsClick}
            icon={OverflowHorizontalIcon}
        />
    );

    actionButtons[ActionBarAction.Remove] = (
        <ActionBarButton
            key={ActionBarAction.Remove}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.Remove]}
            label={_t("action|remove")}
            onActivate={vm.onRemoveClick}
            icon={DeleteIcon}
        />
    );

    actionButtons[ActionBarAction.ViewInRoom] = (
        <ActionBarButton
            key={ActionBarAction.ViewInRoom}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.ViewInRoom]}
            label={_t("timeline|mab|view_in_room")}
            onActivate={vm.onViewInRoomClick}
            icon={VisibilityOnIcon}
        />
    );

    actionButtons[ActionBarAction.ViewSource] = (
        <ActionBarButton
            key={ActionBarAction.ViewSource}
            presentation={presentation}
            buttonRef={actionButtonRefSetters[ActionBarAction.ViewSource]}
            label={_t("action|view_source")}
            onActivate={vm.onViewSourceClick}
            icon={InlineCodeIcon}
        />
    );

    const isActionDisabled = useCallback(
        (action: ActionBarAction): boolean => {
            switch (action) {
                case ActionBarAction.Download:
                    return isDownloadLoading;
                case ActionBarAction.ReplyInThread:
                    return !isThreadReplyAllowed;
                default:
                    return false;
            }
        },
        [isDownloadLoading, isThreadReplyAllowed],
    );

    const toolbarButtons = useMemo<ToolbarButtonMeta[]>(() => {
        return actions.map((action) => ({
            action,
            disabled: isActionDisabled(action),
        }));
    }, [actions, isActionDisabled]);
    const rovingProviderKey = toolbarButtons
        .map(({ action, disabled }) => `${action}:${disabled ? "1" : "0"}`)
        .join("|");

    if (toolbarButtons.length === 0) {
        return null;
    }

    // aria-live=off to not have this read out automatically as navigating around timeline, gets repetitive.
    return (
        <RovingTabIndexProvider key={rovingProviderKey} handleLeftRight handleHomeEnd handleLoop>
            {({ onKeyDownHandler }) => (
                <Flex
                    display="inline-flex"
                    direction="row"
                    role="toolbar"
                    aria-label={_t("timeline|mab|label")}
                    aria-live="off"
                    onKeyDown={onKeyDownHandler}
                    className={classNames(className, styles.toolbar)}
                >
                    {toolbarButtons.map((meta) => actionButtons[meta.action])}
                </Flex>
            )}
        </RovingTabIndexProvider>
    );
}
