import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:archive/archive.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdfx/pdfx.dart';
import 'package:pyramid/core/theme.dart';
import 'package:url_launcher/url_launcher.dart';

// ─── Static render cache ──────────────────────────────────────────────────────
// Survives widget dispose/recreate (e.g. scroll in chat list).
// Key: filename + file-size-bytes + page + render-dimensions.
// Evicts the oldest entry (FIFO) when the limit is reached.

class _RenderCache {
  static const _max = 80;
  static final _data = <String, Uint8List>{};

  static String _k(String name, int sz, int page, int w, int h) =>
      '$name\x00$sz\x00$page\x00${w}x$h';

  static Uint8List? get(String name, int sz, int page, int w, int h) =>
      _data[_k(name, sz, page, w, h)];

  static void put(String name, int sz, int page, int w, int h, Uint8List img) {
    if (_data.length >= _max) _data.remove(_data.keys.first);
    _data[_k(name, sz, page, w, h)] = img;
  }
}

// ─── Inline chat preview ──────────────────────────────────────────────────────
// Used by _DocPreviewContent in message_group.dart.
// Shows the first page of a PDF as an image with page navigation arrows.
// Tap → opens the full DocumentViewer overlay.

class PdfInlinePreview extends StatefulWidget {
  final Uint8List bytes;
  final String filename;
  final PyramidTheme pt;
  final String senderName;
  final VoidCallback? onDownload;

  const PdfInlinePreview({
    super.key,
    required this.bytes,
    required this.filename,
    required this.pt,
    required this.senderName,
    this.onDownload,
  });

  @override
  State<PdfInlinePreview> createState() => _PdfInlinePreviewState();
}

class _PdfInlinePreviewState extends State<PdfInlinePreview> {
  PdfDocument? _doc;
  int _currentPage = 1;
  int _pageCount = 0;
  double? _pageAspect; // width/height
  final Map<int, Uint8List?> _cache = {};
  bool _loading = true;
  String? _error;
  bool _rendering = false;

  static const _previewW = 280.0;
  static const _renderScale = 2.0;

  @override
  void initState() {
    super.initState();
    _init();
  }

  Future<void> _init() async {
    try {
      final doc = await PdfDocument.openData(widget.bytes);
      if (!mounted) { await doc.close(); return; }
      _doc = doc;
      _pageCount = doc.pagesCount;
      setState(() => _loading = false);
      await _ensurePage(1);
    } catch (e) {
      if (mounted) setState(() { _loading = false; _error = e.toString().split('\n').first; });
    }
  }

  Future<void> _ensurePage(int n) async {
    if (_cache.containsKey(n) || _rendering) return;
    _rendering = true;
    try {
      final page = await _doc!.getPage(n);
      if (mounted && _pageAspect == null) {
        setState(() => _pageAspect = page.width / page.height);
      }
      final rw = (page.width * _renderScale).round();
      final rh = (page.height * _renderScale).round();
      final hit = _RenderCache.get(widget.filename, widget.bytes.length, n, rw, rh);
      if (hit != null) {
        await page.close();
        if (mounted) setState(() => _cache[n] = hit);
        return;
      }
      final img = await page.render(
        width: page.width * _renderScale,
        height: page.height * _renderScale,
        format: PdfPageImageFormat.jpeg,
        backgroundColor: '#ffffff',
      );
      await page.close();
      if (img?.bytes != null) {
        _RenderCache.put(widget.filename, widget.bytes.length, n, rw, rh, img!.bytes);
      }
      if (mounted) setState(() => _cache[n] = img?.bytes);
    } catch (_) {
      _cache[n] = null;
    } finally {
      _rendering = false;
    }
  }

  void _goTo(int page) {
    if (page < 1 || page > _pageCount) return;
    setState(() => _currentPage = page);
    _ensurePage(page);
    _ensurePage((page + 1).clamp(1, _pageCount));
  }

  void _openFull() {
    DocumentViewer.show(
      context,
      filename: widget.filename,
      bytes: widget.bytes,
      senderName: widget.senderName,
      initialPage: _currentPage,
    );
  }

  @override
  void dispose() {
    _doc?.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final pt = widget.pt;
    final radius = BorderRadius.circular(pt.rBase);

    if (_loading) {
      return Container(
        width: _previewW,
        // Match the typical loaded A4 height so the preview doesn't jump (and
        // shift the scroll position) once the first page finishes rendering.
        height: (_previewW / 0.707).clamp(160.0, 400.0),
        margin: const EdgeInsets.only(top: 4, bottom: 4),
        decoration: BoxDecoration(
          color: pt.bg2,
          borderRadius: radius,
          border: Border.all(color: pt.border),
        ),
        child: Center(child: CircularProgressIndicator(strokeWidth: 2, color: pt.accent)),
      );
    }

    if (_error != null) {
      return _FileFallback(filename: widget.filename, pt: pt, onDownload: widget.onDownload);
    }

    final pageBytes = _cache[_currentPage];
    final aspect = _pageAspect ?? 0.707; // A4 fallback
    final imgH = (_previewW / aspect).clamp(160.0, 400.0);

    return Listener(
      onPointerSignal: (event) {
        if (event is PointerScrollEvent) {
          GestureBinding.instance.pointerSignalResolver.register(event, (resolved) {
            final dy = (resolved as PointerScrollEvent).scrollDelta.dy;
            if (dy > 0) { _goTo(_currentPage + 1); }
            else if (dy < 0) { _goTo(_currentPage - 1); }
          });
        }
      },
      child: GestureDetector(
      onTap: _openFull,
      onHorizontalDragEnd: (d) {
        if ((d.primaryVelocity ?? 0) < -200) _goTo(_currentPage + 1);
        if ((d.primaryVelocity ?? 0) > 200)  _goTo(_currentPage - 1);
      },
      child: Container(
        margin: const EdgeInsets.only(top: 4, bottom: 4),
        width: _previewW,
        decoration: BoxDecoration(
          borderRadius: radius,
          border: Border.all(color: pt.border),
        ),
        clipBehavior: Clip.hardEdge,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            // Page image
            Container(
              width: _previewW,
              height: imgH,
              color: Colors.white,
              child: pageBytes != null
                  ? Image.memory(pageBytes, width: _previewW, height: imgH, fit: BoxFit.cover, alignment: Alignment.topCenter)
                  : Center(child: CircularProgressIndicator(strokeWidth: 2, color: pt.accent)),
            ),
            // Footer
            Container(
              color: pt.bg2,
              padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
              child: Row(
                children: [
                  Icon(Icons.picture_as_pdf_outlined, size: 14, color: pt.fgDim),
                  const SizedBox(width: 6),
                  Expanded(
                    child: Text(
                      widget.filename,
                      style: TextStyle(color: pt.fg, fontSize: 12, fontWeight: FontWeight.w500),
                      overflow: TextOverflow.ellipsis,
                    ),
                  ),
                  if (_pageCount > 1) ...[
                    _NavArrow(
                      icon: Icons.chevron_left_rounded,
                      enabled: _currentPage > 1,
                      pt: pt,
                      onTap: () => _goTo(_currentPage - 1),
                    ),
                    Text(
                      '$_currentPage/$_pageCount',
                      style: TextStyle(color: pt.fgMuted, fontSize: 11, fontFeatures: const [FontFeature.tabularFigures()]),
                    ),
                    _NavArrow(
                      icon: Icons.chevron_right_rounded,
                      enabled: _currentPage < _pageCount,
                      pt: pt,
                      onTap: () => _goTo(_currentPage + 1),
                    ),
                  ],
                  if (widget.onDownload != null) ...[
                    const SizedBox(width: 4),
                    GestureDetector(
                      onTap: widget.onDownload,
                      behavior: HitTestBehavior.opaque,
                      child: Padding(
                        padding: const EdgeInsets.all(2),
                        child: Icon(Icons.download_rounded, size: 16, color: pt.fgMuted),
                      ),
                    ),
                  ],
                ],
              ),
            ),
          ],
        ),
      ),
      ),
    );
  }
}

