import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';

import '../../../../api/marianumcloud/talk/room/get_room_response.dart';
import '../../../../api/marianumcloud/talk/send_message/send_message.dart';
import '../../../../api/marianumcloud/talk/send_message/send_message_params.dart';
import '../../../../api/marianumcloud/talk/share_files_to_chat.dart';
import '../../../../api/marianumcloud/webdav/webdav_api.dart';
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
import '../../../../state/app/modules/chat/bloc/chat_state.dart';
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../../widget/async_action_button.dart';
import '../../../../widget/demo_restricted.dart';
import '../../../../widget/details_bottom_sheet.dart';
import '../../../../widget/downloads/download_tray.dart';
import '../../../../widget/emoji_picker_dialog.dart';
import '../../../../widget/file_pick.dart';
import '../../../../widget/focus_behaviour.dart';
import '../../files/files_upload_dialog.dart';
import 'answer_reference.dart';
import 'poll_create_sheet.dart';

class ChatTextfield extends StatefulWidget {
  final String sendToToken;
  final String? selfId;
  final GetRoomResponseObjectConversationType? roomType;

  const ChatTextfield(
    this.sendToToken, {
    this.selfId,
    this.roomType,
    super.key,
  });

  @override
  State<ChatTextfield> createState() => _ChatTextfieldState();
}

class _ChatTextfieldState extends State<ChatTextfield> {
  late SettingsCubit settings;
  final TextEditingController _textBoxController = TextEditingController();
  final AsyncActionController _sendController = AsyncActionController();
  final FocusNode _focusNode = FocusNode();
  final GlobalKey _sizeKey = GlobalKey();
  String? _sendError;

  /// Publishes the input bar's measured height so the downloads chip floats
  /// above it instead of covering it.
  void _publishHeight() {
    if (!mounted) return;
    final height = _sizeKey.currentContext?.size?.height;
    if (height != null) downloadChipBottomObstruction.value = height;
  }

  void share(List<String> uploadedRemotePaths) {
    shareFilesToChat(
      token: widget.sendToToken,
      remoteFilePaths: uploadedRemotePaths,
    ).then((_) {
      if (mounted) context.read<ChatBloc>().refresh();
    });
  }

  Future<void> mediaUpload(List<String>? paths) async {
    if (paths == null) return;

    unawaited(
      WebdavApi.webdav.then(
        (webdav) => webdav.mkcol(PathUri.parse('/$talkShareFolder')),
      ),
    );

    if (!mounted) return;
    unawaited(
      pushScreen(
        context,
        withNavBar: false,
        screen: FilesUploadDialog(
          filePaths: paths,
          remotePath: talkShareFolder,
          onUploadFinished: share,
          uniqueNames: true,
        ),
      ),
    );
  }

  void _setDraft(String text) {
    final talkSettings = settings.val(write: true).talkSettings;
    if (text.isNotEmpty) {
      talkSettings.drafts[widget.sendToToken] = text;
    } else {
      talkSettings.drafts.removeWhere((key, _) => key == widget.sendToToken);
    }
  }

  void _setDraftReply(int? messageId) {
    final talkSettings = settings.val(write: true).talkSettings;
    if (messageId != null) {
      talkSettings.draftReplies[widget.sendToToken] = messageId;
    } else {
      talkSettings.draftReplies.removeWhere(
        (key, _) => key == widget.sendToToken,
      );
    }
  }

  @override
  void initState() {
    super.initState();
    settings = context.read<SettingsCubit>();
    final draftReply = settings
        .val()
        .talkSettings
        .draftReplies[widget.sendToToken];
    if (draftReply != null) {
      context.read<ChatBloc>().setReferenceMessageId(draftReply);
    }
  }

  @override
  void dispose() {
    // Defer to end-of-frame: resetting the shared notifier synchronously during
    // teardown fires setState while the tree is locked ("widget tree was locked").
    WidgetsBinding.instance.addPostFrameCallback(
      (_) => downloadChipBottomObstruction.value = 0,
    );
    _sendController.dispose();
    _focusNode.dispose();
    super.dispose();
  }

