import React, {
  ChangeEventHandler,
  ClipboardEventHandler,
  FormEventHandler,
  KeyboardEventHandler,
  useCallback,
  useMemo,
  useRef,
  useState,
} from 'react';
import {
  Overlay,
  OverlayBackdrop,
  OverlayCenter,
  Box,
  Header,
  config,
  Text,
  IconButton,
  Icon,
  Icons,
  Input,
  Button,
  Spinner,
  color,
  TextArea,
  Dialog,
  Menu,
  toRem,
  Scroll,
  MenuItem,
} from 'folds';
import { Room } from 'matrix-js-sdk';
import { isKeyHotkey } from 'is-hotkey';
import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next';
import { stopPropagation } from '../../utils/keyboard';
import { useDirectUsers } from '../../hooks/useDirectUsers';
import { getMxIdLocalPart, getMxIdServer, isUserId } from '../../utils/matrix';
import { Membership } from '../../../types/matrix/room';
import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch';
import { highlightText, makeHighlightRegex } from '../../plugins/react-custom-html-parser';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { BreakWord } from '../../styles/Text.css';
import { useAlive } from '../../hooks/useAlive';

const FALLBACK_SERVER = 'vojo.chat';

// Anchored domain[:port] check. The shared `isServerName` regex is unanchored
// so it would accept trailing junk like `matrix.org/foo`.
const SERVER_NAME_REGEX = /^(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?::\d+)?$/;

const SEARCH_OPTIONS: UseAsyncSearchOptions = {
  limit: 1000,
  matchOptions: {
    contain: true,
  },
};
const getUserIdString = (userId: string) => getMxIdLocalPart(userId) ?? userId;

const buildUserId = (rawUsername: string, server: string): string | undefined => {
  const username = rawUsername.trim().replace(/^@/, '');
  const trimmedServer = server.trim();
  if (
    !username ||
    /[@:\s]/.test(username) ||
    !trimmedServer ||
    !SERVER_NAME_REGEX.test(trimmedServer)
  ) {
    return undefined;
  }
  const userId = `@${username}:${trimmedServer}`;
  return isUserId(userId) ? userId : undefined;
};