class _NavArrow extends StatelessWidget {
  final IconData icon;
  final bool enabled;
  final PyramidTheme pt;
  final VoidCallback onTap;
  const _NavArrow({required this.icon, required this.enabled, required this.pt, required this.onTap});

  @override
  Widget build(BuildContext context) => GestureDetector(
    onTap: enabled ? onTap : null,
    behavior: HitTestBehavior.opaque,
    child: Padding(
      padding: const EdgeInsets.symmetric(horizontal: 2),
      child: Icon(icon, size: 18, color: enabled ? pt.fg : pt.fgDim),
    ),
  );
}

class _FileFallback extends StatelessWidget {
  final String filename;
  final PyramidTheme pt;
  final VoidCallback? onDownload;
  const _FileFallback({required this.filename, required this.pt, this.onDownload});

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.only(top: 4, bottom: 4),
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
      constraints: const BoxConstraints(maxWidth: 300),
      decoration: BoxDecoration(
        color: pt.bg3,
        borderRadius: BorderRadius.circular(pt.rBase),
        border: Border.all(color: pt.border),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            width: 36, height: 36,
            decoration: BoxDecoration(color: pt.accentSoft, borderRadius: BorderRadius.circular(pt.rSm)),
            child: Icon(Icons.picture_as_pdf_outlined, size: 20, color: pt.accent),
          ),
          const SizedBox(width: 10),
          Expanded(
            child: Text(filename, style: TextStyle(color: pt.fg, fontSize: 13, fontWeight: FontWeight.w500), maxLines: 2, overflow: TextOverflow.ellipsis),
          ),
          if (onDownload != null)
            IconButton(
              onPressed: onDownload,
              icon: Icon(Icons.download_rounded, size: 20, color: pt.fgMuted),
              constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
              padding: EdgeInsets.zero,
            ),
        ],
      ),
    );
  }
}

// ─── Full overlay viewer ──────────────────────────────────────────────────────

class DocumentViewer extends StatelessWidget {
  final String filename;
  final Uint8List bytes;
  final String senderName;
  final int initialPage;

  const DocumentViewer({
    super.key,
    required this.filename,
    required this.bytes,
    required this.senderName,
    this.initialPage = 1,
  });

  static bool supportsPreview(String filename) =>
      _detectType(filename) != _DocType.unknown;

  /// True for formats rendered inline in chat (pdf/svg types, including ai/eps).
  static bool isInlinePreviewable(String filename) {
    final t = _detectType(filename);
    return t == _DocType.pdf || t == _DocType.svg;
  }

  /// True for formats that show a card inline in chat (needs download-on-tap).
  static bool showsInlineCard(String filename) {
    final t = _detectType(filename);
    return t == _DocType.proprietary || t == _DocType.psdThumb || t == _DocType.zipPreview;
  }

  /// Kept for compatibility — same as showsInlineCard.
  static bool isProprietaryFormat(String filename) => showsInlineCard(filename);

