part of '../rooms_panel.dart';

class _SpaceRoomsList extends ConsumerStatefulWidget {
  final String spaceId;
  final String filter;
  final Map<String, bool> collapsed;
  final PyramidTheme pt;
  final ValueChanged<String> onToggleSection;

  const _SpaceRoomsList({
    required this.spaceId,
    required this.filter,
    required this.collapsed,
    required this.pt,
    required this.onToggleSection,
  });

  @override
  ConsumerState<_SpaceRoomsList> createState() => _SpaceRoomsListState();
}

class _SpaceRoomsListState extends ConsumerState<_SpaceRoomsList> {
  // Local order override after drag-and-drop (room IDs in order per category key)
  final Map<String, List<String>> _localOrder = {};

  // Joinable channels discovered via the space hierarchy (not yet a member).
  List<_JoinableRoom> _joinable = [];
  final Set<String> _joining = {};

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      final client = ref.read(matrixClientProvider).valueOrNull;
      if (client != null) _loadHierarchy(client);
    });
  }

  @override
  void didUpdateWidget(covariant _SpaceRoomsList old) {
    super.didUpdateWidget(old);
    if (old.spaceId != widget.spaceId) {
      setState(() => _joinable = []);
      final client = ref.read(matrixClientProvider).valueOrNull;
      if (client != null) _loadHierarchy(client);
    }
  }

  /// Queries the server-side space hierarchy and keeps only rooms the user is
  /// not yet a member of — these are the channels they can still join.
  Future<void> _loadHierarchy(Client client) async {
    try {
      final resp = await client.getSpaceHierarchy(
        widget.spaceId,
        maxDepth: 3,
        suggestedOnly: false,
      );
      final joinable = <_JoinableRoom>[];
      for (final chunk in resp.rooms) {
        if (chunk.roomId == widget.spaceId) continue;
        if (chunk.roomType == 'm.space') continue; // skip sub-spaces
        // Keep every channel the hierarchy reports. The build method filters out
        // the ones already rendered in the joined sections — relying on local
        // membership here is unreliable because Continuwuity's sync lags behind.
        joinable.add(_JoinableRoom(
          roomId: chunk.roomId,
          name: chunk.name ?? chunk.canonicalAlias ?? chunk.roomId,
          isVoice: chunk.roomType == _kVoiceRoomType,
          memberCount: chunk.numJoinedMembers,
        ));
      }
      if (mounted) setState(() => _joinable = joinable);
    } catch (_) {
      // Hierarchy endpoint unavailable / no permission — silently ignore.
    }
  }

  Future<void> _joinAndEnter(Client client, _JoinableRoom jr) async {
    setState(() => _joining.add(jr.roomId));
    try {
      await client.joinRoomById(jr.roomId).timeout(const Duration(seconds: 20));
      // Give the sync a brief chance to deliver the room object, but don't block
      // on it — the local membership may lag behind the server.
      Room? room = client.getRoomById(jr.roomId);
      if (room == null) {
        try {
          await client.onSync.stream
              .firstWhere((_) => client.getRoomById(jr.roomId) != null)
              .timeout(const Duration(seconds: 4));
        } catch (_) {}
        room = client.getRoomById(jr.roomId);
      }
      if (!mounted) return;
      setState(() => _joining.remove(jr.roomId));
      if (jr.isVoice) {
        // LiveKit only needs the room id + a display name, so connect even if the
        // matrix room object hasn't synced locally yet.
        final call = ref.read(callStateProvider);
        if (call.isActive) await call.hangUp();
        ref.read(activeVoiceRoomIdProvider.notifier).state = jr.roomId;
        await call.startCall(
          roomName: jr.roomId,
          roomDisplayName: room?.getLocalizedDisplayname() ?? jr.name,
          identity: client.userID ?? '',
          audioOnly: true,
          voiceChannel: true,
          matrixClient: client,
          matrixRoomId: jr.roomId,
        );
      } else if (room != null) {
        ref.read(activeRoomIdProvider.notifier).state = room.id;
        ref.read(viewModeProvider.notifier).state = ViewMode.chat;
      }
    } catch (e) {
      if (mounted) {
        setState(() => _joining.remove(jr.roomId));
        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
          content:
              Text('Beitritt fehlgeschlagen: ${e.toString().split('\n').first}'),
          backgroundColor: widget.pt.danger,
        ));
      }
    }
  }

  Future<void> _applyReorder(
      Room space, String categoryKey, List<SpaceChild> newOrder) async {
    try {
      for (var i = 0; i < newOrder.length; i++) {
        final child = newOrder[i];
        if (child.roomId == null) continue;
        final newOrd = (i + 1).toString().padLeft(4, '0');
        if (child.order != newOrd) {
          await space.setSpaceChild(child.roomId!,
              via: child.via.isEmpty ? null : child.via, order: newOrd);
        }
      }
    } catch (_) {
    }
  }

  @override
  Widget build(BuildContext context) {
    // Rebuild when rooms list changes (sync)
    ref.watch(roomListProvider);
    final client = ref.watch(matrixClientProvider).valueOrNull;
    final activeRoomId = ref.watch(activeRoomIdProvider);

    if (client == null) {
      return Center(
          child: SizedBox(
              width: 20,
              height: 20,
              child: CircularProgressIndicator(
                  strokeWidth: 2, color: widget.pt.accent)));
    }

    final space = client.getRoomById(widget.spaceId);
    if (space == null) {
      return Center(
          child: Text('Space nicht gefunden',
              style: TextStyle(color: widget.pt.fgDim, fontSize: 12)));
    }

    final isAdmin = space.ownPowerLevel >= 50;
    final allChildren = space.spaceChildren;

    // Separate sub-spaces (categories) from direct rooms
    final subSpaceEntries = <SpaceChild>[];
    final directEntries = <SpaceChild>[];
    for (final c in allChildren) {
      if (c.roomId == null) continue;
      final room = client.getRoomById(c.roomId!);
      if (room == null || room.membership == Membership.leave) continue;
      if (room.isSpace) {
        subSpaceEntries.add(c);
      } else {
        directEntries.add(c);
      }
    }
    subSpaceEntries.sort(_compareSpaceChildren);
    directEntries.sort(_compareSpaceChildren);

    // Collect invited rooms
    final invites = allChildren
        .where((c) => c.roomId != null)
        .map((c) => client.getRoomById(c.roomId!))
        .whereType<Room>()
        .where((r) => r.membership == Membership.invite)
        .toList();

    // IDs already rendered in the joined/invited sections — used to avoid showing
    // them again under "available channels" from the hierarchy.
    final shownIds = <String>{...invites.map((r) => r.id)};

    void selectRoom(Room room) {
      ref.read(activeRoomIdProvider.notifier).state = room.id;
      ref.read(viewModeProvider.notifier).state = ViewMode.chat;
    }

    void joinVoiceChannel(Room room) async {
      final call = ref.read(callStateProvider);
      final activeVoice = ref.read(activeVoiceRoomIdProvider);
      // Toggle: leave if already in this channel
      if (call.isVoiceChannel && activeVoice == room.id) {
        ref.read(activeVoiceRoomIdProvider.notifier).state = null;
        await call.hangUp();
        return;
      }
      if (call.isActive) await call.hangUp();
      ref.read(activeVoiceRoomIdProvider.notifier).state = room.id;
      final userId = client.userID ?? '';
      await call.startCall(
        roomName: room.id,
        roomDisplayName: room.getLocalizedDisplayname(),
        identity: userId,
        audioOnly: true,
        voiceChannel: true,
        matrixClient: client,
        matrixRoomId: room.id,
      );
    }

    // Build category sections for sub-spaces
    List<Widget> categoryWidgets = [];
    for (final catEntry in subSpaceEntries) {
      final subSpace = client.getRoomById(catEntry.roomId!)!;
      final catId = subSpace.id;
      final catName = subSpace.getLocalizedDisplayname();
      final isCollapsed = widget.collapsed[catId] ?? false;

      final catChildren = subSpace.spaceChildren
          .where((c) => c.roomId != null)
          .map((c) => client.getRoomById(c.roomId!))
          .whereType<Room>()
          .where((r) =>
              r.membership != Membership.leave && !r.isSpace)
          .toList();
      shownIds.addAll(catChildren.map((r) => r.id));

      final filtered = catChildren
          .where((r) => widget.filter.isEmpty ||
              r.getLocalizedDisplayname()
                  .toLowerCase()
                  .contains(widget.filter.toLowerCase()))
          .toList();

      categoryWidgets.add(_CategoryHeader(
        name: catName.toUpperCase(),
        isCollapsed: isCollapsed,
        pt: widget.pt,
        onTap: () => widget.onToggleSection(catId),
      ));
      if (!isCollapsed) {
        for (final room in filtered) {
          final isVoice = _isVoiceRoom(room);
          categoryWidgets.add(_RoomItem(
            key: ValueKey('cat_${room.id}'),
            room: room,
            isActive: isVoice ? false : activeRoomId == room.id,
            isInVoice: isVoice && ref.read(activeVoiceRoomIdProvider) == room.id,
            pt: widget.pt,
            onTap: () => isVoice ? joinVoiceChannel(room) : selectRoom(room),
          ));
        }
      }
    }

    // Build direct rooms section (reorderable for admin)
    final directRooms = directEntries
        .map((c) => client.getRoomById(c.roomId!))
        .whereType<Room>()
        .toList();
    shownIds.addAll(directRooms.map((r) => r.id));

    // Apply local order override if present
    final localOrd = _localOrder['__direct__'];
    if (localOrd != null) {
      directRooms.sort((a, b) {
        final ai = localOrd.indexOf(a.id);
        final bi = localOrd.indexOf(b.id);
        if (ai == -1 && bi == -1) return 0;
        if (ai == -1) return 1;
        if (bi == -1) return -1;
        return ai.compareTo(bi);
      });
    }

    final filteredDirect = directRooms
        .where((r) => widget.filter.isEmpty ||
            r.getLocalizedDisplayname()
                .toLowerCase()
                .contains(widget.filter.toLowerCase()))
        .toList();

    // Joinable channels from the hierarchy that aren't already listed above.
    final visibleJoinable = _joinable
        .where((jr) =>
            !shownIds.contains(jr.roomId) &&
            (widget.filter.isEmpty ||
                jr.name.toLowerCase().contains(widget.filter.toLowerCase())))
        .toList();

    // Build the complete list
    return ListView(
      padding: const EdgeInsets.fromLTRB(8, 4, 8, 12),
      children: [
        // Invite cards
        if (invites.isNotEmpty) ...[
          Padding(
            padding: const EdgeInsets.fromLTRB(8, 6, 8, 2),
            child: Text('EINLADUNGEN',
                style: TextStyle(
                    color: widget.pt.accent,
                    fontSize: 10,
                    fontWeight: FontWeight.w700,
                    letterSpacing: 0.6)),
          ),
          ...invites.map((r) => Padding(
              padding: const EdgeInsets.symmetric(vertical: 2),
              child: _InviteItem(room: r, pt: widget.pt))),
          const SizedBox(height: 4),
        ],
        // Sub-space categories
        ...categoryWidgets,
        // Direct rooms
        if (filteredDirect.isNotEmpty) ...[
          if (subSpaceEntries.isNotEmpty)
            _CategoryHeader(
              name: 'KANÄLE',
              isCollapsed: widget.collapsed['__direct__'] ?? false,
              pt: widget.pt,
              onTap: () => widget.onToggleSection('__direct__'),
            ),
          if (!(widget.collapsed['__direct__'] ?? false))
            if (isAdmin)
              // Reorderable list for admins
              ReorderableListView(
                shrinkWrap: true,
                physics: const NeverScrollableScrollPhysics(),
                buildDefaultDragHandles: false,
                onReorderItem: (oldIdx, newIdx) {
                  final ids = filteredDirect.map((r) => r.id).toList();
                  final moved = ids.removeAt(oldIdx);
                  ids.insert(newIdx, moved);
                  setState(() => _localOrder['__direct__'] = ids);
                  // Apply to Matrix
                  final newOrder = ids
                      .map((id) =>
                          directEntries.firstWhere((c) => c.roomId == id,
                              orElse: () => directEntries.first))
                      .toList();
                  _applyReorder(space, '__direct__', newOrder);
                },
                children: [
                  for (var i = 0; i < filteredDirect.length; i++)
                    _DraggableRoomItem(
                      key: ValueKey(filteredDirect[i].id),
                      room: filteredDirect[i],
                      index: i,
                      isActive: _isVoiceRoom(filteredDirect[i]) ? false : activeRoomId == filteredDirect[i].id,
                      isInVoice: _isVoiceRoom(filteredDirect[i]) && ref.read(activeVoiceRoomIdProvider) == filteredDirect[i].id,
                      pt: widget.pt,
                      onTap: () => _isVoiceRoom(filteredDirect[i]) ? joinVoiceChannel(filteredDirect[i]) : selectRoom(filteredDirect[i]),
                    ),
                ],
              )
            else
              ...filteredDirect.map((r) {
                final isVoice = _isVoiceRoom(r);
                return _RoomItem(
                    key: ValueKey(r.id),
                    room: r,
                    isActive: isVoice ? false : activeRoomId == r.id,
                    isInVoice: isVoice && ref.read(activeVoiceRoomIdProvider) == r.id,
                    pt: widget.pt,
                    onTap: () => isVoice ? joinVoiceChannel(r) : selectRoom(r),
                  );
              }),
        ],
        // Joinable channels from the space hierarchy (not yet a member)
        if (visibleJoinable.isNotEmpty) ...[
          Padding(
            padding: const EdgeInsets.fromLTRB(8, 12, 8, 2),
            child: Text('VERFÜGBARE KANÄLE',
                style: TextStyle(
                    color: widget.pt.fgDim,
                    fontSize: 10,
                    fontWeight: FontWeight.w700,
                    letterSpacing: 0.6)),
          ),
          ...visibleJoinable.map((jr) => _JoinableRoomItem(
                jr: jr,
                joining: _joining.contains(jr.roomId),
                pt: widget.pt,
                onJoin: () => _joinAndEnter(client, jr),
              )),
        ],
        if (filteredDirect.isEmpty &&
            categoryWidgets.isEmpty &&
            invites.isEmpty &&
            visibleJoinable.isEmpty)
          Padding(
            padding: const EdgeInsets.all(16),
            child: Center(
                child: Text('Keine Kanäle',
                    style:
                        TextStyle(color: widget.pt.fgDim, fontSize: 13))),
          ),
      ],
    );
  }
}

