import React, { Fragment, MouseEventHandler, MutableRefObject, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useAtom, useAtomValue } from 'jotai';
import { Box } from 'folds';
import { Room } from 'matrix-js-sdk';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { mDirectAtom } from '../../../state/mDirectList';
import { NavCategory } from '../../../components/nav';
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
import { useSpace } from '../../../hooks/useSpace';
import { RoomNavItem } from '../../../features/room-nav';
import { makeNavCategoryId } from '../../../state/closedNavCategories';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
import { HierarchyItem, useSpaceJoinedHierarchy } from '../../../hooks/useSpaceHierarchy';
import { allRoomsAtom } from '../../../state/room-list/roomList';
import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories';
import { useCallEmbed } from '../../../hooks/useCallEmbed';
import {
  getRoomNotificationMode,
  useRoomsNotificationPreferencesContext,
} from '../../../hooks/useRoomsNotificationPreferences';
import { getChannelsRoomPath } from '../../pathUtils';
import { BridgedRoomKind, getBridgedRoomKind } from '../../../utils/room';
import * as css from './ChannelsList.css';

type SectionTint = 'chats' | 'groups' | 'broadcasts' | 'neutral';

// Collapsible-category order for bridge spaces. mautrix puts ALL portals —
// DMs, groups and broadcast channels — under one space; instead of a single
// «Каналы» root category we split the root section into one category per
// portal kind (same collapsible-шторка idiom). Synthetic parent tokens keep
// the collapse state distinct per kind in closedNavCategories without
// colliding with real room ids.
const BRIDGED_SECTIONS: Array<{
  kind: BridgedRoomKind;
  token: string;
  labelKey: string;
  tint: SectionTint;
}> = [
  { kind: 'dm', token: 'vojo.bridged.dm', labelKey: 'Channels.category_chats', tint: 'chats' },
  {
    kind: 'group',
    token: 'vojo.bridged.group',
    labelKey: 'Channels.category_groups',
    tint: 'groups',
  },
  {
    kind: 'broadcast',
    token: 'vojo.bridged.broadcast',
    labelKey: 'Channels.category_broadcasts',
    tint: 'broadcasts',
  },
];

// 16px stroke glyphs for the tinted section squares — same visual job as the
// colored letter-avatars in the Dawn sidebar mock.
const SECTION_ICONS: Record<SectionTint, React.ReactNode> = {
  chats: (
    <svg
      width="16"
      height="16"
      viewBox="0 0 20 20"
      fill="none"
      stroke="currentColor"
      strokeWidth="1.7"
      aria-hidden="true"
    >
      <path
        d="M3.5 9.4c0-3.3 2.9-5.9 6.5-5.9s6.5 2.6 6.5 5.9-2.9 5.9-6.5 5.9c-.8 0-1.6-.1-2.3-.4L4.6 16l.9-2.7c-1.2-1-2-2.4-2-3.9Z"
        strokeLinejoin="round"
      />
    </svg>
  ),
  groups: (
    <svg
      width="16"
      height="16"
      viewBox="0 0 20 20"
      fill="none"
      stroke="currentColor"
      strokeWidth="1.7"
      aria-hidden="true"
    >
      <circle cx="7.2" cy="7.4" r="2.6" />
      <path d="M2.8 15.6c.7-2.3 2.4-3.5 4.4-3.5s3.7 1.2 4.4 3.5" strokeLinecap="round" />
      <circle cx="13.8" cy="7.4" r="2.2" />
      <path d="M13.6 12.1c1.8.1 3.2 1.2 3.8 3.2" strokeLinecap="round" />
    </svg>
  ),
  broadcasts: (
    <svg
      width="16"
      height="16"
      viewBox="0 0 20 20"
      fill="none"
      stroke="currentColor"
      strokeWidth="1.7"
      aria-hidden="true"
    >
      <path d="M3.5 8.2v3.6l2.8.6 7.2 3V4.6l-7.2 3-2.8.6Z" strokeLinejoin="round" />
      <path d="M16 8a3.4 3.4 0 0 1 0 4" strokeLinecap="round" />
      <path d="M6.8 12.8l.9 3.4" strokeLinecap="round" />
    </svg>
  ),
  neutral: (
    <svg
      width="16"
      height="16"
      viewBox="0 0 20 20"
      fill="none"
      stroke="currentColor"
      strokeWidth="1.7"
      aria-hidden="true"
    >
      <path d="M7.5 3.5 6 16.5M14 3.5l-1.5 13M4 7.5h13M3 12.5h13" strokeLinecap="round" />
    </svg>
  ),
};

type SectionHeaderProps = {
  categoryId: string;
  label: string;
  closed: boolean;
  onClick: MouseEventHandler<HTMLButtonElement>;
  tint?: SectionTint;
  count?: number;
  first?: boolean;
};