type InviteUserProps = {
  room: Room;
  requestClose: () => void;
};
export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
  const { t } = useTranslation();
  const mx = useMatrixClient();
  const alive = useAlive();

  const usernameInputRef = useRef<HTMLInputElement>(null);
  const serverInputRef = useRef<HTMLInputElement>(null);
  const directUsers = useDirectUsers();

  const userServer = getMxIdServer(mx.getSafeUserId()) ?? FALLBACK_SERVER;
  const [validUserId, setValidUserId] = useState<string | undefined>(undefined);
  const [invalidUserId, setInvalidUserId] = useState(false);

  const filteredUsers = useMemo(
    () =>
      directUsers.filter((userId) => {
        const membership = room.getMember(userId)?.membership;
        return membership !== Membership.Join;
      }),
    [directUsers, room]
  );
  const [result, search, resetSearch] = useAsyncSearch(
    filteredUsers,
    getUserIdString,
    SEARCH_OPTIONS
  );
  const queryHighlighRegex = result?.query
    ? makeHighlightRegex(result.query.split(' '))
    : undefined;

  const [inviteState, invite] = useAsyncCallback<void, Error, [string, string | undefined]>(
    useCallback(
      async (userId, reason) => {
        await mx.invite(room.roomId, userId, reason);
      },
      [mx, room]
    )
  );

  const inviting = inviteState.status === AsyncStatus.Loading;

  const recomputeValidity = useCallback(() => {
    const username = usernameInputRef.current?.value.trim() ?? '';
    // A full MXID typed into the username field is valid only if its parsed
    // local/server pass the same strict checks as buildUserId — otherwise the
    // submit button would enable for inputs the submit handler will reject.
    if (isUserId(username)) {
      const parsedLocal = getMxIdLocalPart(username);
      const parsedServer = getMxIdServer(username);
      setValidUserId(
        parsedLocal && parsedServer ? buildUserId(parsedLocal, parsedServer) : undefined
      );
      return;
    }
    const server = serverInputRef.current?.value.trim() || userServer;
    setValidUserId(buildUserId(username, server));
  }, [userServer]);

  const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
    evt.preventDefault();
    if (inviting) return;
    setInvalidUserId(false);

    const target = evt.target as HTMLFormElement | undefined;
    const usernameInput = target?.usernameInput as HTMLInputElement | undefined;
    const serverInput = target?.serverInput as HTMLInputElement | undefined;
    const reasonInput = target?.reasonInput as HTMLTextAreaElement | undefined;

    let rawUsername = usernameInput?.value.trim() ?? '';
    let server = serverInput?.value.trim() || userServer;

    if (!usernameInput || !rawUsername) return;

    // If user pasted a full MXID into the username field, split it across both
    if (isUserId(rawUsername)) {
      const parsedLocal = getMxIdLocalPart(rawUsername);
      const parsedServer = getMxIdServer(rawUsername);
      if (parsedLocal && parsedServer) {
        rawUsername = parsedLocal;
        server = parsedServer;
        usernameInput.value = parsedLocal;
        if (serverInput) serverInput.value = parsedServer;
      }
    }

    const userId = buildUserId(rawUsername, server);
    if (!userId) {
      setInvalidUserId(true);
      return;
    }

    const reason = reasonInput?.value.trim();
    invite(userId, reason || undefined)
      .then(() => {
        if (alive()) requestClose();
      })
      .catch(() => {
        // error surfaced via inviteState; swallow to keep DevTools clean
      });
  };

  const handleUsernameChange: ChangeEventHandler<HTMLInputElement> = (evt) => {
    setInvalidUserId(false);
    const value = evt.currentTarget.value.trim();

    const term = value.startsWith('@') ? value.slice(1) : value;
    if (term) {
      search(term);
    } else {
      resetSearch();
    }
    recomputeValidity();
  };

  const handleUsernamePaste: ClipboardEventHandler<HTMLInputElement> = (evt) => {
    const pasted = evt.clipboardData.getData('text').trim();
    if (!isUserId(pasted)) return;
    const parsedLocal = getMxIdLocalPart(pasted);
    const parsedServer = getMxIdServer(pasted);
    if (!parsedLocal || !parsedServer) return;

    evt.preventDefault();
    if (usernameInputRef.current) usernameInputRef.current.value = parsedLocal;
    if (serverInputRef.current) serverInputRef.current.value = parsedServer;
    resetSearch();
    setInvalidUserId(false);
    recomputeValidity();
  };

  const handleServerChange: ChangeEventHandler<HTMLInputElement> = () => {
    setInvalidUserId(false);
    recomputeValidity();
  };

  const handlePickUser = (userId: string) => {
    const localPart = getMxIdLocalPart(userId);
    const userIdServer = getMxIdServer(userId);
    if (!localPart || !userIdServer) return;

    if (usernameInputRef.current) usernameInputRef.current.value = localPart;
    if (serverInputRef.current) serverInputRef.current.value = userIdServer;
    resetSearch();
    setInvalidUserId(false);
    recomputeValidity();
    usernameInputRef.current?.focus();
  };

  const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (evt) => {
    if (isKeyHotkey('escape', evt)) {
      resetSearch();
      return;
    }
    if (isKeyHotkey('tab', evt) && result && result.items.length > 0) {
      evt.preventDefault();
      const userId = result.items[0];
      handlePickUser(userId);
    }
  };

  return (
    <Overlay open backdrop={<OverlayBackdrop />}>
      <OverlayCenter>
        <FocusTrap
          focusTrapOptions={{
            initialFocus: () => usernameInputRef.current,
            clickOutsideDeactivates: true,
            onDeactivate: requestClose,
            escapeDeactivates: stopPropagation,
          }}
        >
          <Dialog>
            <Box grow="Yes" direction="Column">
              <Header
                size="500"
                style={{ padding: `0 ${config.space.S200} 0 ${config.space.S400}` }}
              >
                <Box grow="Yes">
                  <Text size="H4" truncate>
                    {t('Inbox.invite_title')}
                  </Text>
                </Box>
                <Box shrink="No">
                  <IconButton size="300" radii="300" onClick={requestClose}>
                    <Icon src={Icons.Cross} />
                  </IconButton>
                </Box>
              </Header>
              <Box
                as="form"
                onSubmit={handleSubmit}
                shrink="No"
                style={{ padding: config.space.S400 }}
                direction="Column"
                gap="400"
              >
                <Box direction="Column" gap="100">
                  <Box direction="Row" wrap="Wrap" gap="200">
                    <Box grow="Yes" style={{ minWidth: toRem(120) }} direction="Column" gap="100">
                      <Text size="L400">{t('Direct.username')}</Text>
                      <div>
                        <Input
                          size="500"
                          radii="400"
                          ref={usernameInputRef}
                          onChange={handleUsernameChange}
                          onPaste={handleUsernamePaste}
                          onKeyDown={handleKeyDown}
                          placeholder={t('Direct.username_placeholder')}
                          name="usernameInput"
                          variant="SurfaceVariant"
                          disabled={inviting}
                          autoComplete="off"
                          required
                        />
                        {result && result.items.length > 0 && (
                          <FocusTrap
                            focusTrapOptions={{
                              initialFocus: false,
                              onDeactivate: resetSearch,
                              returnFocusOnDeactivate: false,
                              clickOutsideDeactivates: true,
                              allowOutsideClick: true,
                              isKeyForward: (evt: KeyboardEvent) => isKeyHotkey('arrowdown', evt),
                              isKeyBackward: (evt: KeyboardEvent) => isKeyHotkey('arrowup', evt),
                              escapeDeactivates: stopPropagation,
                            }}
                          >
                            <Box style={{ position: 'relative' }}>
                              <Menu
                                style={{ position: 'absolute', top: 0, zIndex: 1, width: '100%' }}
                              >
                                <Scroll size="300" style={{ maxHeight: toRem(100) }}>
                                  <div style={{ padding: config.space.S100 }}>
                                    {result.items.map((userId) => {
                                      const username = `${getMxIdLocalPart(userId)}`;
                                      const userIdServer = getMxIdServer(userId);

                                      return (
                                        <MenuItem
                                          key={userId}
                                          type="button"
                                          size="300"
                                          variant="Surface"
                                          radii="300"
                                          onClick={() => handlePickUser(userId)}
                                          after={
                                            <Text size="T200" truncate>
                                              {userIdServer}
                                            </Text>
                                          }
                                          disabled={inviting}
                                        >
                                          <Box grow="Yes">
                                            <Text size="T300" truncate>
                                              <b>
                                                {queryHighlighRegex
                                                  ? highlightText(queryHighlighRegex, [
                                                      username ?? userId,
                                                    ])
                                                  : username}
                                              </b>
                                            </Text>
                                          </Box>
                                        </MenuItem>
                                      );
                                    })}
                                  </div>
                                </Scroll>
                              </Menu>
                            </Box>
                          </FocusTrap>
                        )}
                      </div>
                    </Box>
                    <Box shrink="No" style={{ minWidth: toRem(150) }} direction="Column" gap="100">
                      <Text size="L400">{t('Direct.server')}</Text>
                      <Input
                        size="500"
                        radii="400"
                        ref={serverInputRef}
                        onChange={handleServerChange}
                        placeholder={userServer}
                        name="serverInput"
                        variant="SurfaceVariant"
                        disabled={inviting}
                        autoComplete="off"
                      />
                    </Box>
                  </Box>
                  {invalidUserId && (
                    <Box style={{ color: color.Critical.Main }} alignItems="Center" gap="100">
                      <Icon src={Icons.Warning} filled size="50" />
                      <Text size="T200" style={{ color: color.Critical.Main }}>
                        <b>{t('Direct.invalid_user_id')}</b>
                      </Text>
                    </Box>
                  )}
                </Box>
                <Box direction="Column" gap="100">
                  <Text size="L400">{t('Inbox.reason_optional')}</Text>
                  <TextArea
                    size="500"
                    name="reasonInput"
                    variant="Background"
                    rows={4}
                    resize="None"
                  />
                </Box>
                {inviteState.status === AsyncStatus.Error && (
                  <Text size="T200" style={{ color: color.Critical.Main }} className={BreakWord}>
                    <b>{inviteState.error.message}</b>
                  </Text>
                )}
                <Button
                  type="submit"
                  disabled={!validUserId || inviting}
                  before={inviting && <Spinner size="200" variant="Primary" fill="Solid" />}
                >
                  <Text size="B400">{t('Inbox.invite_button')}</Text>
                </Button>
              </Box>
            </Box>
          </Dialog>
        </FocusTrap>
      </OverlayCenter>
    </Overlay>
  );
}
