// Dawn-styled members sheet body. Renders a centred room hero
// (avatar + name + count + e2ee + optional topic) and a flat
// power-tag-grouped list of joined members. Mirrors the visual
// language of the 1:1 peer-profile sheet (`UserHero` + info table)
// so the two sheets read as one design system.
//
// No internal scroll container — the host (mobile horseshoe or
// desktop side pane) wraps this in its own scroll surface. That
// lets the host measure the natural content height for content-fit
// rail sizing.
//
// Tap on a row opens the per-user profile via `useOpenUserRoomProfile`.
// Atom mutual-exclusion (`useOpenUserRoomProfile` clears the members
// atom) routes the transition cleanly: on desktop the right-pane
// content swaps; on mobile group the same horseshoe silhouette
// switches body (handled in `RoomViewMembersPanel`).
//
// Search / filter / sort were intentionally not ported — product asked
// for a clean grouped list first. The legacy
// `features/room/MembersDrawer.tsx` keeps those affordances and is
// still used by `features/lobby/Lobby.tsx` for space lobbies.

import React, { useMemo } from 'react';
import { Avatar, Icon, Icons, Text } from 'folds';
import { Room, RoomMember } from 'matrix-js-sdk';
import classNames from 'classnames';
import { TypingIndicator } from '../typing-indicator';
import { UserAvatar } from '../user-avatar';
import { Membership, MemberPowerTag } from '../../../types/matrix/room';
import { GetMemberPowerTag, useFlattenPowerTagMembers } from '../../hooks/useMemberPowerTag';
import { useMemberPowerSort } from '../../hooks/useMemberSort';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers';
import { useOpenUserRoomProfile } from '../../state/hooks/userRoomProfile';
import { useSpaceOptionally } from '../../hooks/useSpace';
import { useGetMemberPowerLevel, usePowerLevelsContext } from '../../hooks/usePowerLevels';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { getMemberDisplayName } from '../../utils/room';
import { getMxIdLocalPart } from '../../utils/matrix';
import { RoomMembersHero } from './RoomMembersHero';
import * as css from './styles.css';

type MembersListProps = {
  room: Room;
  members: RoomMember[];
  getPowerTag: GetMemberPowerTag;
  // Forwarded to `RoomMembersHero` — tap on hero avatar opens
  // full-view. Only the mobile horseshoe wires this; the desktop
  // side pane doesn't get an avatar-zoom path (the right-pane chrome
  // is fixed-width so there's no «expand to silhouette»).
  onHeroAvatarClick?: () => void;
};

type MemberRowProps = {
  room: Room;
  member: RoomMember;
  typing: boolean;
  onOpenProfile: (userId: string, anchor: HTMLButtonElement) => void;
};
function MemberRow({ room, member, typing, onOpenProfile }: MemberRowProps) {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  const name =
    getMemberDisplayName(room, member.userId) ?? getMxIdLocalPart(member.userId) ?? member.userId;
  // First-name display per design: only what fits before the first space
  // (or the whole string if no space). Keeps each row narrow against the
  // ~240 px panel without horizontal ellipsis kicking in immediately.
  const shortName = name.split(/\s+/)[0] ?? name;
  const avatarMxcUrl = member.getMxcAvatarUrl();
  const avatarUrl = avatarMxcUrl
    ? mx.mxcUrlToHttp(avatarMxcUrl, 64, 64, 'crop', undefined, false, useAuthentication)
    : undefined;

  return (
    <button
      type="button"
      className={css.MemberRow}
      onClick={(evt) => onOpenProfile(member.userId, evt.currentTarget)}
    >
      <Avatar size="200" className={css.MemberAvatar}>
        <UserAvatar
          userId={member.userId}
          src={avatarUrl ?? undefined}
          alt={name}
          renderFallback={() => <Icon size="50" src={Icons.User} filled />}
        />
      </Avatar>
      <div className={css.MemberMainCol}>
        <div className={css.MemberNameRow}>
          <Text as="span" size="T300" truncate className={css.MemberName}>
            {shortName}
          </Text>
        </div>
        {typing && (
          <span className={css.MemberTyping}>
            <TypingIndicator size="300" />
          </span>
        )}
      </div>
    </button>
  );
}

