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/errors/error_mapper.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 '../../../routing/app_routes.dart';
import '../../../share_intent/pending_share.dart';
import '../../../share_intent/remote_file_ref.dart';
import '../../../share_intent/share_intent_listener.dart';
import '../../../state/app/infrastructure/loadable_state/view/loadable_state_consumer.dart';
import '../../../state/app/modules/chat_list/bloc/chat_list_bloc.dart';
import '../../../state/app/modules/chat_list/bloc/chat_list_state.dart';
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../widget/app_progress_indicator.dart';
import '../../../widget/info_dialog.dart';
import '../../../widget/placeholder_view.dart';
import '../files/files_upload_dialog.dart';
import '../talk/search_chat.dart';
import '../talk/widgets/chat_tile.dart';

typedef _ChatPickedCallback =
    Future<void> Function(BuildContext context, GetRoomResponseObject room);

class ShareChatPicker extends StatelessWidget {
  final _ChatPickedCallback _onPicked;

  const ShareChatPicker._({required _ChatPickedCallback onPicked})
    : _onPicked = onPicked;

  /// External share-intent flow: uploads local files into the Talk share
  /// folder, then shares them in the chosen chat. Falls back to a draft-only
  /// flow when the pending share contains no files.
  factory ShareChatPicker.forExternalShare({required PendingShare share}) =>
      ShareChatPicker._(
        onPicked: (ctx, room) => _externalShareFlow(ctx, room, share),
      );

  /// In-app share flow: links an already-uploaded server file into the chosen
  /// chat via FileSharingApi (no upload needed).
  factory ShareChatPicker.forInternalShare({required RemoteFileRef file}) =>
      ShareChatPicker._(
        onPicked: (ctx, room) => _internalShareFlow(ctx, room, file),
      );

  /// Forward an existing Talk message (text and/or already-uploaded file
  /// attachment) into another chat. The attachment is re-shared via the same
  /// FileSharingApi path used for [forInternalShare]; plain text is posted
  /// with [SendMessage].
  factory ShareChatPicker.forMessageForward({
    String? text,
    RemoteFileRef? file,
  }) {
    assert(
      text != null || file != null,
      'forMessageForward requires either text or file',
    );
    return ShareChatPicker._(
      onPicked: (ctx, room) => _forwardMessageFlow(ctx, room, text, file),
    );
  }

  @override
  Widget build(BuildContext context) {
    final talkSettings = context.watch<SettingsCubit>().val().talkSettings;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Talk-Chat auswählen'),
        actions: [
          Builder(
            builder: (ctx) => IconButton(
              tooltip: 'Suchen',
              icon: const Icon(Icons.search),
              onPressed: () {
                final rooms = ctx.read<ChatListBloc>().state.data?.rooms;
                if (rooms == null) return;
                showSearch(
                  context: ctx,
                  delegate: SearchChat(
                    rooms.data.where((r) => r.readOnly == 0).toList(),
                    onTapOverride: (room) {
                      Navigator.of(ctx).pop();
                      _onPicked(ctx, room);
                    },
                  ),
                );
              },
            ),
          ),
        ],
      ),
      body: LoadableStateConsumer<ChatListBloc, ChatListState>(
        child: (state, _) {
          final rooms = state.rooms;
          if (rooms == null) return const SizedBox.shrink();
          final sorted = rooms
              .sortBy(
                favoritesToTop: talkSettings.sortFavoritesToTop,
                unreadToTop: talkSettings.sortUnreadToTop,
              )
              // Hide chats the user can't write to (announcement channels,
              // archived rooms, …) — uploading there would only fail at the
              // share-API call with 403.
              .where((r) => r.readOnly == 0)
              .toList();
          if (sorted.isEmpty) {
            return const PlaceholderView(
              icon: Icons.chat_bubble_outline,
              text: 'Keine schreibbaren Chats verfügbar',
            );
          }
          return ListView.builder(
            padding: EdgeInsets.zero,
            itemCount: sorted.length,
            itemBuilder: (context, i) => ChatTile(
              data: sorted[i],
              disableContextActions: true,
              onTapOverride: (room) => _onPicked(context, room),
            ),
          );
        },
      ),
    );
  }
}

