import FocusTrap from 'focus-trap-react';
import {
  Avatar,
  Box,
  color,
  config,
  Icon,
  Icons,
  MenuItem,
  Modal,
  Overlay,
  OverlayCenter,
  IconButton,
  Scroll,
  Spinner,
  Text,
  toRem,
} from 'folds';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { isKeyHotkey } from 'is-hotkey';
import { useAtom } from 'jotai';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { RoomAvatar, RoomIcon } from '../../components/room-avatar';
import { UserAvatar } from '../../components/user-avatar';
import {
  getAllParents,
  getDirectRoomAvatarUrl,
  getRoomAvatarUrl,
  guessPerfectParent,
} from '../../utils/room';
import { nameInitials } from '../../utils/common';
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { getMxIdLocalPart, getMxIdServer, mxcUrlToHttp } from '../../utils/matrix';
import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge';
import { searchModalAtom } from '../../state/searchModal';
import { useKeyDown } from '../../hooks/useKeyDown';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { getDmUserId, useRoomSearch } from './useRoomSearch';
import { StartDirectDialog } from './StartDirectDialog';

// Dawn hairline divider (the canon's rgba(255,255,255,0.06–0.08) row line).
const HAIRLINE = '1px solid rgba(255, 255, 255, 0.08)';