export function MembersList({ room, members, getPowerTag, onHeroAvatarClick }: MembersListProps) {
  const openUserRoomProfile = useOpenUserRoomProfile();
  const space = useSpaceOptionally();
  const typingMembers = useRoomTypingMember(room.roomId);

  // Filter + sort BEFORE flatten — `useFlattenPowerTagMembers` emits a
  // new group header whenever the current member's tag differs from the
  // previous one's tag. Feeding it an unsorted list produces duplicate
  // role headers ("admin · 1, member · 5, admin · 1") and the row
  // counts go wrong. Mirrors the legacy `MembersDrawer.tsx::filteredMembers`
  // pipeline (filter joined-only → sort by power).
  //
  // Joined-only matches the design canon's "Участники · 28" — banned /
  // kicked / left memberships have their own surfaces in moderation
  // settings, not in the members sheet.
  const powerLevels = usePowerLevelsContext();
  const creators = useRoomCreators(room);
  const getPowerLevel = useGetMemberPowerLevel(powerLevels);
  const memberPowerSort = useMemberPowerSort(creators, getPowerLevel);
  const processedMembers = useMemo(
    () => members.filter((m) => m.membership === Membership.Join).sort(memberPowerSort),
    [members, memberPowerSort]
  );
  const flat = useFlattenPowerTagMembers(processedMembers, getPowerTag);

  // Index of the first tag entry in the flat list — used to drop the
  // top padding on the first section header so it lines up tight with
  // the rule above (the List's top border).
  const firstTagIndex = flat.findIndex((entry) => !('userId' in entry));

  // Per-tag member count for the section header — cheap (single pass)
  // and lets the design's "admin · 1 / mod · 1 / участники · 22" labels
  // read truthfully.
  const tagCounts = useMemo(() => {
    const counts = new Map<MemberPowerTag, number>();
    flat.forEach((entry) => {
      if ('userId' in entry) return;
      counts.set(entry, 0);
    });
    let currentTag: MemberPowerTag | undefined;
    flat.forEach((entry) => {
      if (!('userId' in entry)) {
        currentTag = entry;
        return;
      }
      if (currentTag) counts.set(currentTag, (counts.get(currentTag) ?? 0) + 1);
    });
    return counts;
  }, [flat]);

  const typingByUser = useMemo(() => {
    const set = new Set<string>();
    typingMembers.forEach((receipt) => set.add(receipt.userId));
    return set;
  }, [typingMembers]);

  const handleOpenProfile = (userId: string, anchor: HTMLButtonElement) => {
    openUserRoomProfile(room.roomId, space?.roomId, userId, anchor.getBoundingClientRect(), 'Left');
  };

  return (
    <div className={css.Root}>
      <RoomMembersHero room={room} onAvatarClick={onHeroAvatarClick} />
      <div className={css.List}>
        {flat.map((entry, idx) => {
          if (!('userId' in entry)) {
            const count = tagCounts.get(entry) ?? 0;
            return (
              <div
                key={`tag-${entry.name}`}
                className={classNames(css.GroupLabel, idx === firstTagIndex && css.GroupLabelFirst)}
              >
                <Text as="span" size="T200" className={css.GroupLabelText}>
                  {entry.name}
                </Text>
                <Text as="span" size="T200" className={css.GroupLabelCount}>
                  {count}
                </Text>
              </div>
            );
          }
          return (
            <MemberRow
              key={`user-${entry.userId}`}
              room={room}
              member={entry}
              typing={typingByUser.has(entry.userId)}
              onOpenProfile={handleOpenProfile}
            />
          );
        })}
      </div>
    </div>
  );
}
