import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:open_filex/open_filex.dart';
import 'package:photo_view/photo_view.dart';
import 'package:share_plus/share_plus.dart';

import '../routing/app_routes.dart';
import '../share_intent/remote_file_ref.dart';
import '../state/app/modules/settings/bloc/settings_cubit.dart';
import '../utils/downloads/download_manager.dart';
import 'app_progress_indicator.dart';
import 'async_action_button.dart';
import 'centered_leading.dart';
import 'confirm_dialog.dart';
import 'file_viewer/code_line.dart';
import 'file_viewer/deferred_pdf_viewer.dart';
import 'file_viewer/file_kind.dart';
import 'file_viewer/media_player.dart';
import 'file_viewer/unknown_preview_block.dart';
import 'info_dialog.dart';
import 'share_position_origin.dart';

class FileViewer extends StatefulWidget {
  final String path;
  final bool openExternal;

  /// Enables in-app "An Chat senden" / "In Dateien speichern" — these
  /// need a server-side reference instead of the local cache path.
  final RemoteFileRef? remoteFile;

  const FileViewer({
    super.key,
    required this.path,
    this.openExternal = false,
    this.remoteFile,
  });

  @override
  State<FileViewer> createState() => _FileViewerState();
}

enum FileViewingActions { openExternal, share, save, sendToChat, saveToCloud }

class _FileViewerState extends State<FileViewer> {
  final PhotoViewController photoViewController = PhotoViewController();

  late SettingsCubit settings = context.read<SettingsCubit>();
  late bool openExternal;
  Future<FileKind>? _fileKind;

  @override
  void initState() {
    super.initState();
    openExternal =
        settings.val().fileViewSettings.alwaysOpenExternally ||
        widget.openExternal;
    if (openExternal) {
      WidgetsBinding.instance.addPostFrameCallback(
        (_) => _openExternallyAndPop(),
      );
    } else {
      _fileKind = detectFileKind(widget.path);
    }
  }

  Future<void> _openExternallyAndPop() async {
    final result = await OpenFilex.open(widget.path);
    if (!mounted) return;
    Navigator.of(context).pop();
    if (result.type != ResultType.done) {
      InfoDialog.show(context, result.message);
    }
  }

  @override
  void dispose() {
    photoViewController.dispose();
    super.dispose();
  }

  /// Android may clear the cache dir behind an open viewer at any time —
  /// verify the file is still there before handing its path to an action.
  bool _ensureLocalFile() {
    if (File(widget.path).existsSync()) return true;
    final remote = widget.remoteFile;
    if (remote == null) {
      InfoDialog.show(
        context,
        'Die Datei wurde vom System aus dem Zwischenspeicher entfernt. Bitte lade sie erneut herunter.',
        title: 'Datei nicht mehr verfügbar',
      );
      return false;
    }
    ConfirmDialog(
      title: 'Datei nicht mehr verfügbar',
      content:
          'Die Datei wurde vom System aus dem Zwischenspeicher entfernt.\nErneut herunterladen?',
      confirmButton: 'Herunterladen',
      onConfirm: () {
        // Pop the viewer before starting so the fresh download auto-opens.
        Navigator.of(context).pop();
        unawaited(
          DownloadManager.instance.start(
            remotePath: remote.path,
            name: remote.name,
            remoteFile: remote,
          ),
        );
      },
    ).asDialog(context);
    return false;
  }

  Future<void> _handleAction(FileViewingActions value) async {
    switch (value) {
      case FileViewingActions.openExternal:
        if (!_ensureLocalFile()) return;
        AppRoutes.openFileViewer(
          context,
          widget.path,
          openExternal: true,
          remoteFile: widget.remoteFile,
        );
        break;
      case FileViewingActions.sendToChat:
        AppRoutes.openInternalShareToChat(context, widget.remoteFile!);
        break;
      case FileViewingActions.saveToCloud:
        AppRoutes.openInternalSaveToFolder(context, widget.remoteFile!);
        break;
      case FileViewingActions.share:
        if (!_ensureLocalFile()) return;
        unawaited(
          runWithErrorDialog(
            context,
            () => SharePlus.instance.share(
              ShareParams(
                files: [XFile(widget.path)],
                sharePositionOrigin: SharePositionOrigin.get(context),
              ),
            ),
          ),
        );
        break;
      case FileViewingActions.save:
        if (!_ensureLocalFile()) return;
        try {
          final source = File(widget.path);
          final size = await source.length();
          // file_picker has no path/stream save API, so the whole file
          // gets loaded into RAM. Cap big media; user falls back to share.
          const maxBytes = 200 * 1024 * 1024;
          if (size > maxBytes) {
            if (!mounted) return;
            InfoDialog.show(
              context,
              'Diese Datei ist zu groß (${(size / (1024 * 1024)).toStringAsFixed(0)} MB), '
              'um direkt gespeichert zu werden. Nutze stattdessen die Teilen-Funktion.',
              title: 'Speichern nicht möglich',
            );
            return;
          }
          final bytes = await source.readAsBytes();
          final saved = await FilePicker.saveFile(
            fileName: widget.path.split('/').last,
            bytes: bytes,
          );
          if (!mounted) return;
          if (saved != null) {
            InfoDialog.show(context, 'Datei gespeichert.');
          }
        } on Object catch (e) {
          if (!mounted) return;
          InfoDialog.show(
            context,
            'Speichern fehlgeschlagen: $e',
            copyable: true,
            title: 'Fehler',
          );
        }
        break;
    }
  }