Future<void> _externalShareFlow(
  BuildContext context,
  GetRoomResponseObject room,
  PendingShare share,
) async {
  if (share.hasFiles) {
    try {
      final webdav = await WebdavApi.webdav;
      await webdav.mkcol(PathUri.parse('/$talkShareFolder'));
    } catch (_) {
      // mkcol throws when the folder already exists; ignore.
    }
    if (!context.mounted) return;
    await pushScreen(
      context,
      withNavBar: false,
      screen: FilesUploadDialog(
        filePaths: share.filePaths,
        remotePath: talkShareFolder,
        uniqueNames: true,
        onUploadFinished: (uploaded) =>
            _afterExternalFilesUploaded(context, room, uploaded, share),
      ),
    );
    return;
  }
  if (share.hasText) {
    _setExternalDraftAndOpenChat(context, room, share);
  }
}

Future<void> _afterExternalFilesUploaded(
  BuildContext context,
  GetRoomResponseObject room,
  List<String> uploadedRemotePaths,
  PendingShare share,
) => _runShareFlow(
  context,
  action: () =>
      shareFilesToChat(token: room.token, remoteFilePaths: uploadedRemotePaths),
  onSuccess: () => _setExternalDraftAndOpenChat(context, room, share),
);

/// Shared share-flow scaffolding: shows the blocking spinner, runs [action],
/// maps failures to an error dialog (popping the spinner first), and invokes
/// [onSuccess] on success while still mounted.
Future<void> _runShareFlow(
  BuildContext context, {
  required Future<void> Function() action,
  required VoidCallback onSuccess,
}) async {
  unawaited(_showBlockingSpinner(context));
  try {
    await action();
  } catch (e) {
    if (context.mounted) Navigator.of(context).pop();
    if (context.mounted) {
      InfoDialog.show(
        context,
        errorToUserMessage(e),
        title: 'Fehler',
        copyable: true,
      );
    }
    return;
  }
  if (!context.mounted) return;
  onSuccess();
}

void _setExternalDraftAndOpenChat(
  BuildContext context,
  GetRoomResponseObject room,
  PendingShare share,
) {
  if (share.hasText) {
    final settings = context.read<SettingsCubit>();
    settings.val(write: true).talkSettings.drafts[room.token] = share.text!;
  }
  ShareIntentListener.instance.clear(ifCurrent: share);
  _finishWithChat(context, room);
}

/// Closes any picker/spinner pages stacked on top of the current tab and
/// jumps to the chosen chat. Shared by external + internal share flows.
void _finishWithChat(BuildContext context, GetRoomResponseObject room) {
  Navigator.of(context).popUntil((route) => route.isFirst);
  AppRoutes.openChatByToken(context, room.token);
}

Future<void> _internalShareFlow(
  BuildContext context,
  GetRoomResponseObject room,
  RemoteFileRef file,
) => _runShareFlow(
  context,
  action: () =>
      shareFilesToChat(token: room.token, remoteFilePaths: [file.path]),
  onSuccess: () => _finishWithChat(context, room),
);

Future<void> _forwardMessageFlow(
  BuildContext context,
  GetRoomResponseObject room,
  String? text,
  RemoteFileRef? file,
) => _runShareFlow(
  context,
  action: () async {
    if (file != null) {
      await shareFilesToChat(token: room.token, remoteFilePaths: [file.path]);
    }
    if (text != null && text.isNotEmpty) {
      await SendMessage(room.token, SendMessageParams(text)).run();
    }
  },
  onSuccess: () => _finishWithChat(context, room),
);

/// Modal progress overlay shown during share-API roundtrips. The dialog is
/// popped together with the picker by the subsequent popUntil(isFirst).
Future<void> _showBlockingSpinner(BuildContext context) => showDialog<void>(
  context: context,
  barrierDismissible: false,
  builder: (_) => const PopScope(
    canPop: false,
    child: Center(child: AppProgressIndicator.large()),
  ),
);