  void _showAttachmentSheet() {
    if (guardDemoAction(context)) return;
    showDetailsBottomSheet(
      context,
      children: (sheetCtx) => [
        ListTile(
          leading: const Icon(Icons.file_open),
          title: const Text('Aus Dateien auswählen'),
          onTap: () {
            FilePick.documentPick().then(mediaUpload);
            Navigator.of(sheetCtx).pop();
          },
        ),
        ListTile(
          leading: const Icon(Icons.image),
          title: const Text('Aus Galerie auswählen'),
          onTap: () {
            FilePick.multipleGalleryPick().then((value) {
              if (value != null) {
                mediaUpload(value.map((e) => e.path).toList());
              }
            });
            Navigator.of(sheetCtx).pop();
          },
        ),
        ListTile(
          leading: const Icon(Icons.camera_alt_outlined),
          title: const Text('Foto aufnehmen'),
          onTap: () {
            FilePick.cameraPick().then((image) {
              if (image != null) mediaUpload([image.path]);
            });
            Navigator.of(sheetCtx).pop();
          },
        ),
        if (_pollsAllowed)
          ListTile(
            leading: const Icon(Icons.poll_outlined),
            title: const Text('Umfrage erstellen'),
            onTap: () {
              Navigator.of(sheetCtx).pop();
              showPollCreateSheet(
                context,
                token: widget.sendToToken,
                onCreated: () {
                  if (mounted) context.read<ChatBloc>().refresh();
                },
              );
            },
          ),
      ],
    );
  }

  /// Polls can only be created in group/public conversations — the server
  /// rejects them in one-to-one chats.
  bool get _pollsAllowed =>
      widget.roomType == GetRoomResponseObjectConversationType.group ||
      widget.roomType == GetRoomResponseObjectConversationType.public;

  Future<void> _pickEmoji() async {
    final emoji = await showEmojiPicker(context);
    if (emoji == null || !mounted) return;
    _insertEmoji(emoji);
    // Keep the field focused so the user can keep typing after inserting.
    _focusNode.requestFocus();
  }

  void _insertEmoji(String emoji) {
    final selection = _textBoxController.selection;
    final text = _textBoxController.text;
    // Selection is invalid (-1) until the field was focused once — append then.
    final start = selection.start < 0 ? text.length : selection.start;
    final end = selection.end < 0 ? text.length : selection.end;
    final newText = text.replaceRange(start, end, emoji);
    _textBoxController.value = TextEditingValue(
      text: newText,
      selection: TextSelection.collapsed(offset: start + emoji.length),
    );
    // Programmatic edits skip the TextField's onChanged, so persist manually.
    _setDraft(newText);
  }

  /// Reply target for the next send, or null when there is none. Requires the
  /// referenced message to sit in this room's loaded history — the same
  /// criterion the reply banner renders on, so what the user sees is what gets
  /// sent. Talk rejects the whole message when the parent id is foreign to the
  /// room, so a leftover reference would otherwise block the chat entirely.
  String? _replyTargetFor(ChatState? chatState) {
    final referenceId = chatState?.referenceMessageId;
    if (referenceId == null) return null;
    if (chatState!.currentToken != widget.sendToToken) return null;
    final messages = chatState.chatResponse?.data;
    if (messages == null || !messages.any((e) => e.id == referenceId)) {
      return null;
    }
    return referenceId.toString();
  }

  Future<void> _sendMessage(ChatBloc chatBloc) async {
    if (_textBoxController.text.isEmpty) return;
    if (guardDemoAction(context)) return;
    final text = _textBoxController.text;
    final replyTo = _replyTargetFor(chatBloc.state.data);
    final ownToken = widget.sendToToken;
    setState(() => _sendError = null);
    await SendMessage(
      ownToken,
      SendMessageParams(text, replyTo: replyTo),
    ).run();
    // Reached only on success — SendMessage.run() throws on failure and
    // skips this block, leaving the persisted draft as a recovery aid.
    // Drafts live on the global SettingsCubit keyed by token, so clear
    // them even when the user navigated away mid-send — otherwise the
    // sent message lingers as a phantom draft on the chat list.
    _setDraft('');
    _setDraftReply(null);
    // The global ChatBloc may already point at a different chat (user
    // switched mid-send); only touch it while it still references us.
    if (chatBloc.state.data?.currentToken == ownToken) {
      chatBloc.setReferenceMessageId(null);
      chatBloc.refresh();
    }
    if (!mounted) return;
    _textBoxController.text = '';
  }