// Dawn-canon section header: tinted square glyph + regular app type + count
// pill (the mock's badge shape in a neutral tone) + rotating chevron. The
// whole row is the collapse target; vertical rhythm is deliberately tight —
// the previous chip-on-padding stack read as a big gap between sections.
function ChannelsSectionHeader({
  categoryId,
  label,
  closed,
  onClick,
  tint = 'neutral',
  count,
  first,
}: SectionHeaderProps) {
  return (
    <div className={first ? undefined : css.SectionGap}>
      <button
        type="button"
        className={css.SectionHeader}
        data-category-id={categoryId}
        onClick={onClick}
        aria-expanded={!closed}
      >
        <span className={css.SectionIcon({ tint })}>{SECTION_ICONS[tint]}</span>
        <span className={css.SectionLabel}>{label}</span>
        {count !== undefined ? <span className={css.SectionCount}>{count}</span> : null}
        <span className={css.SectionChevron({ closed })}>
          <svg
            width="12"
            height="12"
            viewBox="0 0 16 16"
            fill="none"
            stroke="currentColor"
            strokeWidth="1.8"
            aria-hidden="true"
          >
            <path d="M4 6 L8 10 L12 6" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </span>
      </button>
    </div>
  );
}

type ChannelsListProps = {
  // Kept for API compatibility with PageNavContent's scrollRef wiring even
  // though the list itself no longer virtualizes — parent still passes the
  // ref so other consumers (StreamHeader curtain gesture) can read it.
  scrollRef: MutableRefObject<HTMLDivElement | null>;
};