  List<_ActionDescriptor> _availableActions() => [
    _ActionDescriptor(
      action: FileViewingActions.openExternal,
      icon: Platform.isIOS ? Icons.ios_share : Icons.open_in_new,
      label: Platform.isIOS ? 'Extern öffnen' : 'Öffnen mit',
    ),
    if (widget.remoteFile != null) ...[
      const _ActionDescriptor(
        action: FileViewingActions.sendToChat,
        icon: Icons.chat_bubble_outline,
        label: 'An Talk-Chat senden',
      ),
      const _ActionDescriptor(
        action: FileViewingActions.saveToCloud,
        icon: Icons.cloud_outlined,
        label: 'In Cloud speichern',
      ),
    ],
    const _ActionDescriptor(
      action: FileViewingActions.share,
      icon: Icons.share_outlined,
      label: 'Teilen',
    ),
    const _ActionDescriptor(
      action: FileViewingActions.save,
      icon: Icons.save_alt_outlined,
      label: 'Speichern',
    ),
  ];

  AppBar _appbar({
    List<Widget> actions = const [],
    bool showActionsMenu = true,
  }) => AppBar(
    title: Text(widget.path.split('/').last),
    actions: [
      ...actions,
      if (showActionsMenu)
        PopupMenuButton<FileViewingActions>(
          tooltip: 'Dateiaktionen',
          onSelected: _handleAction,
          itemBuilder: (context) => _availableActions()
              .map(
                (a) => PopupMenuItem(
                  value: a.action,
                  child: ListTile(
                    leading: Icon(a.icon),
                    title: Text(a.label),
                    dense: true,
                  ),
                ),
              )
              .toList(),
        ),
    ],
  );

