import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:syncfusion_pdfviewer_platform_interface/pdfviewer_platform_interface.dart';

/// Service for generating, caching, and retrieving page thumbnails.
class ThumbnailService {
  static Future<File> _thumbnailFile(String documentId, int pageIndex) async {
    final appDir = await getApplicationDocumentsDirectory();
    final dir = Directory('${appDir.path}/thumbnails/$documentId');
    if (!await dir.exists()) await dir.create(recursive: true);
    return File('${dir.path}/$pageIndex.png');
  }

  static Future<Uint8List?> _rgbaToPng(
    Uint8List rgba,
    int width,
    int height,
  ) async {
    final completer = Completer<ui.Image>();
    ui.decodeImageFromPixels(
      rgba,
      width,
      height,
      ui.PixelFormat.bgra8888,
      completer.complete,
      rowBytes: width * 4,
    );
    final image = await completer.future;
    final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    return byteData?.buffer.asUint8List();
  }

  /// Render a single PDF page to PNG bytes at [maxWidth] pixel width.
  /// Returns null on failure.
  static Future<Uint8List?> generate(
    String filePath,
    int pageIndex, {
    int maxWidth = 160,
  }) async {
    // Stable, low-collision renderer handle key for this file. Plain
    // `filePath.hashCode` can collide between different paths; combining it
    // with the path length and basename (no extra deps beyond `path`)
    // drastically reduces the chance two distinct files share a handle.
    final documentId =
        'thumb-${filePath.hashCode}-${filePath.length}-${p.basename(filePath)}';
    try {
      final bytes = await File(filePath).readAsBytes();
      final pageCountStr = await PdfViewerPlatform.instance
          .initializePdfRenderer(bytes, documentId);
      if (pageCountStr == null) return null;

      final pageCount = int.tryParse(pageCountStr);
      if (pageCount == null || pageIndex < 0 || pageIndex >= pageCount) {
        await PdfViewerPlatform.instance.closeDocument(documentId);
        return null;
      }

      final pagesHeight = await PdfViewerPlatform.instance.getPagesHeight(
        documentId,
      );
      final pagesWidth = await PdfViewerPlatform.instance.getPagesWidth(
        documentId,
      );
      if (pagesHeight == null || pagesWidth == null) {
        await PdfViewerPlatform.instance.closeDocument(documentId);
        return null;
      }

      final pageHeight = pagesHeight[pageIndex] as double;
      final pageWidth = pagesWidth[pageIndex] as double;
      final thumbnailHeight = (maxWidth * pageHeight / pageWidth).round();

      final rgba = await PdfViewerPlatform.instance.getPage(
        pageIndex + 1,
        maxWidth,
        thumbnailHeight,
        documentId,
      );
      await PdfViewerPlatform.instance.closeDocument(documentId);

      if (rgba == null) return null;
      return _rgbaToPng(rgba, maxWidth, thumbnailHeight);
    } catch (_) {
      try {
        await PdfViewerPlatform.instance.closeDocument(documentId);
      } catch (_) {}
      return null;
    }
  }

  /// Persist thumbnail bytes to disk and return the file.
  static Future<File?> cacheThumbnail(
    String documentId,
    int pageIndex,
    Uint8List data,
  ) async {
    final file = await _thumbnailFile(documentId, pageIndex);
    await file.writeAsBytes(data);
    return file;
  }

  /// Whether a cached thumbnail exists on disk.
  static Future<bool> hasCached(String documentId, int pageIndex) async {
    return (await _thumbnailFile(documentId, pageIndex)).exists();
  }

  /// Return the cached file if it exists, otherwise null.
  static Future<File?> getCached(String documentId, int pageIndex) async {
    final file = await _thumbnailFile(documentId, pageIndex);
    return (await file.exists()) ? file : null;
  }

  /// Delete all cached thumbnails for [documentId].
  static Future<void> invalidateAll(String documentId) async {
    final appDir = await getApplicationDocumentsDirectory();
    final dir = Directory('${appDir.path}/thumbnails/$documentId');
    if (await dir.exists()) await dir.delete(recursive: true);
  }

  /// Invalidate a single page thumbnail.
  static Future<void> invalidatePage(String documentId, int pageIndex) async {
    final file = await _thumbnailFile(documentId, pageIndex);
    if (await file.exists()) await file.delete();
  }
}