// Joined-hierarchy list rendered inside the channels left pane. Mirrors the
// scaffold of `Space.tsx` (hierarchy of joined rooms grouped by sub-spaces)
// but routes selections through the /channels/<space>/<room>/ path so the
// room timeline opens inside the channels surface, not the legacy
// /<spaceId>/<room>/ tree.
//
// NOT virtualized on purpose. Lists in the channels pane are bounded (one
// orphan workspace + its joined rooms — typically tens, not thousands),
// and @tanstack/react-virtual had a fragile race here with folds' Scroll
// component: every re-render of `<Scroll>` creates a fresh inline ref
// callback, React 18 detaches the old callback (nulling scrollRef.current)
// before children's useLayoutEffects run, and the virtualizer's
// `_willUpdate` saw null on its first chance to subscribe — leaving the
// list permanently empty on the 2nd+ remount of the same workspace. Plain
// `.map()` sidesteps the whole subscription dance.
export function ChannelsList({ scrollRef: _scrollRef }: ChannelsListProps) {
  const { t } = useTranslation();
  const mx = useMatrixClient();
  const space = useSpace();
  const mDirects = useAtomValue(mDirectAtom);
  const roomToUnread = useAtomValue(roomToUnreadAtom);
  const allRooms = useAtomValue(allRoomsAtom);
  const allJoinedRooms = useMemo(() => new Set(allRooms), [allRooms]);
  const notificationPreferences = useRoomsNotificationPreferencesContext();

  const selectedRoomId = useSelectedRoom();
  const callEmbed = useCallEmbed();
  const spaceIdOrAlias = getCanonicalAliasOrRoomId(mx, space.roomId);

  const [closedCategories, setClosedCategories] = useAtom(useClosedNavCategoriesAtom());

  const getRoom = useCallback(
    (rId: string): Room | undefined => {
      if (allJoinedRooms.has(rId)) {
        return mx.getRoom(rId) ?? undefined;
      }
      return undefined;
    },
    [mx, allJoinedRooms]
  );

  // A room stays visible inside a closed category when it demands attention —
  // same rule the upstream hierarchy filter applies to real sub-spaces.
  const showRoomAnyway = useCallback(
    (roomId: string): boolean =>
      roomToUnread.has(roomId) || roomId === selectedRoomId || callEmbed?.roomId === roomId,
    [roomToUnread, selectedRoomId, callEmbed]
  );

  const hierarchy = useSpaceJoinedHierarchy(
    space.roomId,
    getRoom,
    useCallback(
      (parentId, roomId) => {
        if (!closedCategories.has(makeNavCategoryId(space.roomId, parentId))) {
          return false;
        }
        return !showRoomAnyway(roomId);
      },
      [space.roomId, closedCategories, showRoomAnyway]
    ),
    useCallback(
      (sId) => closedCategories.has(makeNavCategoryId(space.roomId, sId)),
      [closedCategories, space.roomId]
    )
  );

  const handleCategoryClick = useCategoryHandler(setClosedCategories, (categoryId) =>
    closedCategories.has(categoryId)
  );

  const getToLink = (roomId: string) =>
    getChannelsRoomPath(spaceIdOrAlias, getCanonicalAliasOrRoomId(mx, roomId));

  // Classify the ROOT section's rooms by the type their bridge stamped into
  // `m.bridge` (dm / group / broadcast — see getBridgedRoomKind). Sub-space
  // sections (e.g. Telegram forum supergroups) keep their own headers and are
  // not re-bucketed. When the root section doesn't mix at least two kinds
  // (non-bridge spaces, single-kind spaces), rendering falls back to the
  // plain «Каналы» root category exactly as before.
  const rootKinds = useMemo(() => {
    const kinds = new Map<string, BridgedRoomKind | undefined>();
    hierarchy.forEach((item) => {
      if ('space' in item && item.space) return;
      if (item.parentId !== space.roomId) return;
      const room = getRoom(item.roomId);
      if (!room) return;
      kinds.set(item.roomId, getBridgedRoomKind(mx, room));
    });
    return kinds;
  }, [hierarchy, getRoom, mx, space.roomId]);

  const splitRootByKind = useMemo(() => {
    const present = new Set<BridgedRoomKind>();
    rootKinds.forEach((kind) => {
      if (kind) present.add(kind);
    });
    return present.size >= 2;
  }, [rootKinds]);

  // Buckets preserve hierarchy order. Unbridged rooms inside a bridge space
  // (rare) land with broadcasts under «Каналы» so nothing silently vanishes.
  const rootBuckets = useMemo(() => {
    if (!splitRootByKind) return null;
    const buckets: Record<BridgedRoomKind, HierarchyItem[]> = {
      dm: [],
      group: [],
      broadcast: [],
    };
    hierarchy.forEach((item) => {
      if ('space' in item && item.space) return;
      if (item.parentId !== space.roomId) return;
      buckets[rootKinds.get(item.roomId) ?? 'broadcast'].push(item);
    });
    return buckets;
  }, [splitRootByKind, hierarchy, rootKinds, space.roomId]);

  const renderRoom = (roomId: string) => {
    const room = mx.getRoom(roomId);
    if (!room) return null;
    return (
      <RoomNavItem
        key={roomId}
        room={room}
        selected={selectedRoomId === roomId}
        showAvatar={mDirects.has(roomId)}
        direct={mDirects.has(roomId)}
        linkPath={getToLink(roomId)}
        notificationMode={getRoomNotificationMode(notificationPreferences, room.roomId)}
      />
    );
  };

  // Render plan: group the flat hierarchy into header+rooms sections so each
  // section's rooms can nest inside the indented guide block (SectionChildren)
  // under its шторка. Bridge-space root expands into the per-kind sections.
  type RenderSection =
    | { type: 'bridged'; def: typeof BRIDGED_SECTIONS[number]; items: HierarchyItem[] }
    | { type: 'plain'; headerRoomId: string; label: string; rooms: string[] };

  const sections: RenderSection[] = [];
  hierarchy.forEach((item) => {
    const room = mx.getRoom(item.roomId);
    if (!room) return;
    if (room.isSpaceRoom()) {
      if (item.roomId === space.roomId && rootBuckets) {
        BRIDGED_SECTIONS.forEach((def) => {
          const items = rootBuckets[def.kind];
          if (items.length > 0) sections.push({ type: 'bridged', def, items });
        });
        return;
      }
      sections.push({
        type: 'plain',
        headerRoomId: item.roomId,
        label: item.roomId === space.roomId ? t('Channels.root_category') : room.name ?? '',
        rooms: [],
      });
      return;
    }
    // Root rooms of a bridge space live inside the per-kind sections.
    if (rootBuckets && item.parentId === space.roomId) return;
    const last = sections[sections.length - 1];
    if (last && last.type === 'plain') last.rooms.push(item.roomId);
  });

  return (
    <Box direction="Column" gap="300">
      <NavCategory>
        {sections.map((section, index) => {
          if (section.type === 'bridged') {
            const { def, items } = section;
            const categoryId = makeNavCategoryId(space.roomId, def.token);
            const closed = closedCategories.has(categoryId);
            const visibleItems = closed
              ? items.filter((item) => showRoomAnyway(item.roomId))
              : items;
            return (
              <Fragment key={def.token}>
                <ChannelsSectionHeader
                  categoryId={categoryId}
                  label={t(def.labelKey)}
                  closed={closed}
                  onClick={handleCategoryClick}
                  tint={def.tint}
                  count={items.length}
                  first={index === 0}
                />
                {visibleItems.length > 0 ? (
                  <div className={css.SectionChildren}>
                    {visibleItems.map((item) => renderRoom(item.roomId))}
                  </div>
                ) : null}
              </Fragment>
            );
          }

          const categoryId = makeNavCategoryId(space.roomId, section.headerRoomId);
          return (
            <Fragment key={section.headerRoomId}>
              <ChannelsSectionHeader
                categoryId={categoryId}
                label={section.label}
                closed={closedCategories.has(categoryId)}
                onClick={handleCategoryClick}
                first={index === 0}
              />
              {section.rooms.length > 0 ? (
                <div className={css.SectionChildren}>
                  {section.rooms.map((roomId) => renderRoom(roomId))}
                </div>
              ) : null}
            </Fragment>
          );
        })}
      </NavCategory>
    </Box>
  );
}