  @override
  Widget build(BuildContext context) {
    _textBoxController.text =
        settings.val().talkSettings.drafts[widget.sendToToken] ?? '';
    final chatBloc = context.watch<ChatBloc>();
    final chatState = chatBloc.state.data;

    Widget replyBanner = const SizedBox.shrink();
    if (chatState != null &&
        chatState.referenceMessageId != null &&
        chatState.chatResponse != null) {
      try {
        final referenceMessage = chatState.chatResponse!
            .sortByTimestamp()
            .firstWhere((e) => e.id == chatState.referenceMessageId);
        replyBanner = Row(
          children: [
            Expanded(
              child: AnswerReference(
                referenceMessage: referenceMessage,
                selfId: widget.selfId,
              ),
            ),
            IconButton(
              tooltip: 'Antwort verwerfen',
              onPressed: () {
                chatBloc.setReferenceMessageId(null);
                _setDraftReply(null);
              },
              icon: const Icon(Icons.close_outlined),
              padding: const EdgeInsets.only(left: 0),
            ),
          ],
        );
      } catch (_) {
        /* reference no longer in current chat data */
      }
    }

    WidgetsBinding.instance.addPostFrameCallback((_) => _publishHeight());
    return Stack(
      key: _sizeKey,
      children: <Widget>[
        Align(
          alignment: Alignment.bottomLeft,
          child: Container(
            padding: const EdgeInsets.only(
              left: 10,
              bottom: 3,
              top: 3,
              right: 10,
            ),
            width: double.infinity,
            child: Column(
              children: [
                replyBanner,
                if (_sendError != null)
                  Padding(
                    padding: const EdgeInsets.only(bottom: 4),
                    child: Text(
                      _sendError!,
                      style: TextStyle(
                        color: Theme.of(context).colorScheme.error,
                        fontSize: 12,
                      ),
                    ),
                  ),
                Row(
                  // Outer row centers the pill against the (taller) send FAB.
                  // The inner row keeps end-alignment so the icon buttons drop
                  // to the bottom once the text wraps to multiple lines.
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[
                    Expanded(
                      child: DecoratedBox(
                        decoration: BoxDecoration(
                          color: Theme.of(
                            context,
                          ).colorScheme.surfaceContainerHighest,
                          borderRadius: BorderRadius.circular(24),
                        ),
                        child: Row(
                          crossAxisAlignment: CrossAxisAlignment.end,
                          children: <Widget>[
                            IconButton(
                              visualDensity: VisualDensity.compact,
                              padding: const EdgeInsets.all(4),
                              constraints: const BoxConstraints(),
                              tooltip: 'Emoji einfügen',
                              icon: Icon(
                                Icons.emoji_emotions_outlined,
                                color: Theme.of(context).hintColor,
                              ),
                              onPressed: _pickEmoji,
                            ),
                            Expanded(
                              child: Padding(
                                // 7px keeps a single line as tall as the
                                // 32px icon buttons, so end-alignment reads as
                                // centered for one line but drops the buttons
                                // to the bottom once the text wraps.
                                padding: const EdgeInsets.symmetric(
                                  horizontal: 4,
                                  vertical: 7,
                                ),
                                child: TextField(
                                  autocorrect: true,
                                  textCapitalization:
                                      TextCapitalization.sentences,
                                  controller: _textBoxController,
                                  focusNode: _focusNode,
                                  maxLines: 7,
                                  minLines: 1,
                                  decoration: const InputDecoration(
                                    hintText: 'Nachricht schreiben...',
                                    border: InputBorder.none,
                                    isCollapsed: true,
                                  ),
                                  onChanged: (text) {
                                    if (text.trim().toLowerCase() ==
                                        'marbot marbot marbot') {
                                      const newText =
                                          'Roboter sind cool und so, aber Marbots sind besser!';
                                      _textBoxController.text = newText;
                                      text = newText;
                                    }
                                    _setDraft(text);
                                  },
                                  onTapOutside: (_) =>
                                      FocusBehaviour.textFieldTapOutside(
                                        context,
                                      ),
                                ),
                              ),
                            ),
                            IconButton(
                              visualDensity: VisualDensity.compact,
                              padding: const EdgeInsets.all(4),
                              constraints: const BoxConstraints(),
                              tooltip: 'Anhang',
                              icon: Icon(
                                Icons.attach_file_outlined,
                                color: Theme.of(context).hintColor,
                              ),
                              onPressed: _showAttachmentSheet,
                            ),
                          ],
                        ),
                      ),
                    ),
                    const SizedBox(width: 8),
                    ValueListenableBuilder<TextEditingValue>(
                      valueListenable: _textBoxController,
                      builder: (context, value, _) => AsyncFab(
                        mini: true,
                        heroTag: 'chatSend_${widget.sendToToken}',
                        icon: Icons.send,
                        backgroundColor: Theme.of(context).primaryColor,
                        foregroundColor: Colors.white,
                        controller: _sendController,
                        onPressed: value.text.trim().isEmpty
                            ? null
                            : () => _sendMessage(chatBloc),
                        onError: (message) =>
                            setState(() => _sendError = message),
                        onSuccess: () => setState(() => _sendError = null),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}
