import { Box, Button, color, Icon, Icons, Input, Spinner, Switch, Text, toRem } from 'folds';
import React, { FormEventHandler, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ICreateRoomStateEvent, MatrixError, Preset, Visibility } from 'matrix-js-sdk';
import { useNavigate } from 'react-router-dom';
import { SettingTile } from '../../components/setting-tile';
import { SettingsSection } from '../settings/SettingsSection';
import { Mono, SectionLabel } from '../settings/styles.css';
import {
  addRoomIdToMDirect,
  getDMRoomFor,
  getMxIdLocalPart,
  getMxIdServer,
  isServerName,
  isUserId,
} from '../../utils/matrix';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { ErrorCode } from '../../cs-errorcode';
import { millisecondsToMinutes } from '../../utils/common';
import { createRoomEncryptionState } from '../../components/create-room';
import { useAlive } from '../../hooks/useAlive';
import { getDirectRoomPath } from '../../pages/pathUtils';

const FALLBACK_SERVER = 'vojo.chat';

type CreateChatProps = {
  defaultUserId?: string;
  // Called after a successful create or after navigating to an
  // already-existing DM. Inline header mount uses this to retract the
  // curtain over the form; the legacy `/direct/_create` route leaves it
  // unset.
  onCreated?: (roomId: string) => void;
  // `gap` between form blocks. Default `'500'` matches the legacy
  // standalone page. The inline header passes `'400'` for a tighter
  // visual.
  gap?: '400' | '500';
};
export function CreateChat({ defaultUserId, onCreated, gap = '500' }: CreateChatProps) {
  const { t } = useTranslation();
  const mx = useMatrixClient();
  const alive = useAlive();
  const navigate = useNavigate();

  const userServer = getMxIdServer(mx.getSafeUserId()) ?? FALLBACK_SERVER;
  const defaultUsername = defaultUserId ? getMxIdLocalPart(defaultUserId) : undefined;
  const defaultServer = defaultUserId ? getMxIdServer(defaultUserId) : undefined;

  const [encryption, setEncryption] = useState(false);
  const [invalidUserId, setInvalidUserId] = useState(false);

  const [createState, create] = useAsyncCallback<string, Error | MatrixError, [string, boolean]>(
    useCallback(
      async (userId, encrypted) => {
        const initialState: ICreateRoomStateEvent[] = [];

        if (encrypted) initialState.push(createRoomEncryptionState());

        const result = await mx.createRoom({
          is_direct: true,
          invite: [userId],
          visibility: Visibility.Private,
          preset: Preset.TrustedPrivateChat,
          initial_state: initialState,
          // TrustedPrivateChat already gives both parties PL 100, so either can
          // toggle the voice-messages state event; no events override needed
          // (it would replace Synapse's default events map). See voice_messages.md §4.
        });

        addRoomIdToMDirect(mx, result.room_id, userId);

        return result.room_id;
      },
      [mx]
    )
  );
  const loading = createState.status === AsyncStatus.Loading;
  const error = createState.status === AsyncStatus.Error ? createState.error : undefined;
  const disabled = createState.status === AsyncStatus.Loading;

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

    const target = evt.target as HTMLFormElement | undefined;
    const usernameInput = target?.usernameInput as HTMLInputElement | undefined;
    const serverInput = target?.serverInput as HTMLInputElement | undefined;
    let rawUsername = usernameInput?.value.trim() ?? '';
    const server = serverInput?.value.trim() || userServer;

    if (!usernameInput || !rawUsername) return;

    // If user pasted a full MXID like @alice:matrix.org, split it into fields
    if (isUserId(rawUsername)) {
      const parsedLocal = getMxIdLocalPart(rawUsername);
      const parsedServer = getMxIdServer(rawUsername);
      if (parsedLocal && parsedServer) {
        rawUsername = parsedLocal;
        usernameInput.value = parsedLocal;
        if (serverInput) serverInput.value = parsedServer;
      }
    }

    const username = rawUsername.replace(/^@/, '');
    if (!username || /[@:\s]/.test(username) || !isServerName(server)) {
      setInvalidUserId(true);
      return;
    }

    const userId = `@${username}:${server}`;

    const existing = getDMRoomFor(mx, userId);
    if (existing) {
      usernameInput.value = '';
      navigate(getDirectRoomPath(existing.roomId));
      onCreated?.(existing.roomId);
      return;
    }

    create(userId, encryption).then((roomId) => {
      if (alive()) {
        usernameInput.value = '';
        navigate(getDirectRoomPath(roomId));
        onCreated?.(roomId);
      }
    });
  };

  return (
    <Box as="form" onSubmit={handleSubmit} grow="Yes" direction="Column" gap={gap}>
      <Box direction="Column" gap="200">
        <Text as="span" className={SectionLabel}>
          {t('Direct.address')}
        </Text>
        <Box direction="Row" wrap="Wrap" gap="200">
          <Box grow="Yes" style={{ minWidth: toRem(120) }} direction="Column" gap="100">
            <Input
              defaultValue={defaultUsername}
              placeholder={t('Direct.username_placeholder')}
              name="usernameInput"
              variant="Background"
              size="500"
              radii="400"
              outlined
              before={
                <Text as="span" size="B500" className={Mono} style={{ opacity: 0.5 }}>
                  @
                </Text>
              }
              required
              autoFocus
              autoComplete="off"
              disabled={disabled}
            />
          </Box>
          <Box shrink="No" style={{ minWidth: toRem(150) }} direction="Column" gap="100">
            <Input
              defaultValue={
                defaultServer && defaultServer !== userServer ? defaultServer : undefined
              }
              placeholder={userServer}
              name="serverInput"
              variant="Background"
              size="500"
              radii="400"
              outlined
              className={Mono}
              autoComplete="off"
              disabled={disabled}
            />
          </Box>
        </Box>
        {invalidUserId && (
          <Box style={{ color: color.Critical.Main }} alignItems="Center" gap="100">
            <Icon src={Icons.Warning} size="50" />
            <Text size="T200" style={{ color: color.Critical.Main }}>
              {t('Direct.invalid_user_id')}
            </Text>
          </Box>
        )}
      </Box>
      <SettingsSection label={t('Direct.options')}>
        <SettingTile
          title={t('Direct.e2e_encryption')}
          description={t('Direct.e2e_encryption_desc')}
          after={
            <Switch
              variant="Primary"
              value={encryption}
              onChange={setEncryption}
              disabled={disabled}
            />
          }
        />
      </SettingsSection>
      {error && (
        <Box style={{ color: color.Critical.Main }} alignItems="Center" gap="200">
          <Icon src={Icons.Warning} size="100" />
          <Text size="T300" style={{ color: color.Critical.Main }}>
            {(() => {
              if (error instanceof MatrixError && error.name === ErrorCode.M_LIMIT_EXCEEDED) {
                const ra = error.data?.retry_after_ms;
                return t('Direct.rate_limited', {
                  minutes: millisecondsToMinutes(typeof ra === 'number' ? ra : 0),
                });
              }
              return error.message;
            })()}
          </Text>
        </Box>
      )}
      <Box shrink="No" direction="Column" gap="200">
        <Button
          type="submit"
          size="500"
          variant="Primary"
          radii="400"
          disabled={disabled}
          before={loading && <Spinner variant="Primary" fill="Solid" size="200" />}
        >
          <Text size="B500">{t('Direct.create')}</Text>
        </Button>
      </Box>
    </Box>
  );
}