  static Future<void> show(
    BuildContext context, {
    required String filename,
    required Uint8List bytes,
    required String senderName,
    int initialPage = 1,
  }) {
    return showGeneralDialog(
      context: context,
      barrierDismissible: true,
      barrierLabel: 'Schließen',
      barrierColor: Colors.black.withAlpha(178),
      transitionDuration: const Duration(milliseconds: 180),
      transitionBuilder: (ctx, anim, a, child) =>
          FadeTransition(opacity: anim, child: child),
      pageBuilder: (ctx, a, b) => DocumentViewer(
        filename: filename,
        bytes: bytes,
        senderName: senderName,
        initialPage: initialPage,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final pt = PyramidTheme.of(context);
    final type = _detectType(filename);
    final size = MediaQuery.sizeOf(context);
    final maxW = (size.width - 48).clamp(300.0, 1100.0);
    final maxH = (size.height - 48).clamp(300.0, 900.0);

    return Material(
      type: MaterialType.transparency,
      child: Center(
        child: Container(
          width: maxW,
          height: maxH,
          decoration: BoxDecoration(
            color: pt.bg0,
            borderRadius: BorderRadius.circular(pt.rLg),
            border: Border.all(color: pt.border),
            boxShadow: [BoxShadow(color: Colors.black.withAlpha(80), blurRadius: 32, offset: const Offset(0, 8))],
          ),
          clipBehavior: Clip.hardEdge,
          child: Column(
            children: [
              _DocHeader(filename: filename, senderName: senderName, pt: pt),
              Expanded(
                child: _DocContent(
                  filename: filename,
                  bytes: bytes,
                  type: type,
                  pt: pt,
                  initialPage: initialPage,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// ─── Header ──────────────────────────────────────────────────────────────────

class _DocHeader extends StatelessWidget {
  final String filename;
  final String senderName;
  final PyramidTheme pt;
  const _DocHeader({required this.filename, required this.senderName, required this.pt});

  @override
  Widget build(BuildContext context) {
    final ext = filename.contains('.') ? filename.split('.').last.toUpperCase() : '';
    return Container(
      padding: const EdgeInsets.fromLTRB(16, 12, 8, 12),
      decoration: BoxDecoration(
        color: pt.bg1,
        border: Border(bottom: BorderSide(color: pt.border)),
      ),
      child: Row(
        children: [
          _ExtBadge(ext: ext, pt: pt),
          const SizedBox(width: 12),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(filename, style: TextStyle(color: pt.fg, fontSize: 14, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis),
                Text(senderName, style: TextStyle(color: pt.fgDim, fontSize: 12)),
              ],
            ),
          ),
          IconButton(
            onPressed: () => Navigator.of(context).pop(),
            icon: Icon(Icons.close_rounded, color: pt.fgMuted, size: 20),
            tooltip: 'Schließen',
          ),
        ],
      ),
    );
  }
}

class _ExtBadge extends StatelessWidget {
  final String ext;
  final PyramidTheme pt;
  const _ExtBadge({required this.ext, required this.pt});

  @override
  Widget build(BuildContext context) {
    final label = ext.length > 4 ? ext.substring(0, 4) : ext;
    return Container(
      width: 44, height: 44,
      decoration: BoxDecoration(color: pt.accentSoft, borderRadius: BorderRadius.circular(pt.rSm)),
      child: Center(
        child: Text(label, style: TextStyle(color: pt.accent, fontSize: label.length > 3 ? 10 : 12, fontWeight: FontWeight.w700)),
      ),
    );
  }
}

// ─── Content dispatcher ───────────────────────────────────────────────────────

class _DocContent extends StatelessWidget {
  final String filename;
  final Uint8List bytes;
  final _DocType type;
  final PyramidTheme pt;
  final int initialPage;
  const _DocContent({required this.filename, required this.bytes, required this.type, required this.pt, required this.initialPage});

  @override
  Widget build(BuildContext context) {
    return switch (type) {
      _DocType.pdf      => _PdfFullViewer(bytes: bytes, filename: filename, pt: pt, initialPage: initialPage),
      _DocType.svg      => _SvgViewer(bytes: bytes, pt: pt),
      _DocType.markdown => _MarkdownViewer(bytes: bytes, pt: pt),
      _DocType.text     => _TextViewer(bytes: bytes, pt: pt, mono: false),
      _DocType.code     => _TextViewer(bytes: bytes, pt: pt, mono: true),
      _DocType.office   => _OfficeTextViewer(bytes: bytes, filename: filename, pt: pt),
      _DocType.archive      => _ArchiveViewer(bytes: bytes, filename: filename, pt: pt),
      _DocType.psdThumb     => _PsdThumbViewer(bytes: bytes, filename: filename, pt: pt),
      _DocType.zipPreview   => _ZipPreviewViewer(bytes: bytes, filename: filename, pt: pt),
      _DocType.proprietary  => _ProprietaryViewer(filename: filename, bytes: bytes, pt: pt),
      _DocType.unknown      => _UnsupportedViewer(pt: pt),
    };
  }
}

// ─── PDF full viewer with thumbnail strip ────────────────────────────────────

class _PdfFullViewer extends StatefulWidget {
  final Uint8List bytes;
  final String filename;
  final PyramidTheme pt;
  final int initialPage;
  const _PdfFullViewer({required this.bytes, required this.filename, required this.pt, required this.initialPage});

  @override
  State<_PdfFullViewer> createState() => _PdfFullViewerState();
}

class _PdfFullViewerState extends State<_PdfFullViewer> {
  late final PdfController _ctrl;
  PdfDocument? _thumbDoc;
  final Map<int, Uint8List?> _thumbs = {};
  int _pageCount = 0;
  int _currentPage = 1;
  bool _animating = false;

  @override
  void initState() {
    super.initState();
    _currentPage = widget.initialPage;
    _ctrl = PdfController(
      document: PdfDocument.openData(widget.bytes),
      initialPage: widget.initialPage,
    );
    _ctrl.pageListenable.addListener(_onPage);
    _initThumbs();
  }

  Future<void> _initThumbs() async {
    final doc = await PdfDocument.openData(widget.bytes);
    if (!mounted) { await doc.close(); return; }
    _thumbDoc = doc;
    if (!mounted) { await doc.close(); _thumbDoc = null; return; }
    setState(() => _pageCount = doc.pagesCount);
    for (var i = 1; i <= doc.pagesCount; i++) {
      _renderThumb(i);
    }
  }

  Future<void> _renderThumb(int n) async {
    final doc = _thumbDoc;
    if (doc == null) return;
    try {
      final page = await doc.getPage(n);
      final rw = (page.width * 0.12).round();
      final rh = (page.height * 0.12).round();
      final hit = _RenderCache.get(widget.filename, widget.bytes.length, n, rw, rh);
      if (hit != null) {
        await page.close();
        if (mounted) setState(() => _thumbs[n] = hit);
        return;
      }
      final img = await page.render(
        width: page.width * 0.12,
        height: page.height * 0.12,
        format: PdfPageImageFormat.jpeg,
        backgroundColor: '#ffffff',
      );
      await page.close();
      if (img?.bytes != null) {
        _RenderCache.put(widget.filename, widget.bytes.length, n, rw, rh, img!.bytes);
      }
      if (mounted) setState(() => _thumbs[n] = img?.bytes);
    } catch (_) {
      _thumbs[n] = null;
    }
  }

  void _onPage() => setState(() => _currentPage = _ctrl.page);

  void _onScrollPage(int delta) {
    if (_animating) return;
    final pageCount = _ctrl.pagesCount ?? _pageCount;
    if (pageCount == 0) return;
    final target = (_currentPage + delta).clamp(1, pageCount);
    if (target == _currentPage) return;
    _animating = true;
    _ctrl.animateToPage(
      target,
      duration: const Duration(milliseconds: 280),
      curve: Curves.easeInOut,
    ).then((_) { if (mounted) setState(() => _animating = false); });
  }

  @override
  void dispose() {
    _ctrl.pageListenable.removeListener(_onPage);
    _ctrl.dispose();
    _thumbDoc?.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final pt = widget.pt;
    return Column(
      children: [
        Expanded(
          child: Stack(
            children: [
              Listener(
                onPointerSignal: (event) {
                  if (event is PointerScrollEvent) {
                    if (event.scrollDelta.dy > 0) { _onScrollPage(1); }
                    else if (event.scrollDelta.dy < 0) { _onScrollPage(-1); }
                  }
                },
                child: PdfView(
                  controller: _ctrl,
                  scrollDirection: Axis.vertical,
                  physics: const NeverScrollableScrollPhysics(),
                  backgroundDecoration: BoxDecoration(color: pt.bg0),
                  onPageChanged: (p) => setState(() => _currentPage = p),
                  // Render at high resolution in PNG: sharper text/line-art and no
                  // JPEG chroma artifacts (matters most for AI/EPS/vector content),
                  // and gives headroom for the InteractiveViewer zoom.
                  renderer: (page) => page.render(
                    width: page.width * 4,
                    height: page.height * 4,
                    format: PdfPageImageFormat.png,
                    backgroundColor: '#ffffff',
                  ),
                  builders: PdfViewBuilders<DefaultBuilderOptions>(
                    options: const DefaultBuilderOptions(),
                    documentLoaderBuilder: (ctx) => Center(
                      child: CircularProgressIndicator(strokeWidth: 2, color: pt.accent),
                    ),
                    errorBuilder: (ctx, error) => _ErrorView(message: error.toString(), pt: pt),
                  ),
                ),
              ),
              // Launch the immersive fullscreen page viewer.
              Positioned(
                top: 8,
                right: 8,
                child: Material(
                  color: pt.bg0.withAlpha(200),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(pt.rSm),
                    side: BorderSide(color: pt.border),
                  ),
                  child: IconButton(
                    tooltip: 'Vollbild',
                    icon: Icon(Icons.fullscreen_rounded, size: 22, color: pt.fg),
                    onPressed: () => _PdfFullscreenViewer.show(
                      context,
                      bytes: widget.bytes,
                      filename: widget.filename,
                      initialPage: _currentPage,
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
        if (_pageCount > 1) _ThumbnailStrip(
          pageCount: _pageCount,
          currentPage: _currentPage,
          thumbs: _thumbs,
          pt: pt,
          onTap: (p) {
            _ctrl.jumpToPage(p);
            setState(() => _currentPage = p);
          },
        ),
      ],
    );
  }
}

// ─── Immersive fullscreen PDF viewer ─────────────────────────────────────────
// Launched from the enlarged preview. Per-page high-res render, pan via drag,
// zoom via +/− buttons (20%/click) and Ctrl+wheel, page nav via wheel/arrows.

Future<void> _saveBytes(BuildContext context, Uint8List bytes, String filename) async {
  try {
    Directory? dir;
    if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
      dir = await getDownloadsDirectory();
    }
    dir ??= await getTemporaryDirectory();
    final ext = filename.contains('.') ? filename.split('.').last : 'bin';
    final name = 'pyramid_${DateTime.now().millisecondsSinceEpoch}.$ext';
    final file = File('${dir.path}/$name');
    await file.writeAsBytes(bytes);
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Gespeichert: ${file.path}'), duration: const Duration(seconds: 4)),
      );
    }
  } catch (e) {
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Fehler: $e')));
    }
  }
}

class _PdfFullscreenViewer extends StatefulWidget {
  final Uint8List bytes;
  final String filename;
  final int initialPage;
  const _PdfFullscreenViewer({required this.bytes, required this.filename, required this.initialPage});

  static Future<void> show(BuildContext context, {
    required Uint8List bytes,
    required String filename,
    required int initialPage,
  }) {
    return Navigator.of(context, rootNavigator: true).push(PageRouteBuilder(
      opaque: true,
      barrierColor: Colors.black,
      transitionDuration: const Duration(milliseconds: 180),
      pageBuilder: (_, _, _) =>
          _PdfFullscreenViewer(bytes: bytes, filename: filename, initialPage: initialPage),
      transitionsBuilder: (_, anim, _, child) => FadeTransition(opacity: anim, child: child),
    ));
  }

  @override
  State<_PdfFullscreenViewer> createState() => _PdfFullscreenViewerState();
}

class _PdfFullscreenViewerState extends State<_PdfFullscreenViewer> {
  PdfDocument? _doc;
  final _tc = TransformationController();
  final _viewerKey = GlobalKey();
  int _page = 1;
  int _count = 0;
  Uint8List? _img;
  bool _loading = true;
  bool _rendering = false;
  bool _uiVisible = true;

  bool get _isMobile => Platform.isAndroid || Platform.isIOS;

  static const _minScale = 1.0;
  static const _maxScale = 8.0;
  static const _renderScale = 5.0; // high-res page render for crisp zoom

  @override
  void initState() {
    super.initState();
    _page = widget.initialPage;
    _open();
  }

  Future<void> _open() async {
    try {
      final doc = await PdfDocument.openData(widget.bytes);
      if (!mounted) { await doc.close(); return; }
      _doc = doc;
      _count = doc.pagesCount;
      await _render(_page);
    } catch (_) {
      if (mounted) setState(() => _loading = false);
    }
  }

  Future<void> _render(int n) async {
    final doc = _doc;
    if (doc == null || _rendering) return;
    _rendering = true;
    if (mounted) setState(() => _loading = true);
    try {
      final page = await doc.getPage(n);
      final rw = (page.width * _renderScale).round();
      final rh = (page.height * _renderScale).round();
      var bytes = _RenderCache.get(widget.filename, widget.bytes.length, n, rw, rh);
      if (bytes == null) {
        final img = await page.render(
          width: page.width * _renderScale,
          height: page.height * _renderScale,
          format: PdfPageImageFormat.png,
          backgroundColor: '#ffffff',
        );
        bytes = img?.bytes;
        if (bytes != null) {
          _RenderCache.put(widget.filename, widget.bytes.length, n, rw, rh, bytes);
        }
      }
      await page.close();
      if (mounted) setState(() { _img = bytes; _loading = false; });
    } catch (_) {
      if (mounted) setState(() => _loading = false);
    } finally {
      _rendering = false;
    }
  }

  void _goto(int n) {
    final t = n.clamp(1, _count);
    if (t == _page) return;
    _tc.value = Matrix4.identity(); // reset zoom on page change
    setState(() => _page = t);
    _render(t);
  }

  // factor 1.2 = +20%, 1/1.2 = −20%
  void _zoom(double factor) {
    final size = _viewerKey.currentContext?.size;
    if (size == null) return;
    final current = _tc.value.getMaxScaleOnAxis();
    final target = (current * factor).clamp(_minScale, _maxScale);
    final applied = target / current;
    if ((applied - 1).abs() < 0.001) return;
    final cx = size.width / 2, cy = size.height / 2;
    final m = _tc.value.clone()
      ..translateByDouble(cx, cy, 0, 1)
      ..scaleByDouble(applied, applied, applied, 1)
      ..translateByDouble(-cx, -cy, 0, 1);
    setState(() => _tc.value = m);
  }

  @override
  void dispose() {
    _doc?.close();
    _tc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final pt = PyramidTheme.of(context);
    return Scaffold(
      backgroundColor: Colors.black,
      body: SafeArea(
        child: Stack(
          children: [
            Positioned.fill(
              child: GestureDetector(
                // Single tap on the page (not on a control) toggles the UI.
                behavior: HitTestBehavior.translucent,
                onTap: () => setState(() => _uiVisible = !_uiVisible),
                child: Listener(
                  onPointerSignal: (e) {
                    if (e is! PointerScrollEvent) return;
                    final ctrl = HardwareKeyboard.instance.isControlPressed ||
                        HardwareKeyboard.instance.isMetaPressed;
                    if (ctrl) {
                      _zoom(e.scrollDelta.dy < 0 ? 1.2 : 1 / 1.2);
                    } else {
                      _goto(e.scrollDelta.dy > 0 ? _page + 1 : _page - 1);
                    }
                  },
                  child: _img == null
                      ? Center(
                          child: _loading
                              ? CircularProgressIndicator(color: pt.accent)
                              : const Icon(Icons.error_outline_rounded, color: Colors.white54, size: 40),
                        )
                      : InteractiveViewer(
                          key: _viewerKey,
                          transformationController: _tc,
                          minScale: _minScale,
                          maxScale: _maxScale,
                          // Pinch-zoom on mobile only. On desktop the built-in
                          // scroll-wheel zoom is disabled so plain scrolling
                          // pages instead; zoom is Ctrl+wheel and the +/− buttons.
                          scaleEnabled: _isMobile,
                          child: Center(
                            child: Image.memory(_img!, fit: BoxFit.contain, gaplessPlayback: true),
                          ),
                        ),
                ),
              ),
            ),

            // Right-side control column
            if (_uiVisible)
              Positioned(
                right: 10,
                top: 0,
                bottom: 0,
                child: Center(
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      _FsBtn(icon: Icons.add_rounded, tooltip: 'Vergrößern', pt: pt, onTap: () => _zoom(1.2)),
                      const SizedBox(height: 8),
                      _FsBtn(icon: Icons.remove_rounded, tooltip: 'Verkleinern', pt: pt, onTap: () => _zoom(1 / 1.2)),
                      const SizedBox(height: 8),
                      _FsBtn(icon: Icons.download_rounded, tooltip: 'Herunterladen', pt: pt,
                          onTap: () => _saveBytes(context, widget.bytes, widget.filename)),
                      const SizedBox(height: 8),
                      _FsBtn(icon: Icons.fullscreen_exit_rounded, tooltip: 'Vollbild beenden', pt: pt,
                          onTap: () => Navigator.of(context).maybePop()),
                    ],
                  ),
                ),
              ),

            // Bottom-center page navigation
            if (_uiVisible && _count > 1)
              Positioned(
                bottom: 16,
                left: 0,
                right: 0,
                child: Center(
                  child: Container(
                    padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
                    decoration: BoxDecoration(
                      color: Colors.black.withAlpha(160),
                      borderRadius: BorderRadius.circular(24),
                      border: Border.all(color: Colors.white24),
                    ),
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        _FsBtn(icon: Icons.chevron_left_rounded, tooltip: 'Vorherige', pt: pt,
                            enabled: _page > 1, onTap: () => _goto(_page - 1), flat: true),
                        Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 10),
                          child: Text('$_page / $_count',
                              style: const TextStyle(color: Colors.white, fontSize: 13,
                                  fontFeatures: [FontFeature.tabularFigures()])),
                        ),
                        _FsBtn(icon: Icons.chevron_right_rounded, tooltip: 'Nächste', pt: pt,
                            enabled: _page < _count, onTap: () => _goto(_page + 1), flat: true),
                      ],
                    ),
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }
}

class _FsBtn extends StatelessWidget {
  final IconData icon;
  final String tooltip;
  final PyramidTheme pt;
  final VoidCallback onTap;
  final bool enabled;
  final bool flat;
  const _FsBtn({required this.icon, required this.tooltip, required this.pt,
      required this.onTap, this.enabled = true, this.flat = false});

  @override
  Widget build(BuildContext context) {
    final btn = Material(
      color: flat ? Colors.transparent : Colors.black.withAlpha(160),
      shape: flat
          ? const CircleBorder()
          : RoundedRectangleBorder(borderRadius: BorderRadius.circular(10), side: const BorderSide(color: Colors.white24)),
      child: IconButton(
        tooltip: tooltip,
        icon: Icon(icon, color: enabled ? Colors.white : Colors.white30, size: 22),
        onPressed: enabled ? onTap : null,
      ),
    );
    return btn;
  }
}

class _ThumbnailStrip extends StatefulWidget {
  final int pageCount;
  final int currentPage;
  final Map<int, Uint8List?> thumbs;
  final PyramidTheme pt;
  final ValueChanged<int> onTap;
  const _ThumbnailStrip({required this.pageCount, required this.currentPage, required this.thumbs, required this.pt, required this.onTap});

  @override
  State<_ThumbnailStrip> createState() => _ThumbnailStripState();
}

class _ThumbnailStripState extends State<_ThumbnailStrip> {
  late final ScrollController _sc;

  @override
  void initState() {
    super.initState();
    _sc = ScrollController();
  }

  @override
  void didUpdateWidget(_ThumbnailStrip old) {
    super.didUpdateWidget(old);
    if (old.currentPage != widget.currentPage) {
      _scrollToPage(widget.currentPage);
    }
  }

  void _scrollToPage(int page) {
    const thumbW = 60.0 + 8.0; // thumb + margin
    final target = (page - 1) * thumbW - 80;
    if (_sc.hasClients) {
      _sc.animateTo(
        target.clamp(0.0, _sc.position.maxScrollExtent),
        duration: const Duration(milliseconds: 200),
        curve: Curves.easeOut,
      );
    }
  }

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

  @override
  Widget build(BuildContext context) {
    final pt = widget.pt;
    return Container(
      height: 88,
      decoration: BoxDecoration(
        color: pt.bg1,
        border: Border(top: BorderSide(color: pt.border)),
      ),
      child: ListView.builder(
        controller: _sc,
        scrollDirection: Axis.horizontal,
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
        itemCount: widget.pageCount,
        itemBuilder: (ctx, i) {
          final page = i + 1;
          final isCurrent = page == widget.currentPage;
          final bytes = widget.thumbs[page];
          return GestureDetector(
            onTap: () => widget.onTap(page),
            child: Container(
              width: 52,
              margin: const EdgeInsets.only(right: 8),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(4),
                border: Border.all(
                  color: isCurrent ? pt.accent : pt.border,
                  width: isCurrent ? 2 : 1,
                ),
              ),
              clipBehavior: Clip.hardEdge,
              child: bytes != null
                  ? Image.memory(bytes, fit: BoxFit.cover)
                  : Center(
                      child: Text('$page', style: TextStyle(color: pt.fgDim, fontSize: 10)),
                    ),
            ),
          );
        },
      ),
    );
  }
}

// ─── SVG viewer ───────────────────────────────────────────────────────────────

class _SvgViewer extends StatelessWidget {
  final Uint8List bytes;
  final PyramidTheme pt;
  const _SvgViewer({required this.bytes, required this.pt});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: InteractiveViewer(
        minScale: 0.5,
        maxScale: 10,
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: SvgPicture.memory(bytes),
        ),
      ),
    );
  }
}

// ─── Markdown viewer ──────────────────────────────────────────────────────────

class _MarkdownViewer extends StatelessWidget {
  final Uint8List bytes;
  final PyramidTheme pt;
  const _MarkdownViewer({required this.bytes, required this.pt});

  @override
  Widget build(BuildContext context) {
    final String text;
    try {
      text = utf8.decode(bytes, allowMalformed: true);
    } catch (e) {
      return _ErrorView(message: e.toString(), pt: pt);
    }

    return Markdown(
      data: text,
      selectable: true,
      padding: const EdgeInsets.all(24),
      styleSheet: MarkdownStyleSheet(
        p: TextStyle(color: pt.fg, fontSize: 14, height: 1.65),
        h1: TextStyle(color: pt.fg, fontSize: 22, fontWeight: FontWeight.w700),
        h2: TextStyle(color: pt.fg, fontSize: 18, fontWeight: FontWeight.w600),
        h3: TextStyle(color: pt.fg, fontSize: 16, fontWeight: FontWeight.w600),
        h4: TextStyle(color: pt.fg, fontSize: 14, fontWeight: FontWeight.w600),
        code: TextStyle(color: pt.accent, fontFamily: 'monospace', fontSize: 13, backgroundColor: pt.bg2),
        codeblockDecoration: BoxDecoration(
          color: pt.bg2,
          borderRadius: BorderRadius.circular(pt.rSm),
          border: Border.all(color: pt.border),
        ),
        blockquoteDecoration: BoxDecoration(
          border: Border(left: BorderSide(color: pt.accent, width: 3)),
          color: pt.accentSoft,
        ),
        blockquotePadding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
        a: TextStyle(color: pt.accent, decoration: TextDecoration.underline, decorationColor: pt.accent),
        strong: TextStyle(color: pt.fg, fontWeight: FontWeight.w600),
        em: TextStyle(color: pt.fg, fontStyle: FontStyle.italic),
        listBullet: TextStyle(color: pt.fgMuted),
        horizontalRuleDecoration: BoxDecoration(border: Border(top: BorderSide(color: pt.border))),
      ),
      onTapLink: (text, href, title) {
        if (href != null) launchUrl(Uri.parse(href), mode: LaunchMode.externalApplication);
      },
    );
  }
}

// ─── Text / Code viewer ───────────────────────────────────────────────────────

class _TextViewer extends StatelessWidget {
  final Uint8List bytes;
  final PyramidTheme pt;
  final bool mono;
  static const _maxBytes = 512 * 1024;
  const _TextViewer({required this.bytes, required this.pt, required this.mono});

  @override
  Widget build(BuildContext context) {
    final bool truncated = bytes.length > _maxBytes;
    final slice = truncated ? bytes.sublist(0, _maxBytes) : bytes;
    final String text;
    try {
      text = utf8.decode(slice, allowMalformed: true);
    } catch (e) {
      return _ErrorView(message: e.toString(), pt: pt);
    }

    return Column(
      children: [
        if (truncated)
          Container(
            width: double.infinity,
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
            color: pt.bg2,
            child: Text('Zeige erste ${_maxBytes ~/ 1024} KB', style: TextStyle(color: pt.fgDim, fontSize: 11)),
          ),
        Expanded(
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(20),
            child: SelectableText(
              text,
              style: mono
                  ? TextStyle(color: pt.fg, fontFamily: 'monospace', fontSize: 13, height: 1.5)
                  : TextStyle(color: pt.fg, fontSize: 14, height: 1.65),
            ),
          ),
        ),
      ],
    );
  }
}

// ─── Office text extraction (DOCX / PPTX / XLSX) ────────────────────────────

class _OfficeTextViewer extends StatefulWidget {
  final Uint8List bytes;
  final String filename;
  final PyramidTheme pt;
  const _OfficeTextViewer({required this.bytes, required this.filename, required this.pt});

  @override
  State<_OfficeTextViewer> createState() => _OfficeTextViewerState();
}

class _OfficeTextViewerState extends State<_OfficeTextViewer> {
  String? _text;
  String? _error;

  @override
  void initState() {
    super.initState();
    _extract();
  }

  void _extract() {
    try {
      final archive = ZipDecoder().decodeBytes(widget.bytes);
      final ext = widget.filename.contains('.') ? widget.filename.split('.').last.toLowerCase() : '';

      final xmlFiles = <ArchiveFile>[];
      for (final f in archive.files) {
        if (!f.isFile) continue;
        final n = f.name.toLowerCase();
        if (ext == 'docx' && n == 'word/document.xml') { xmlFiles.add(f); break; }
        if (ext == 'xlsx' && n.startsWith('xl/worksheets/') && n.endsWith('.xml')) xmlFiles.add(f);
        if (ext == 'pptx' && n.startsWith('ppt/slides/slide') && n.endsWith('.xml')) xmlFiles.add(f);
        // ODF formats (Writer/Impress/Calc) all use content.xml
        if ((ext == 'odt' || ext == 'odp' || ext == 'ods') && n == 'content.xml') { xmlFiles.add(f); break; }
      }

      final buf = StringBuffer();
      for (final f in xmlFiles) {
        final xml = utf8.decode(f.content as List<int>, allowMalformed: true);
        // Strip XML tags, collapse whitespace
        final stripped = xml
            .replaceAll(RegExp(r'<[^>]+>'), ' ')
            .replaceAll(RegExp(r'&amp;'), '&')
            .replaceAll(RegExp(r'&lt;'), '<')
            .replaceAll(RegExp(r'&gt;'), '>')
            .replaceAll(RegExp(r'&nbsp;'), ' ')
            .replaceAll(RegExp(r'\s+'), ' ')
            .trim();
        buf.writeln(stripped);
        buf.writeln();
      }

      _text = buf.toString().trim().isEmpty
          ? '(Kein lesbarer Text gefunden)'
          : buf.toString().trim();
    } catch (e) {
      _error = e.toString().split('\n').first;
    }
  }

  @override
  Widget build(BuildContext context) {
    final pt = widget.pt;
    if (_error != null) return _ErrorView(message: _error!, pt: pt);

    return Column(
      children: [
        Container(
          width: double.infinity,
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          color: pt.bg2,
          child: Row(
            children: [
              Icon(Icons.info_outline_rounded, size: 14, color: pt.fgDim),
              const SizedBox(width: 8),
              Text('Textvorschau (Formatierung nicht verfügbar)', style: TextStyle(color: pt.fgDim, fontSize: 12)),
            ],
          ),
        ),
        Expanded(
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(20),
            child: SelectableText(
              _text ?? '',
              style: TextStyle(color: pt.fg, fontSize: 14, height: 1.65),
            ),
          ),
        ),
      ],
    );
  }
}

// ─── Archive viewer ───────────────────────────────────────────────────────────

class _ArchiveViewer extends StatefulWidget {
  final Uint8List bytes;
  final String filename;
  final PyramidTheme pt;
  const _ArchiveViewer({required this.bytes, required this.filename, required this.pt});

  @override
  State<_ArchiveViewer> createState() => _ArchiveViewerState();
}

class _ArchiveViewerState extends State<_ArchiveViewer> {
  List<_ArchiveEntry>? _entries;
  String? _error;

  @override
  void initState() {
    super.initState();
    _decode();
  }

  void _decode() {
    try {
      final ext = widget.filename.contains('.') ? widget.filename.split('.').last.toLowerCase() : '';
      Archive archive;
      if (ext == 'tar') {
        archive = TarDecoder().decodeBytes(widget.bytes);
      } else if (ext == 'gz' || ext == 'tgz') {
        final inner = GZipDecoder().decodeBytes(widget.bytes);
        archive = TarDecoder().decodeBytes(inner);
      } else {
        archive = ZipDecoder().decodeBytes(widget.bytes);
      }

      final entries = archive.files.map((f) => _ArchiveEntry(name: f.name, size: f.size, isDir: !f.isFile)).toList()
        ..sort((a, b) {
          if (a.isDir != b.isDir) return a.isDir ? -1 : 1;
          return a.name.compareTo(b.name);
        });
      setState(() => _entries = entries);
    } catch (e) {
      setState(() => _error = e.toString());
    }
  }

  String _fmtSize(int b) {
    if (b < 1024) return '$b B';
    if (b < 1024 * 1024) return '${(b / 1024).toStringAsFixed(1)} KB';
    return '${(b / (1024 * 1024)).toStringAsFixed(1)} MB';
  }

  @override
  Widget build(BuildContext context) {
    if (_error != null) return _ErrorView(message: _error!, pt: widget.pt);
    if (_entries == null) return Center(child: CircularProgressIndicator(strokeWidth: 2, color: widget.pt.accent));

    final pt = widget.pt;
    final entries = _entries!;
    final totalBytes = entries.fold<int>(0, (s, e) => s + (e.isDir ? 0 : e.size));

    return Column(
      children: [
        Container(
          width: double.infinity,
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          color: pt.bg1,
          child: Row(
            children: [
              Icon(Icons.folder_zip_outlined, size: 15, color: pt.fgDim),
              const SizedBox(width: 8),
              Text('${entries.length} Einträge · ${_fmtSize(totalBytes)} unkomprimiert', style: TextStyle(color: pt.fgDim, fontSize: 12)),
            ],
          ),
        ),
        Expanded(
          child: ListView.builder(
            padding: const EdgeInsets.symmetric(vertical: 4),
            itemCount: entries.length,
            itemBuilder: (ctx, i) {
              final e = entries[i];
              return ListTile(
                dense: true,
                visualDensity: VisualDensity.compact,
                leading: Icon(e.isDir ? Icons.folder_outlined : _fileIcon(e.name), size: 18, color: e.isDir ? pt.accent : pt.fgDim),
                title: Text(e.name, style: TextStyle(color: pt.fg, fontSize: 13), overflow: TextOverflow.ellipsis),
                trailing: e.isDir ? null : Text(_fmtSize(e.size), style: TextStyle(color: pt.fgDim, fontSize: 11)),
              );
            },
          ),
        ),
      ],
    );
  }

  IconData _fileIcon(String name) {
    final ext = name.contains('.') ? name.split('.').last.toLowerCase() : '';
    return switch (ext) {
      'dart' || 'py' || 'js' || 'ts' || 'json' || 'xml' || 'yaml' || 'toml' => Icons.code_rounded,
      'png' || 'jpg' || 'jpeg' || 'gif' || 'webp' || 'svg'                  => Icons.image_outlined,
      'mp4' || 'mov' || 'avi' || 'mkv'                                       => Icons.videocam_outlined,
      'mp3' || 'ogg' || 'flac' || 'wav'                                      => Icons.music_note_outlined,
      'pdf'                                                                    => Icons.picture_as_pdf_outlined,
      'zip' || 'tar' || 'gz' || '7z' || 'rar'                               => Icons.folder_zip_outlined,
      'md' || 'markdown'                                                       => Icons.article_outlined,
      _                                                                        => Icons.insert_drive_file_outlined,
    };
  }
}

class _ArchiveEntry {
  final String name;
  final int size;
  final bool isDir;
  const _ArchiveEntry({required this.name, required this.size, required this.isDir});
}

// ─── PSD/PSB thumbnail viewer ────────────────────────────────────────────────
// Extracts the JPEG thumbnail stored in PSD Image Resource 1036.

Uint8List? _extractPsdThumbnail(Uint8List bytes) {
  try {
    if (bytes.length < 34) return null;
    final data = ByteData.sublistView(bytes);
    // Verify '8BPS' signature
    if (bytes[0] != 0x38 || bytes[1] != 0x42 || bytes[2] != 0x50 || bytes[3] != 0x53) return null;

    // PSD is big-endian throughout.
    // Color Mode Data section length at offset 26.
    final cmdLen = data.getUint32(26, Endian.big);
    // Image Resources section starts after 26-byte header + 4-byte length field + cmdLen bytes.
    final irPos = 30 + cmdLen;
    if (irPos + 4 > bytes.length) return null;
    final irLen = data.getUint32(irPos, Endian.big);

    int pos = irPos + 4;
    final irEnd = pos + irLen;

    while (pos + 10 <= irEnd && pos + 10 <= bytes.length) {
      // Signature: '8BIM'
      if (bytes[pos] != 0x38 || bytes[pos+1] != 0x42 || bytes[pos+2] != 0x49 || bytes[pos+3] != 0x4D) break;
      pos += 4;

      final resourceId = data.getUint16(pos, Endian.big);
      pos += 2;

      // Pascal string: 1-byte length + string, total padded to even.
      final nameLen = bytes[pos];
      pos += (nameLen + 2) & ~1;

      if (pos + 4 > bytes.length) break;
      final dataSize = data.getUint32(pos, Endian.big);
      pos += 4;

      // Resource 1036 (Photoshop 5+) or 1033 (older) = Thumbnail
      if ((resourceId == 0x040C || resourceId == 0x0409) && dataSize >= 28) {
        final format = data.getUint32(pos, Endian.big);
        if (format == 1) { // kJpegRGB
          // Use dataSize-28 for the JPEG extent (more reliable than the
          // compressedSize field at pos+20, which varies across PS versions).
          final jpegStart = pos + 28;
          final jpegEnd = (pos + dataSize).clamp(jpegStart, bytes.length);
          if (jpegEnd > jpegStart) {
            final thumb = Uint8List.sublistView(bytes, jpegStart, jpegEnd);
            // Validate JPEG magic bytes (FF D8)
            if (thumb.length >= 2 && thumb[0] == 0xFF && thumb[1] == 0xD8) {
              return thumb;
            }
            // Some files need a forward scan for the actual JPEG start
            for (var i = 0; i < thumb.length - 1; i++) {
              if (thumb[i] == 0xFF && thumb[i + 1] == 0xD8) {
                return Uint8List.sublistView(thumb, i);
              }
            }
          }
        }
        return null;
      }

      // Advance past this resource's data (padded to even).
      final advance = (dataSize + 1) & ~1;
      if (advance == 0 && dataSize == 0) { pos += 0; } else { pos += advance; }
    }
    return null;
  } catch (_) {
    return null;
  }
}

class _PsdThumbViewer extends StatelessWidget {
  final Uint8List bytes;
  final String filename;
  final PyramidTheme pt;
  const _PsdThumbViewer({required this.bytes, required this.filename, required this.pt});

  @override
  Widget build(BuildContext context) {
    final thumb = _extractPsdThumbnail(bytes);
    if (thumb == null) {
      return _ProprietaryViewer(filename: filename, bytes: bytes, pt: pt);
    }
    return Column(
      children: [
        Container(
          width: double.infinity,
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
          color: pt.bg2,
          child: Row(
            children: [
              Icon(Icons.info_outline_rounded, size: 14, color: pt.fgDim),
              const SizedBox(width: 8),
              Text('Eingebettete Vorschau (Vollbild nicht verfügbar)', style: TextStyle(color: pt.fgDim, fontSize: 12)),
            ],
          ),
        ),
        Expanded(
          child: InteractiveViewer(
            minScale: 0.5,
            maxScale: 10,
            child: Padding(
              padding: const EdgeInsets.all(24),
              child: Image.memory(thumb, fit: BoxFit.contain),
            ),
          ),
        ),
      ],
    );
  }
}

// ─── ZIP-preview viewer (Sketch, XD) ─────────────────────────────────────────
// These formats are ZIP archives that embed a preview PNG.

class _ZipPreviewViewer extends StatefulWidget {
  final Uint8List bytes;
  final String filename;
  final PyramidTheme pt;
  const _ZipPreviewViewer({required this.bytes, required this.filename, required this.pt});

  @override
  State<_ZipPreviewViewer> createState() => _ZipPreviewViewerState();
}

class _ZipPreviewViewerState extends State<_ZipPreviewViewer> {
  Uint8List? _preview;
  bool _loaded = false;
  bool _isImage = false;

  @override
  void initState() {
    super.initState();
    _extract();
  }

  void _extract() {
    try {
      final archive = ZipDecoder().decodeBytes(widget.bytes);
      // Priority search list
      const candidates = [
        'previews/preview.png', 'preview.png', 'thumbnail.png',
        'thumbnails/thumbnail.png', 'preview.jpg', 'thumbnail.jpg',
      ];
      ArchiveFile? found;
      for (final path in candidates) {
        found = archive.findFile(path);
        if (found != null && found.isFile) break;
      }
      // Fallback: any file with preview/thumbnail in the name
      if (found == null) {
        for (final f in archive.files) {
          if (!f.isFile) continue;
          final n = f.name.toLowerCase();
          if ((n.contains('preview') || n.contains('thumb')) &&
              (n.endsWith('.png') || n.endsWith('.jpg') || n.endsWith('.jpeg'))) {
            found = f;
            break;
          }
        }
      }
      if (found != null) {
        setState(() {
          _preview = Uint8List.fromList(found!.content as List<int>);
          _isImage = true;
          _loaded = true;
        });
      } else {
        setState(() => _loaded = true);
      }
    } catch (_) {
      setState(() => _loaded = true);
    }
  }

  @override
  Widget build(BuildContext context) {
    final pt = widget.pt;
    if (!_loaded) {
      return Center(child: CircularProgressIndicator(strokeWidth: 2, color: pt.accent));
    }
    if (_isImage && _preview != null) {
      return Column(
        children: [
          Container(
            width: double.infinity,
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
            color: pt.bg2,
            child: Row(
              children: [
                Icon(Icons.info_outline_rounded, size: 14, color: pt.fgDim),
                const SizedBox(width: 8),
                Text('Eingebettete Vorschau', style: TextStyle(color: pt.fgDim, fontSize: 12)),
              ],
            ),
          ),
          Expanded(
            child: InteractiveViewer(
              minScale: 0.5,
              maxScale: 10,
              child: Padding(
                padding: const EdgeInsets.all(24),
                child: Image.memory(_preview!, fit: BoxFit.contain),
              ),
            ),
          ),
        ],
      );
    }
    // No preview image found — fall back to archive listing
    return _ArchiveViewer(bytes: widget.bytes, filename: widget.filename, pt: pt);
  }
}

// ─── Proprietary format info card ────────────────────────────────────────────

class _ProprietaryViewer extends StatelessWidget {
  final String filename;
  final Uint8List bytes;
  final PyramidTheme pt;
  const _ProprietaryViewer({required this.filename, required this.bytes, required this.pt});

  String _fmtSize(int b) {
    if (b < 1024) return '$b B';
    if (b < 1024 * 1024) return '${(b / 1024).toStringAsFixed(1)} KB';
    return '${(b / (1024 * 1024)).toStringAsFixed(1)} MB';
  }

  IconData _iconFor(String ext) => switch (ext) {
    'psd' || 'psb'                         => Icons.photo_camera_outlined,
    'ai' || 'eps'                          => Icons.gesture_outlined,
    'indd' || 'inx'                        => Icons.menu_book_outlined,
    'afphoto' || 'afdesign' || 'afpub'     => Icons.palette_outlined,
    'sketch' || 'fig' || 'xd'             => Icons.design_services_outlined,
    'doc'                                  => Icons.description_outlined,
    'ppt'                                  => Icons.slideshow_outlined,
    'xls'                                  => Icons.table_chart_outlined,
    _                                      => Icons.insert_drive_file_outlined,
  };

  @override
  Widget build(BuildContext context) {
    final ext = filename.contains('.') ? filename.split('.').last.toLowerCase() : '';
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            width: 80, height: 80,
            decoration: BoxDecoration(color: pt.accentSoft, borderRadius: BorderRadius.circular(pt.rBase)),
            child: Icon(_iconFor(ext), size: 40, color: pt.accent),
          ),
          const SizedBox(height: 16),
          Text(
            filename,
            style: TextStyle(color: pt.fg, fontSize: 15, fontWeight: FontWeight.w600),
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: 6),
          Text(_fmtSize(bytes.length), style: TextStyle(color: pt.fgDim, fontSize: 13)),
          const SizedBox(height: 16),
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
            decoration: BoxDecoration(
              color: pt.bg2,
              borderRadius: BorderRadius.circular(pt.rSm),
              border: Border.all(color: pt.border),
            ),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                Icon(Icons.info_outline_rounded, size: 16, color: pt.fgDim),
                const SizedBox(width: 8),
                Text(
                  'Format kann nicht nativ dargestellt werden',
                  style: TextStyle(color: pt.fgDim, fontSize: 13),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// ─── Unsupported / Error ─────────────────────────────────────────────────────

class _UnsupportedViewer extends StatelessWidget {
  final PyramidTheme pt;
  const _UnsupportedViewer({required this.pt});

  @override
  Widget build(BuildContext context) => Center(
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Icon(Icons.preview_outlined, size: 48, color: pt.fgDim),
        const SizedBox(height: 12),
        Text('Vorschau nicht verfügbar', style: TextStyle(color: pt.fgDim, fontSize: 15)),
        const SizedBox(height: 4),
        Text('Proprietäres Format — bitte herunterladen.', style: TextStyle(color: pt.fgMuted, fontSize: 12)),
      ],
    ),
  );
}

class _ErrorView extends StatelessWidget {
  final String message;
  final PyramidTheme pt;
  const _ErrorView({required this.message, required this.pt});

  @override
  Widget build(BuildContext context) => Center(
    child: Padding(
      padding: const EdgeInsets.all(32),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(Icons.error_outline_rounded, size: 32, color: pt.danger),
          const SizedBox(height: 8),
          Text('Fehler beim Laden', style: TextStyle(color: pt.danger, fontSize: 14, fontWeight: FontWeight.w600)),
          const SizedBox(height: 6),
          Text(message, style: TextStyle(color: pt.fgMuted, fontSize: 12), textAlign: TextAlign.center),
        ],
      ),
    ),
  );
}

// ─── Type detection ───────────────────────────────────────────────────────────

enum _DocType { pdf, svg, text, code, markdown, office, archive, psdThumb, zipPreview, proprietary, unknown }

_DocType _detectType(String filename) {
  final ext = filename.contains('.') ? filename.split('.').last.toLowerCase() : '';
  return switch (ext) {
    // AI/EPS files embed a full PDF when saved with PDF compatibility
    'pdf' || 'ai' || 'eps'                         => _DocType.pdf,
    'svg'                                          => _DocType.svg,
    'md' || 'markdown' || 'rst'                    => _DocType.markdown,
    'zip' || 'tar' || 'gz' || 'tgz' || 'tar.gz'   => _DocType.archive,
    'txt' || 'log' || 'csv' || 'tsv' ||
    'rtf' || 'tex' || 'srt' || 'vtt' || 'nfo'     => _DocType.text,
    // ODF formats are ZIP+XML, same extraction approach as OOXML
    'docx' || 'xlsx' || 'pptx' ||
    'odt' || 'odp' || 'ods'                       => _DocType.office,
    'dart' || 'py' || 'js' || 'mjs' || 'cjs' || 'ts' || 'jsx' || 'tsx' ||
    'json' || 'jsonl' || 'ndjson' || 'yaml' || 'yml' || 'xml' || 'plist' ||
    'html' || 'htm' || 'css' || 'scss' || 'sass' || 'less' ||
    'vue' || 'svelte' || 'astro' ||
    'sh' || 'bash' || 'zsh' || 'fish' || 'ps' || 'ps1' || 'bat' || 'cmd' ||
    'kt' || 'kts' || 'gradle' || 'java' || 'swift' || 'go' || 'rs' ||
    'cpp' || 'cc' || 'cxx' || 'c' || 'h' || 'hpp' || 'hxx' || 'm' || 'mm' ||
    'cs' || 'rb' || 'php' || 'r' || 'lua' || 'pl' || 'pm' || 'scala' ||
    'clj' || 'ex' || 'exs' || 'erl' || 'hs' || 'jl' || 'nim' || 'zig' ||
    'toml' || 'ini' || 'conf' || 'cfg' || 'env' || 'editorconfig' ||
    'properties' || 'diff' || 'patch' || 'gitignore' || 'dockerfile' ||
    'sql' || 'graphql' || 'gql' || 'proto'         => _DocType.code,
    // PSD/PSB embed a JPEG thumbnail in Image Resource 1036
    'psd' || 'psb'                                 => _DocType.psdThumb,
    // Sketch and XD are ZIP archives containing a preview PNG
    'sketch' || 'xd'                               => _DocType.zipPreview,
    // Truly proprietary — no extractable preview
    'afphoto' || 'afdesign' || 'afpub' ||
    'indd' || 'inx' || 'fig' ||
    'doc' || 'ppt' || 'xls'                        => _DocType.proprietary,
    _                                              => _DocType.unknown,
  };
}