  @override
  Widget build(BuildContext context) {
    if (openExternal) {
      return Scaffold(
        appBar: AppBar(title: Text(widget.path.split('/').last)),
        body: const Center(child: AppProgressIndicator.large()),
      );
    }
    return FutureBuilder<FileKind>(
      future: _fileKind,
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return Scaffold(
            appBar: _appbar(),
            body: const Center(child: AppProgressIndicator.large()),
          );
        }
        switch (snapshot.data!) {
          case FileKind.image:
            return _buildImageView();
          case FileKind.svg:
            return _buildSvgView();
          case FileKind.pdf:
            return _buildPdfView();
          case FileKind.video:
            return _buildVideoView();
          case FileKind.audio:
            return _buildAudioView();
          case FileKind.text:
            return _buildTextView();
          case FileKind.unknown:
            return _buildUnknownView();
        }
      },
    );
  }

  Widget _buildImageView() => Scaffold(
    appBar: _appbar(
      actions: [
        IconButton(
          onPressed: () {
            setState(() {
              photoViewController.rotation += pi / 2;
            });
          },
          tooltip: 'Drehen',
          icon: const Icon(Icons.rotate_right),
        ),
      ],
    ),
    backgroundColor: Colors.white,
    body: PhotoView(
      controller: photoViewController,
      maxScale: 3.0,
      minScale: 0.1,
      imageProvider: Image.file(File(widget.path)).image,
      backgroundDecoration: BoxDecoration(
        color: Theme.of(context).colorScheme.surface,
      ),
    ),
  );

  Widget _buildSvgView() => Scaffold(
    appBar: _appbar(),
    backgroundColor: Colors.white,
    body: InteractiveViewer(
      minScale: 0.5,
      maxScale: 8,
      child: Center(
        child: SvgPicture.file(
          File(widget.path),
          placeholderBuilder: (_) =>
              const Center(child: AppProgressIndicator.large()),
        ),
      ),
    ),
  );

  Widget _buildPdfView() => Scaffold(
    appBar: _appbar(),
    body: DeferredPdfViewer(path: widget.path),
  );

  Widget _buildVideoView() => Scaffold(
    appBar: _appbar(),
    backgroundColor: Colors.black,
    body: MediaPlayer(path: widget.path, isAudio: false),
  );

  Widget _buildAudioView() => Scaffold(
    appBar: _appbar(),
    body: MediaPlayer(
      path: widget.path,
      isAudio: true,
      filename: widget.path.split('/').last,
    ),
  );

  Widget _buildTextView() => Scaffold(
    appBar: _appbar(),
    body: FutureBuilder<_TextPayload>(
      future: _readTextPayload(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return const Center(child: AppProgressIndicator.large());
        }
        final payload = snapshot.data!;
        final lines = const LineSplitter().convert(payload.content);
        // Stable gutter width — sized by the highest line number's digit count.
        final gutterWidth = (lines.length.toString().length * 9.0) + 16;
        return SelectionArea(
          child: Scrollbar(
            child: CustomScrollView(
              slivers: [
                if (payload.truncated)
                  SliverToBoxAdapter(
                    child: SelectionContainer.disabled(
                      child: Container(
                        width: double.infinity,
                        color: Theme.of(
                          context,
                        ).colorScheme.surfaceContainerHigh,
                        padding: const EdgeInsets.symmetric(
                          horizontal: 16,
                          vertical: 8,
                        ),
                        child: Text(
                          'Datei ist groß — Anzeige auf die ersten ${(_textViewMaxBytes / 1024).round()} KB begrenzt.',
                          style: Theme.of(context).textTheme.bodySmall,
                        ),
                      ),
                    ),
                  ),
                SliverList.builder(
                  itemCount: lines.length,
                  itemBuilder: (context, i) => CodeLine(
                    number: i + 1,
                    text: lines[i],
                    gutterWidth: gutterWidth,
                  ),
                ),
                const SliverToBoxAdapter(child: SizedBox(height: 24)),
              ],
            ),
          ),
        );
      },
    ),
  );

  Widget _buildUnknownView() => Scaffold(
    appBar: _appbar(showActionsMenu: false),
    body: _buildUnknownPlaceholder(),
  );

  Widget _buildUnknownPlaceholder() {
    final theme = Theme.of(context);
    final descriptors = _availableActions();
    return ListView(
      padding: const EdgeInsets.symmetric(vertical: 24),
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 24),
          child: Column(
            children: [
              UnknownPreviewBlock(remoteFile: widget.remoteFile),
              const SizedBox(height: 16),
              Text(
                widget.path.split('/').last,
                style: theme.textTheme.titleSmall?.copyWith(
                  fontWeight: FontWeight.w600,
                ),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 8),
              Text(
                'Wähle eine Aktion, um mit der Datei weiterzuarbeiten.',
                style: theme.textTheme.bodySmall,
                textAlign: TextAlign.center,
              ),
            ],
          ),
        ),
        const SizedBox(height: 24),
        ...descriptors.map(
          (d) => ListTile(
            leading: CenteredLeading(Icon(d.icon)),
            title: Text(d.label),
            onTap: () => _handleAction(d.action),
          ),
        ),
      ],
    );
  }

  static const int _textViewMaxBytes = 5 * 1024 * 1024;

  Future<_TextPayload> _readTextPayload() async {
    final file = File(widget.path);
    final size = await file.length();
    final ext = widget.path.split('.').last.toLowerCase();
    if (size <= _textViewMaxBytes) {
      final raw = await file.readAsString();
      return _TextPayload(content: _maybePrettify(raw, ext), truncated: false);
    }
    final raf = await file.open();
    try {
      final bytes = await raf.read(_textViewMaxBytes);
      // Truncated payloads stay raw — a parser would choke on the dangling tail.
      return _TextPayload(
        content: utf8.decode(bytes, allowMalformed: true),
        truncated: true,
      );
    } finally {
      await raf.close();
    }
  }

  /// Falls through to the original text on parse errors.
  String _maybePrettify(String content, String ext) {
    if (ext != 'json') return content;
    try {
      final parsed = jsonDecode(content);
      return const JsonEncoder.withIndent('  ').convert(parsed);
    } on Object {
      return content;
    }
  }
}

class _ActionDescriptor {
  final FileViewingActions action;
  final IconData icon;
  final String label;
  const _ActionDescriptor({
    required this.action,
    required this.icon,
    required this.label,
  });
}

class _TextPayload {
  final String content;
  final bool truncated;
  const _TextPayload({required this.content, required this.truncated});
}