class _JoinableRoom {
  final String roomId;
  final String name;
  final bool isVoice;
  final int memberCount;
  const _JoinableRoom({
    required this.roomId,
    required this.name,
    required this.isVoice,
    required this.memberCount,
  });
}

class _JoinableRoomItem extends StatefulWidget {
  final _JoinableRoom jr;
  final bool joining;
  final PyramidTheme pt;
  final VoidCallback onJoin;
  const _JoinableRoomItem({
    required this.jr,
    required this.joining,
    required this.pt,
    required this.onJoin,
  });

  @override
  State<_JoinableRoomItem> createState() => _JoinableRoomItemState();
}

class _JoinableRoomItemState extends State<_JoinableRoomItem> {
  bool _hovered = false;

  @override
  Widget build(BuildContext context) {
    final pt = widget.pt;
    final jr = widget.jr;
    return MouseRegion(
      cursor: SystemMouseCursors.click,
      onEnter: (_) => setState(() => _hovered = true),
      onExit: (_) => setState(() => _hovered = false),
      child: GestureDetector(
        onTap: widget.joining ? null : widget.onJoin,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 150),
          margin: const EdgeInsets.symmetric(vertical: 1),
          padding: EdgeInsets.symmetric(
              horizontal: pt.rowPadX, vertical: pt.rowPadY),
          decoration: BoxDecoration(
            color: _hovered ? pt.bgHover : Colors.transparent,
            borderRadius: BorderRadius.circular(pt.rSm),
          ),
          child: Row(
            children: [
              SizedBox(
                width: 16,
                height: 16,
                child: Icon(
                  jr.isVoice ? Icons.mic_rounded : Icons.tag_rounded,
                  size: 14,
                  color: pt.fgDim,
                ),
              ),
              const SizedBox(width: 8),
              Expanded(
                child: Text(
                  jr.name,
                  style: TextStyle(
                      color: pt.fgMuted,
                      fontSize: 14,
                      fontWeight: FontWeight.w500),
                  overflow: TextOverflow.ellipsis,
                ),
              ),
              if (widget.joining)
                const SizedBox(
                  width: 14,
                  height: 14,
                  child: CircularProgressIndicator.adaptive(strokeWidth: 2),
                )
              else
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
                  decoration: BoxDecoration(
                    color: _hovered ? pt.accent : pt.bg3,
                    borderRadius: BorderRadius.circular(6),
                  ),
                  child: Text(
                    'Beitreten',
                    style: TextStyle(
                        color: _hovered ? pt.accentFg : pt.fgMuted,
                        fontSize: 11,
                        fontWeight: FontWeight.w600),
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
}

class _CategoryHeader extends StatefulWidget {
  final String name;
  final bool isCollapsed;
  final PyramidTheme pt;
  final VoidCallback onTap;
  const _CategoryHeader(
      {required this.name,
      required this.isCollapsed,
      required this.pt,
      required this.onTap});

  @override
  State<_CategoryHeader> createState() => _CategoryHeaderState();
}

class _CategoryHeaderState extends State<_CategoryHeader> {
  bool _hovered = false;

  @override
  Widget build(BuildContext context) {
    final pt = widget.pt;
    return MouseRegion(
      cursor: SystemMouseCursors.click,
      onEnter: (_) => setState(() => _hovered = true),
      onExit: (_) => setState(() => _hovered = false),
      child: GestureDetector(
        onTap: widget.onTap,
        child: Padding(
          padding: const EdgeInsets.fromLTRB(8, 10, 8, 2),
          child: Row(children: [
            AnimatedRotation(
              turns: widget.isCollapsed ? -0.25 : 0,
              duration: const Duration(milliseconds: 180),
              child: Icon(Icons.keyboard_arrow_down_rounded,
                  size: 12,
                  color: _hovered ? pt.fgMuted : pt.fgDim),
            ),
            const SizedBox(width: 4),
            Text(
              widget.name,
              style: TextStyle(
                color: _hovered ? pt.fgMuted : pt.fgDim,
                fontSize: 11,
                fontWeight: FontWeight.w600,
                letterSpacing: 0.5,
              ),
            ),
          ]),
        ),
      ),
    );
  }
}
