/*
Copyright 2024 New Vector Ltd.
Copyright 2021 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 React, {
    type ComponentProps,
    type RefObject,
    type SyntheticEvent,
    type KeyboardEvent,
    useContext,
    useRef,
    useState,
    type ChangeEvent,
    type ReactNode,
    useEffect,
} from "react";
import {
    RoomType,
    HistoryVisibility,
    Preset,
    Visibility,
    type MatrixClient,
    type ICreateRoomOpts,
} from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { LockSolidIcon, PublicIcon, ChevronLeftIcon } from "@vector-im/compound-design-tokens/assets/web/icons";

import { _t } from "../../../languageHandler";
import ContextMenu, { ChevronFace } from "../../structures/ContextMenu";
import createRoom, { type IOpts as ICreateOpts } from "../../../createRoom";
import MatrixClientContext, { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import type SpaceBasicSettings from "./SpaceBasicSettings";
import { SpaceAvatar } from "./SpaceBasicSettings";
import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
import Field from "../elements/Field";
import withValidation from "../elements/Validation";
import RoomAliasField from "../elements/RoomAliasField";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { Filter } from "../dialogs/spotlight/Filter";
import { type OpenSpotlightPayload } from "../../../dispatcher/payloads/OpenSpotlightPayload.ts";
import { useSettingValue } from "../../../hooks/useSettings.ts";
import { UIFeature } from "../../../settings/UIFeature.ts";
import SpacePillButton from "../../structures/SpacePillButton.tsx";

export const createSpace = async (
    client: MatrixClient,
    name: string,
    isPublic: boolean,
    alias?: string,
    topic?: string,
    avatar?: string | File,
    createOpts: Partial<ICreateRoomOpts> = {},
    otherOpts: Partial<Omit<ICreateOpts, "createOpts">> = {},
): Promise<string | null> => {
    return createRoom(client, {
        name,
        topic,
        createOpts: {
            preset: isPublic ? Preset.PublicChat : Preset.PrivateChat,
            visibility:
                isPublic && (await client.doesServerSupportUnstableFeature("org.matrix.msc3827.stable"))
                    ? Visibility.Public
                    : Visibility.Private,
            power_level_content_override: {
                // Only allow Admins to write to the timeline to prevent hidden sync spam
                events_default: 100,
                invite: isPublic ? 0 : 50,
            },
            room_alias_name: isPublic && alias ? alias.substring(1, alias.indexOf(":")) : undefined,
            ...createOpts,
        },
        avatar,
        roomType: RoomType.Space,
        historyVisibility: isPublic ? HistoryVisibility.WorldReadable : HistoryVisibility.Invited,
        spinner: false,
        encryption: false,
        andView: true,
        inlineErrors: true,
        ...otherOpts,
    });
};

const spaceNameValidator = withValidation({
    rules: [
        {
            key: "required",
            test: async ({ value }) => !!value,
            invalid: () => _t("create_space|name_required"),
        },
    ],
});

const nameToLocalpart = (name: string): string => {
    return name
        .trim()
        .toLowerCase()
        .replace(/\s+/g, "-")
        .replace(/[^a-z0-9_-]+/gi, "");
};

type BProps = Omit<ComponentProps<typeof SpaceBasicSettings>, "nameDisabled" | "topicDisabled" | "avatarDisabled">;
interface ISpaceCreateFormProps extends BProps {
    busy: boolean;
    alias: string;
    nameFieldRef: RefObject<Field | null>;
    aliasFieldRef: RefObject<RoomAliasField | null>;
    showAliasField?: boolean;
    children?: ReactNode;
    onSubmit(this: void, e: SyntheticEvent): void;
    setAlias(this: void, alias: string): void;
}

export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
    busy,
    onSubmit,
    avatarUrl,
    setAvatar,
    name,
    setName,
    nameFieldRef,
    alias,
    aliasFieldRef,
    setAlias,
    showAliasField,
    topic,
    setTopic,
    children,
}) => {
    const cli = useContext(MatrixClientContext);
    const domain = cli.getDomain() ?? undefined;

    const onKeyDown = (ev: KeyboardEvent): void => {
        const action = getKeyBindingsManager().getAccessibilityAction(ev);
        switch (action) {
            case KeyBindingAction.Enter:
                onSubmit(ev);
                break;
        }
    };

    return (
        <form className="mx_SpaceBasicSettings" onSubmit={onSubmit}>
            <SpaceAvatar avatarUrl={avatarUrl} setAvatar={setAvatar} avatarDisabled={busy} />

            <Field
                name="spaceName"
                label={_t("common|name")}
                autoFocus={true}
                value={name}
                onChange={(ev: ChangeEvent<HTMLInputElement>) => {
                    const newName = ev.target.value;
                    if (!alias || alias === `#${nameToLocalpart(name)}:${domain}`) {
                        setAlias(`#${nameToLocalpart(newName)}:${domain}`);
                        aliasFieldRef.current?.validate({ allowEmpty: true });
                    }
                    setName(newName);
                }}
                onKeyDown={onKeyDown}
                ref={nameFieldRef}
                onValidate={spaceNameValidator}
                disabled={busy}
                autoComplete="off"
            />

            {showAliasField ? (
                <RoomAliasField
                    ref={aliasFieldRef}
                    onChange={setAlias}
                    domain={domain}
                    value={alias}
                    placeholder={name ? nameToLocalpart(name) : _t("create_space|address_placeholder")}
                    label={_t("create_space|address_label")}
                    disabled={busy}
                    onKeyDown={onKeyDown}
                />
            ) : null}

            <Field
                name="spaceTopic"
                element="textarea"
                label={_t("common|description")}
                value={topic ?? ""}
                onChange={(ev) => setTopic(ev.target.value)}
                rows={3}
                disabled={busy}
            />

            {children}
        </form>
    );
};

const SpaceCreateMenu: React.FC<{
    onFinished(this: void): void;
}> = ({ onFinished }) => {
    const cli = useMatrixClientContext();
    const settingAllowPublicSpaces = useSettingValue(UIFeature.AllowCreatingPublicSpaces);
    const [visibility, setVisibility] = useState<Visibility | null>(
        settingAllowPublicSpaces === false ? Visibility.Private : null,
    );
    const [busy, setBusy] = useState<boolean>(false);

    const [name, setName] = useState("");
    const spaceNameField = useRef<Field>(null);
    const [alias, setAlias] = useState("");
    const spaceAliasField = useRef<RoomAliasField>(null);
    const [avatar, setAvatar] = useState<File | undefined>(undefined);
    const [topic, setTopic] = useState<string>("");

    const [supportsSpaceFiltering, setSupportsSpaceFiltering] = useState(true); // assume it does until we find out it doesn't
    useEffect(() => {
        cli.isVersionSupported("v1.4")
            .then((supported) => {
                return supported || cli.doesServerSupportUnstableFeature("org.matrix.msc3827.stable");
            })
            .then((supported) => {
                setSupportsSpaceFiltering(supported);
            });
    }, [cli]);

    const onSpaceCreateClick = async (e: ButtonEvent): Promise<void> => {
        e.preventDefault();
        if (busy) return;

        setBusy(true);
        // require & validate the space name field
        if (spaceNameField.current && !(await spaceNameField.current.validate({ allowEmpty: false }))) {
            spaceNameField.current.focus();
            spaceNameField.current.validate({ allowEmpty: false, focused: true });
            setBusy(false);
            return;
        }

        if (
            spaceAliasField.current &&
            visibility === Visibility.Public &&
            !(await spaceAliasField.current.validate({ allowEmpty: false }))
        ) {
            spaceAliasField.current.focus();
            spaceAliasField.current.validate({ allowEmpty: false, focused: true });
            setBusy(false);
            return;
        }

        try {
            await createSpace(cli, name, visibility === Visibility.Public, alias, topic, avatar);

            onFinished();
        } catch (e) {
            logger.error(e);
        }
    };

    const onSearchClick = (): void => {
        defaultDispatcher.dispatch<OpenSpotlightPayload>({
            action: Action.OpenSpotlight,
            initialFilter: Filter.PublicSpaces,
        });
    };

    let body;
    if (visibility === null) {
        body = (
            <React.Fragment>
                <h2>{_t("create_space|label")}</h2>
                <p>{_t("create_space|explainer")}</p>

                <SpacePillButton
                    icon={<PublicIcon />}
                    title={_t("common|public")}
                    description={_t("create_space|public_description")}
                    onClick={() => setVisibility(Visibility.Public)}
                />
                <SpacePillButton
                    icon={<LockSolidIcon />}
                    title={_t("common|private")}
                    description={_t("create_space|private_description")}
                    onClick={() => setVisibility(Visibility.Private)}
                />

                {supportsSpaceFiltering && (
                    <AccessibleButton kind="primary_outline" onClick={onSearchClick}>
                        {_t("create_space|search_public_button")}
                    </AccessibleButton>
                )}
            </React.Fragment>
        );
    } else {
        body = (
            <React.Fragment>
                {settingAllowPublicSpaces && (
                    <AccessibleButton
                        className="mx_SpaceCreateMenu_back"
                        onClick={() => setVisibility(null)}
                        title={_t("action|go_back")}
                    >
                        <ChevronLeftIcon />
                    </AccessibleButton>
                )}

                <h2>
                    {visibility === Visibility.Public
                        ? _t("create_space|public_heading")
                        : settingAllowPublicSpaces
                          ? _t("create_space|private_heading")
                          : _t("create_space|private_only_heading")}
                </h2>
                <p>
                    {_t("create_space|add_details_prompt")} {_t("create_space|add_details_prompt_2")}
                </p>

                <SpaceCreateForm
                    busy={busy}
                    onSubmit={onSpaceCreateClick}
                    setAvatar={setAvatar}
                    name={name}
                    setName={setName}
                    nameFieldRef={spaceNameField}
                    topic={topic}
                    setTopic={setTopic}
                    alias={alias}
                    setAlias={setAlias}
                    showAliasField={visibility === Visibility.Public}
                    aliasFieldRef={spaceAliasField}
                />

                <AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={busy}>
                    {busy ? _t("create_space|creating") : _t("action|create")}
                </AccessibleButton>
            </React.Fragment>
        );
    }

    return (
        <ContextMenu
            left={72}
            top={62}
            chevronOffset={0}
            chevronFace={ChevronFace.None}
            onFinished={onFinished}
            wrapperClassName="mx_SpaceCreateMenu_wrapper"
            managed={false}
            focusLock={true}
        >
            {body}
        </ContextMenu>
    );
};

export default SpaceCreateMenu;
