import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';

import '../../../../api/errors/error_mapper.dart';
import '../../../../api/marianumconnect/queries/get_ticker_page/get_ticker_page_response.dart';
import '../../../../routing/app_routes.dart';
import '../../../../state/app/infrastructure/loadable_state/loadable_state.dart';
import '../../../../state/app/infrastructure/loadable_state/view/loadable_state_consumer.dart';
import '../../../../state/app/infrastructure/utility_widgets/bloc_module.dart';
import '../../../../state/app/modules/ticker/bloc/ticker_page_bloc.dart';
import '../../../../state/app/modules/ticker/bloc/ticker_page_state.dart';
import '../../../../state/app/modules/ticker/repository/ticker_page_repository.dart';
import '../../../../theming/app_theme.dart';
import '../../../../widget/app_progress_indicator.dart';
import '../../../../widget/placeholder_view.dart';
import '../../../../widget/prosemirror/pm_json_view.dart';
import '../../../../widget/route_transition_gate.dart';
import 'ticker_content_card.dart';
import 'ticker_updated_bar.dart';

/// Embeddable renderer for a single ticker page: drives a per-slug
/// [TickerPageBloc] through [LoadableStateConsumer], so pages behave like the
/// home surface (cache, background refresh, offline banner, pull-to-refresh).
/// Carries no Scaffold so it works both in-place in [TickerView] and in the
/// standalone `TickerPageView`. A REDIRECT opens the browser and invokes
/// [onRedirect] so the host can leave this page.
class TickerPageBody extends StatelessWidget {
  final String slug;
  final void Function(String href) onLinkTap;
  final VoidCallback? onRedirect;

  const TickerPageBody({
    super.key,
    required this.slug,
    required this.onLinkTap,
    this.onRedirect,
  });

  @override
  Widget build(BuildContext context) =>
      BlocModule<TickerPageBloc, LoadableState<TickerPageState>>(
        // A slug switch must rebuild the provider with a fresh bloc.
        key: ValueKey(slug),
        create: (context) => TickerPageBloc(slug),
        child: (context, bloc, _) =>
            LoadableStateConsumer<TickerPageBloc, TickerPageState>(
              isReady: (state) => state.page != null,
              child: (state, loading) => _TickerPageContent(
                page: state.page!,
                onLinkTap: onLinkTap,
                onRedirect: onRedirect,
              ),
            ),
      );
}

/// Renders a resolved [TickerPageResponse] by kind. Stateful so a REDIRECT
/// fires only once across the consumer's background-refresh rebuilds.
class _TickerPageContent extends StatefulWidget {
  final TickerPageResponse page;
  final void Function(String href) onLinkTap;
  final VoidCallback? onRedirect;

  const _TickerPageContent({
    required this.page,
    required this.onLinkTap,
    this.onRedirect,
  });

  @override
  State<_TickerPageContent> createState() => _TickerPageContentState();
}

class _TickerPageContentState extends State<_TickerPageContent> {
  bool _redirected = false;

  @override
  Widget build(BuildContext context) {
    final page = widget.page;
    switch (page.kind) {
      case TickerPageKind.redirect:
        final url = page.externalUrl;
        if (!_redirected) {
          _redirected = true;
          WidgetsBinding.instance.addPostFrameCallback((_) {
            if (!mounted) return;
            if (url != null && url.isNotEmpty) AppRoutes.openExternalUrl(url);
            widget.onRedirect?.call();
          });
        }
        return const Center(child: AppProgressIndicator.large());
      case TickerPageKind.proxiedFile:
        return Column(
          children: [
            // For proxied files the data currency is the last successful
            // proxy fetch, not the page's publish date.
            TickerUpdatedBar(publishedAt: page.fileFetchedAt ?? page.publishedAt),
            Expanded(
              child: _ProxiedFileView(
                repo: context.read<TickerPageBloc>().repo,
                slug: page.slug ?? context.read<TickerPageBloc>().slug,
              ),
            ),
          ],
        );
      default:
        final content = page.content;
        if (content == null) {
          return PlaceholderView(
            icon: Icons.public_off_outlined,
            text: 'Dieser Inhalt ist in der App nicht verfügbar.',
            button: page.webUrl == null
                ? null
                : ElevatedButton.icon(
                    onPressed: () => AppRoutes.openWebUrl(page.webUrl!),
                    icon: const Icon(Icons.open_in_new),
                    label: const Text('Im Browser öffnen'),
                  ),
          );
        }
        return SingleChildScrollView(
          // Pull-to-refresh must trigger even when content fits the viewport.
          physics: const AlwaysScrollableScrollPhysics(),
          padding: const EdgeInsets.only(bottom: AppSpacing.lg),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              TickerUpdatedBar(publishedAt: page.publishedAt),
              TickerContentCard(
                child: PmJsonView(json: content, onLinkTap: widget.onLinkTap),
              ),
            ],
          ),
        );
    }
  }
}

class _ProxiedFileView extends StatefulWidget {
  final TickerPageRepository repo;
  final String slug;

  const _ProxiedFileView({required this.repo, required this.slug});

  @override
  State<_ProxiedFileView> createState() => _ProxiedFileViewState();
}

class _ProxiedFileViewState extends State<_ProxiedFileView> {
  late Future<Uint8List> _bytes;

  @override
  void initState() {
    super.initState();
    _bytes = widget.repo.getPageFile(widget.slug);
  }

  @override
  Widget build(BuildContext context) => FutureBuilder<Uint8List>(
    future: _bytes,
    builder: (context, snapshot) {
      if (snapshot.connectionState != ConnectionState.done) {
        return const Center(child: AppProgressIndicator.large());
      }
      final error = snapshot.error;
      if (error != null) {
        return PlaceholderView(
          icon: Icons.error_outline,
          text: errorToUserMessage(error),
          button: errorAllowsRetry(error)
              ? ElevatedButton.icon(
                  onPressed: () => setState(() {
                    _bytes = widget.repo.getPageFile(widget.slug);
                  }),
                  icon: const Icon(Icons.refresh),
                  label: const Text('Erneut versuchen'),
                )
              : null,
        );
      }
      final bytes = snapshot.data!;
      if (bytes.isEmpty) {
        return const PlaceholderView(
          icon: Icons.picture_as_pdf_outlined,
          text: 'Das Dokument konnte nicht geladen werden.',
        );
      }
      return RouteTransitionGate(
        builder: (context) => SfPdfViewer.memory(bytes),
      );
    },
  );
}
