import 'package:flutter/material.dart' hide Visibility;

import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';

import 'package:fluffychat/pages/chat_access_settings/chat_access_settings_page.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';

class ChatAccessSettings extends StatefulWidget {
  final String roomId;
  const ChatAccessSettings({required this.roomId, super.key});

  @override
  State<ChatAccessSettings> createState() => ChatAccessSettingsController();
}

class ChatAccessSettingsController extends State<ChatAccessSettings> {
  bool joinRulesLoading = false;
  bool visibilityLoading = false;
  bool historyVisibilityLoading = false;
  bool guestAccessLoading = false;
  Room get room => Matrix.of(context).client.getRoomById(widget.roomId)!;

  String get roomVersion =>
      room
          .getState(EventTypes.RoomCreate)!
          .content
          .tryGet<String>('room_version') ??
      'Unknown';

  /// Calculates which join rules are available based on the information on
  /// https://spec.matrix.org/v1.11/rooms/#feature-matrix
  List<JoinRules> get availableJoinRules {
    final joinRules = Set<JoinRules>.from(JoinRules.values);

    final roomVersionInt = int.tryParse(roomVersion);

    // Knock is only supported for rooms up from version 7:
    if (roomVersionInt != null && roomVersionInt <= 6) {
      joinRules.remove(JoinRules.knock);
    }

    // Not yet supported in FluffyChat:
    joinRules.remove(JoinRules.restricted);
    joinRules.remove(JoinRules.knockRestricted);

    // If an unsupported join rule is the current join rule, display it:
    final currentJoinRule = room.joinRules;
    if (currentJoinRule != null) joinRules.add(currentJoinRule);

    return joinRules.toList();
  }

  void setJoinRule(JoinRules? newJoinRules) async {
    if (newJoinRules == null) return;
    setState(() {
      joinRulesLoading = true;
    });

    try {
      await room.setJoinRules(newJoinRules);
    } catch (e, s) {
      Logs().w('Unable to change join rules', e, s);
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(
              e.toLocalizedString(context),
            ),
          ),
        );
      }
    } finally {
      if (mounted) {
        setState(() {
          joinRulesLoading = false;
        });
      }
    }
  }

  void setHistoryVisibility(HistoryVisibility? historyVisibility) async {
    if (historyVisibility == null) return;
    setState(() {
      historyVisibilityLoading = true;
    });

    try {
      await room.setHistoryVisibility(historyVisibility);
    } catch (e, s) {
      Logs().w('Unable to change history visibility', e, s);
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(
              e.toLocalizedString(context),
            ),
          ),
        );
      }
    } finally {
      if (mounted) {
        setState(() {
          historyVisibilityLoading = false;
        });
      }
    }
  }

  void setGuestAccess(GuestAccess? guestAccess) async {
    if (guestAccess == null) return;
    setState(() {
      guestAccessLoading = true;
    });

    try {
      await room.setGuestAccess(guestAccess);
    } catch (e, s) {
      Logs().w('Unable to change guest access', e, s);
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(
              e.toLocalizedString(context),
            ),
          ),
        );
      }
    } finally {
      if (mounted) {
        setState(() {
          guestAccessLoading = false;
        });
      }
    }
  }

  void updateRoomAction() async {
    final roomVersion = room
        .getState(EventTypes.RoomCreate)!
        .content
        .tryGet<String>('room_version');
    final capabilitiesResult = await showFutureLoadingDialog(
      context: context,
      future: () => room.client.getCapabilities(),
    );
    final capabilities = capabilitiesResult.result;
    if (capabilities == null) return;
    final newVersion = await showModalActionPopup<String>(
      context: context,
      title: L10n.of(context).replaceRoomWithNewerVersion,
      cancelLabel: L10n.of(context).cancel,
      actions: capabilities.mRoomVersions!.available.entries
          .where((r) => r.key != roomVersion)
          .map(
            (version) => AdaptiveModalAction(
              value: version.key,
              label:
                  '${version.key} (${version.value.toString().split('.').last})',
            ),
          )
          .toList(),
    );
    if (newVersion == null ||
        OkCancelResult.cancel ==
            await showOkCancelAlertDialog(
              useRootNavigator: false,
              context: context,
              okLabel: L10n.of(context).yes,
              cancelLabel: L10n.of(context).cancel,
              title: L10n.of(context).areYouSure,
              message: L10n.of(context).roomUpgradeDescription,
              isDestructive: true,
            )) {
      return;
    }
    final result = await showFutureLoadingDialog(
      context: context,
      future: () => room.client.upgradeRoom(room.id, newVersion),
    );
    if (result.error != null) return;
    if (!mounted) return;
    context.go('/rooms/${room.id}');
  }

  Future<void> addAlias() async {
    final domain = room.client.userID?.domain;
    if (domain == null) {
      throw Exception('userID or domain is null! This should never happen.');
    }

    final input = await showTextInputDialog(
      context: context,
      title: L10n.of(context).editRoomAliases,
      prefixText: '#',
      suffixText: domain,
      hintText: L10n.of(context).alias,
    );
    final aliasLocalpart = input?.trim();
    if (aliasLocalpart == null || aliasLocalpart.isEmpty) return;
    final alias = '#$aliasLocalpart:$domain';

    final result = await showFutureLoadingDialog(
      context: context,
      future: () => room.client.setRoomAlias(alias, room.id),
    );
    if (result.error != null) return;
    setState(() {});

    if (!room.canChangeStateEvent(EventTypes.RoomCanonicalAlias)) return;

    final canonicalAliasConsent = await showOkCancelAlertDialog(
      context: context,
      title: L10n.of(context).setAsCanonicalAlias,
      message: alias,
      okLabel: L10n.of(context).yes,
      cancelLabel: L10n.of(context).no,
    );

    final altAliases = room
            .getState(EventTypes.RoomCanonicalAlias)
            ?.content
            .tryGetList<String>('alt_aliases')
            ?.toSet() ??
        {};
    if (room.canonicalAlias.isNotEmpty) altAliases.add(room.canonicalAlias);
    altAliases.add(alias);
    if (canonicalAliasConsent == OkCancelResult.ok) {
      altAliases.remove(alias);
    } else {
      altAliases.remove(room.canonicalAlias);
    }

    await showFutureLoadingDialog(
      context: context,
      future: () => room.client.setRoomStateWithKey(
        room.id,
        EventTypes.RoomCanonicalAlias,
        '',
        {
          'alias': canonicalAliasConsent == OkCancelResult.ok
              ? alias
              : room.canonicalAlias,
          if (altAliases.isNotEmpty) 'alt_aliases': altAliases.toList(),
        },
      ),
    );
  }

  void deleteAlias(String alias) async {
    await showFutureLoadingDialog(
      context: context,
      future: () => room.client.deleteRoomAlias(alias),
    );
    setState(() {});
  }

  void setChatVisibilityOnDirectory(bool? visibility) async {
    if (visibility == null) return;
    setState(() {
      visibilityLoading = true;
    });

    try {
      await room.client.setRoomVisibilityOnDirectory(
        room.id,
        visibility: visibility == true ? Visibility.public : Visibility.private,
      );
      setState(() {});
    } catch (e, s) {
      Logs().w('Unable to change visibility', e, s);
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(
              e.toLocalizedString(context),
            ),
          ),
        );
      }
    } finally {
      if (mounted) {
        setState(() {
          visibilityLoading = false;
        });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return ChatAccessSettingsPageView(this);
  }
}
