part of '../document_viewer.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;
  }
}
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')));
    }
  }
}
// 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 _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.
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)),
                    ),
            ),
          );
        },
      ),
    );
  }
}