type SearchProps = {
  requestClose: () => void;
};
export function Search({ requestClose }: SearchProps) {
  const { t } = useTranslation();
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  const { navigateRoom, navigateSpace } = useRoomNavigate();

  const openRoomId = useCallback(
    (roomId: string, isSpace: boolean) => {
      if (isSpace) navigateSpace(roomId);
      else navigateRoom(roomId);
      requestClose();
    },
    [navigateRoom, navigateSpace, requestClose]
  );

  const {
    inputRef,
    scrollRef,
    roomsToRender,
    result,
    listFocus,
    query,
    clearSearch,
    handleInputChange,
    handleInputKeyDown,
    handleRoomClick,
    peopleToRender,
    directoryLoading,
    requestDirect,
    creatingUserId,
    addressPreview,
    dmError,
    pendingDirect,
    confirmDirect,
    cancelDirect,
    roomCount,
    roomIndexBase,
    addressIndex,
    getRoom,
    mDirects,
    orphanSpaces,
    roomToParents,
    roomToUnread,
    myUserId,
  } = useRoomSearch({ onOpenRoomId: openRoomId });

  const isEmpty = roomsToRender.length === 0 && peopleToRender.length === 0 && !addressPreview;

  // Surfaced inside the confirm dialog (the only place a DM is created).
  let dmErrorText: string | undefined;
  if (dmError) {
    dmErrorText = dmError.rateLimited ? t('Search.dm_rate_limited') : t('Search.dm_failed');
  }

  // The typed-address card, rendered at the top of the results. A clean
  // 404 (`notfound`) is a non-clickable note — you can't start a chat with
  // an account that doesn't exist. `resolved` (exists) and `unreachable`
  // (couldn't verify) are clickable and open the confirm dialog.
  const addressCard = addressPreview
    ? (() => {
        const username = getMxIdLocalPart(addressPreview.userId) ?? addressPreview.userId;
        const server = getMxIdServer(addressPreview.userId);
        const header = (
          <Text
            size="L400"
            priority="300"
            style={{ padding: `${toRem(8)} ${toRem(8)} ${toRem(4)}` }}
          >
            {t('Search.by_address')}
          </Text>
        );

        if (addressPreview.state === 'notfound') {
          return (
            <>
              {header}
              <Box
                alignItems="Center"
                gap="200"
                style={{ padding: `${toRem(6)} ${toRem(10)} ${toRem(8)}` }}
              >
                <Icon size="100" src={Icons.Warning} style={{ color: color.Warning.Main }} />
                <Box direction="Column" style={{ minWidth: 0 }}>
                  <Text size="T300" style={{ color: color.Warning.Main }}>
                    {t('Search.user_not_found', { server })}
                  </Text>
                  <Text
                    as="span"
                    size="T200"
                    priority="300"
                    truncate
                    style={{ fontFamily: 'var(--font-mono)' }}
                  >
                    {addressPreview.userId}
                  </Text>
                </Box>
              </Box>
            </>
          );
        }

        const focused = listFocus.index === addressIndex;
        const resolved = addressPreview.state === 'resolved';
        const name = addressPreview.displayName || username;
        const avatarSrc =
          resolved && addressPreview.avatarUrl
            ? mxcUrlToHttp(mx, addressPreview.avatarUrl, useAuthentication, 32, 32, 'crop') ??
              undefined
            : undefined;
        const caption = resolved ? undefined : t('Search.user_unreachable', { server });
        return (
          <>
            {header}
            <MenuItem
              as="button"
              data-focus-index={addressIndex}
              onClick={() =>
                requestDirect({
                  userId: addressPreview.userId,
                  displayName: addressPreview.displayName,
                  avatarUrl: addressPreview.avatarUrl,
                })
              }
              variant="Surface"
              aria-pressed={focused}
              radii="300"
              style={{ backgroundColor: focused ? 'rgba(255, 255, 255, 0.06)' : undefined }}
              after={
                server && (
                  <Text
                    size="T200"
                    priority="300"
                    truncate
                    style={{ fontFamily: 'var(--font-mono)' }}
                  >
                    {server}
                  </Text>
                )
              }
              before={
                <Avatar size="200" radii="400">
                  <UserAvatar
                    userId={addressPreview.userId}
                    src={avatarSrc}
                    alt={name}
                    renderFallback={() => (
                      <Text as="span" size="H6">
                        {nameInitials(name)}
                      </Text>
                    )}
                  />
                </Avatar>
              }
            >
              <Box grow="Yes" alignItems="Center" gap="100">
                <Text size="T400" truncate>
                  {name}
                </Text>
                <Text
                  as="span"
                  size="T200"
                  priority="300"
                  truncate
                  style={{ fontFamily: 'var(--font-mono)' }}
                >
                  @{username}
                </Text>
              </Box>
            </MenuItem>
            {caption && (
              <Text
                size="T200"
                priority="300"
                style={{ display: 'block', padding: `0 ${toRem(8)} ${toRem(4)}` }}
              >
                {caption}
              </Text>
            )}
          </>
        );
      })()
    : null;

  return (
    <>
      <Overlay open>
        <OverlayCenter>
          <FocusTrap
            focusTrapOptions={{
              initialFocus: () => inputRef.current,
              returnFocusOnDeactivate: false,
              allowOutsideClick: true,
              clickOutsideDeactivates: true,
              onDeactivate: requestClose,
              escapeDeactivates: (evt) => {
                evt.stopPropagation();
                return true;
              },
            }}
          >
            <Modal
              size="400"
              style={{
                // `size="400"` only sets max-width/height, so without an
                // explicit width the modal shrinks to its content — making
                // it narrow when empty and widening as result rows appear.
                // Pin width to fill up to the size cap so it stays a stable
                // width from the first keystroke.
                width: '100%',
                maxHeight: toRem(400),
                borderRadius: config.radii.R400,
                backgroundColor: '#181a20',
                border: HAIRLINE,
                boxShadow: '0 16px 48px rgba(0, 0, 0, 0.5)',
              }}
            >
              {/* ── Input row: hairline-divided, integrated (not a boxed Input) ──
                `width: 100%` pins the row to the modal width so the
                flex `input` always has space to fill — otherwise the row
                collapses to its content (search icon) when empty and
                only widens as you type. */}
              <Box
                shrink="No"
                alignItems="Center"
                gap="200"
                style={{
                  width: '100%',
                  minWidth: 0,
                  padding: `${toRem(12)} ${config.space.S400}`,
                  borderBottom: HAIRLINE,
                }}
              >
                <Icon size="200" src={Icons.Search} />
                <input
                  ref={inputRef}
                  type="text"
                  placeholder={t('Search.search')}
                  autoComplete="off"
                  onChange={handleInputChange}
                  onKeyDown={handleInputKeyDown}
                  style={{
                    flex: 1,
                    appearance: 'none',
                    border: 'none',
                    outline: 'none',
                    background: 'transparent',
                    font: 'inherit',
                    fontSize: toRem(15),
                    color: color.Background.OnContainer,
                    minWidth: 0,
                  }}
                />
                {directoryLoading ? (
                  <Spinner size="200" variant="Secondary" />
                ) : (
                  query.length > 0 && (
                    <IconButton
                      type="button"
                      size="300"
                      radii="Pill"
                      variant="SurfaceVariant"
                      fill="None"
                      onClick={clearSearch}
                      aria-label={t('Search.clear')}
                    >
                      <Icon size="100" src={Icons.Cross} />
                    </IconButton>
                  )
                )}
              </Box>
              <Box grow="Yes">
                {isEmpty && !directoryLoading && (
                  <Box
                    style={{ paddingTop: config.space.S700 }}
                    grow="Yes"
                    alignItems="Center"
                    justifyContent="Center"
                    direction="Column"
                    gap="100"
                  >
                    <Text size="H6" align="Center">
                      {result ? t('Search.no_match_found') : t('Search.no_rooms')}
                    </Text>
                    <Text size="T200" align="Center" style={{ maxWidth: toRem(300) }}>
                      {result
                        ? t('Search.no_match_for_query', { query: result.query })
                        : t('Search.no_rooms_to_display')}
                    </Text>
                    <Text
                      size="T200"
                      align="Center"
                      priority="300"
                      style={{ maxWidth: toRem(300), marginTop: config.space.S200 }}
                    >
                      {t('Search.address_hint')}
                    </Text>
                  </Box>
                )}
                {!isEmpty && (
                  <Scroll ref={scrollRef} size="300" hideTrack>
                    <div style={{ padding: config.space.S200, paddingRight: config.space.S100 }}>
                      {/* Typed address → always the top result. */}
                      {addressCard}
                      {roomsToRender.map((roomId, index) => {
                        const room = getRoom(roomId);
                        if (!room) return null;

                        const dm = mDirects.has(roomId);
                        const dmUserId = dm ? getDmUserId(roomId, getRoom, myUserId) : undefined;
                        const dmUsername = dmUserId ? getMxIdLocalPart(dmUserId) : undefined;
                        const dmUserServer = dmUserId ? getMxIdServer(dmUserId) : undefined;

                        const allParents = getAllParents(roomToParents, roomId);
                        const orphanParents = allParents
                          ? orphanSpaces.filter((o) => allParents.has(o))
                          : undefined;
                        const perfectOrphanParent =
                          orphanParents && guessPerfectParent(mx, roomId, orphanParents);

                        const exactParents = roomToParents.get(roomId);
                        const perfectParent =
                          exactParents && guessPerfectParent(mx, roomId, Array.from(exactParents));

                        const unread = roomToUnread.get(roomId);
                        const focusIndex = roomIndexBase + index;
                        const focused = listFocus.index === focusIndex;

                        return (
                          <MenuItem
                            key={roomId}
                            as="button"
                            data-focus-index={focusIndex}
                            data-room-id={roomId}
                            data-space={room.isSpaceRoom()}
                            onClick={handleRoomClick}
                            variant="Surface"
                            aria-pressed={focused}
                            radii="300"
                            style={{
                              // Neutral focus highlight — a soft surface tint,
                              // no accent colour (the violet selection read as
                              // garish next to the yellow match highlight).
                              backgroundColor: focused ? 'rgba(255, 255, 255, 0.06)' : undefined,
                            }}
                            after={
                              <Box gap="100">
                                {dmUserServer && (
                                  <Text
                                    size="T200"
                                    priority="300"
                                    truncate
                                    style={{ fontFamily: 'var(--font-mono)' }}
                                  >
                                    {dmUserServer}
                                  </Text>
                                )}
                                {!dm && perfectOrphanParent && (
                                  <Text
                                    size="T200"
                                    priority="300"
                                    truncate
                                    style={{ fontFamily: 'var(--font-mono)' }}
                                  >
                                    {getRoom(perfectOrphanParent)?.name ?? perfectOrphanParent}
                                  </Text>
                                )}
                                {unread && (
                                  <UnreadBadgeCenter>
                                    <UnreadBadge
                                      highlight={unread.highlight > 0}
                                      count={unread.total}
                                    />
                                  </UnreadBadgeCenter>
                                )}
                              </Box>
                            }
                            before={
                              <Avatar size="200" radii={dm ? '400' : '300'}>
                                {dm || room.isSpaceRoom() ? (
                                  <RoomAvatar
                                    roomId={room.roomId}
                                    src={
                                      dm
                                        ? getDirectRoomAvatarUrl(mx, room, 32, useAuthentication)
                                        : getRoomAvatarUrl(mx, room, 32, useAuthentication)
                                    }
                                    alt={room.name}
                                    renderFallback={() => (
                                      <Text as="span" size="H6">
                                        {nameInitials(room.name)}
                                      </Text>
                                    )}
                                  />
                                ) : (
                                  <RoomIcon
                                    size="100"
                                    joinRule={room.getJoinRule()}
                                    roomType={room.getType()}
                                  />
                                )}
                              </Avatar>
                            }
                          >
                            <Box grow="Yes" alignItems="Center" gap="100">
                              <Text size="T400" truncate>
                                {room.name}
                              </Text>
                              {dmUsername && (
                                <Text
                                  as="span"
                                  size="T200"
                                  priority="300"
                                  truncate
                                  style={{ fontFamily: 'var(--font-mono)' }}
                                >
                                  @{dmUsername}
                                </Text>
                              )}
                              {!dm && perfectParent && perfectParent !== perfectOrphanParent && (
                                <Text size="T200" priority="300" truncate>
                                  — {getRoom(perfectParent)?.name ?? perfectParent}
                                </Text>
                              )}
                            </Box>
                          </MenuItem>
                        );
                      })}

                      {/* ── People (homeserver user directory) ─────────── */}
                      {peopleToRender.length > 0 && (
                        <>
                          <Text
                            size="L400"
                            priority="300"
                            style={{ padding: `${toRem(8)} ${toRem(8)} ${toRem(4)}` }}
                          >
                            {t('Search.people')}
                          </Text>
                          {peopleToRender.map((user, j) => {
                            const index = roomIndexBase + roomCount + j;
                            const focused = listFocus.index === index;
                            const username = getMxIdLocalPart(user.userId) ?? user.userId;
                            const server = getMxIdServer(user.userId);
                            const name = user.displayName || username;
                            const avatarSrc = user.avatarUrl
                              ? mxcUrlToHttp(
                                  mx,
                                  user.avatarUrl,
                                  useAuthentication,
                                  32,
                                  32,
                                  'crop'
                                ) ?? undefined
                              : undefined;

                            return (
                              <MenuItem
                                key={user.userId}
                                as="button"
                                data-focus-index={index}
                                onClick={() =>
                                  requestDirect({
                                    userId: user.userId,
                                    displayName: user.displayName,
                                    avatarUrl: user.avatarUrl,
                                  })
                                }
                                variant="Surface"
                                aria-pressed={focused}
                                radii="300"
                                style={{
                                  backgroundColor: focused
                                    ? 'rgba(255, 255, 255, 0.06)'
                                    : undefined,
                                }}
                                after={
                                  server && (
                                    <Text
                                      size="T200"
                                      priority="300"
                                      truncate
                                      style={{ fontFamily: 'var(--font-mono)' }}
                                    >
                                      {server}
                                    </Text>
                                  )
                                }
                                before={
                                  <Avatar size="200" radii="400">
                                    <UserAvatar
                                      userId={user.userId}
                                      src={avatarSrc}
                                      alt={name}
                                      renderFallback={() => (
                                        <Text as="span" size="H6">
                                          {nameInitials(name)}
                                        </Text>
                                      )}
                                    />
                                  </Avatar>
                                }
                              >
                                <Box grow="Yes" alignItems="Center" gap="100">
                                  <Text size="T400" truncate>
                                    {name}
                                  </Text>
                                  <Text
                                    as="span"
                                    size="T200"
                                    priority="300"
                                    truncate
                                    style={{ fontFamily: 'var(--font-mono)' }}
                                  >
                                    @{username}
                                  </Text>
                                </Box>
                              </MenuItem>
                            );
                          })}
                        </>
                      )}
                    </div>
                  </Scroll>
                )}
              </Box>
              {/* ── Mono keyboard-hint footer (hairline-divided) ── */}
              <Box
                shrink="No"
                justifyContent="Center"
                gap="300"
                style={{
                  padding: `${toRem(8)} ${config.space.S300}`,
                  borderTop: HAIRLINE,
                  fontFamily: 'var(--font-mono)',
                }}
              >
                <Text size="T200" priority="300" style={{ fontFamily: 'var(--font-mono)' }}>
                  {`↑↓ ${t('Search.kbd_select')}`}
                </Text>
                <Text size="T200" priority="300" style={{ fontFamily: 'var(--font-mono)' }}>
                  {`↵ ${t('Search.kbd_open')}`}
                </Text>
                <Text size="T200" priority="300" style={{ fontFamily: 'var(--font-mono)' }}>
                  {`esc ${t('Search.kbd_close')}`}
                </Text>
              </Box>
            </Modal>
          </FocusTrap>
        </OverlayCenter>
      </Overlay>
      {pendingDirect && (
        <StartDirectDialog
          key={pendingDirect.userId}
          target={pendingDirect}
          creating={creatingUserId === pendingDirect.userId}
          errorText={dmErrorText}
          onConfirm={confirmDirect}
          onCancel={cancelDirect}
        />
      )}
    </>
  );
}

export function SearchModalRenderer() {
  const [opened, setOpen] = useAtom(searchModalAtom);

  useKeyDown(
    window,
    useCallback(
      (event) => {
        if (isKeyHotkey('mod+k', event)) {
          event.preventDefault();
          if (opened) {
            setOpen(false);
            return;
          }

          const portalContainer = document.getElementById('portalContainer');
          if (portalContainer && portalContainer.children.length > 0) {
            return;
          }
          setOpen(true);
        }
      },
      [opened, setOpen]
    )
  );

  return opened && <Search requestClose={() => setOpen(false)} />;
}
