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

import 'package:photo_manager/photo_manager.dart';

import 'desktop_video_probe.dart';

enum GalleryPermission { granted, limited, denied }

abstract class GalleryItem {
  String get id;
  bool get isVideo;
  Duration? get duration;
  File? get localFile;
  Future<Uint8List?> thumbnail(int size);
  Future<File?> originFile();
  Future<(int, int)?> dimensions();
  Future<Uint8List?> encodeForUpload({
    required int maxDimension,
    required int quality,
  });

  static GalleryItem fromFile(File file) => _FileGalleryItem(file);
}

class PickedPhoto {
  final GalleryItem item;
  final File? editedFile;

  const PickedPhoto({required this.item, this.editedFile});
}

Future<(int, int)?> imageFileDimensions(File file) async {
  ui.ImmutableBuffer? buffer;
  ui.ImageDescriptor? descriptor;
  try {
    final bytes = await file.readAsBytes();
    buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
    descriptor = await ui.ImageDescriptor.encoded(buffer);
    return (descriptor.width, descriptor.height);
  } catch (_) {
    return null;
  } finally {
    descriptor?.dispose();
    buffer?.dispose();
  }
}

abstract class GallerySource {
  Future<GalleryPermission> ensurePermission();
  Future<List<GalleryItem>> load({int limit});
  Future<void> openSettings();
  Future<void> manageAccess();

  factory GallerySource.create() {
    if (Platform.isAndroid || Platform.isIOS) {
      return _PhotoManagerSource();
    }
    return _DesktopGallerySource();
  }
}

class _PhotoManagerSource implements GallerySource {
  @override
  Future<GalleryPermission> ensurePermission() async {
    final state = await PhotoManager.requestPermissionExtend();
    if (state.isAuth) return GalleryPermission.granted;
    if (state.hasAccess) return GalleryPermission.limited;
    return GalleryPermission.denied;
  }

  @override
  Future<List<GalleryItem>> load({int limit = 120}) async {
    final paths = await PhotoManager.getAssetPathList(
      type: RequestType.common,
      onlyAll: true,
      filterOption: FilterOptionGroup(
        orders: const [
          OrderOption(type: OrderOptionType.createDate, asc: false),
        ],
      ),
    );
    if (paths.isEmpty) return const [];
    final assets = await paths.first.getAssetListRange(start: 0, end: limit);
    return assets.map((a) => _AssetGalleryItem(a)).toList();
  }

  @override
  Future<void> openSettings() => PhotoManager.openSetting();

  @override
  Future<void> manageAccess() => PhotoManager.presentLimited();
}

class _AssetGalleryItem implements GalleryItem {
  final AssetEntity asset;

  _AssetGalleryItem(this.asset);

  @override
  String get id => asset.id;

  @override
  bool get isVideo => asset.type == AssetType.video;

  @override
  Duration? get duration => isVideo ? Duration(seconds: asset.duration) : null;

  @override
  File? get localFile => null;

  @override
  Future<Uint8List?> thumbnail(int size) =>
      asset.thumbnailDataWithSize(ThumbnailSize.square(size));

  @override
  Future<File?> originFile() => asset.file;

  @override
  Future<Uint8List?> encodeForUpload({
    required int maxDimension,
    required int quality,
  }) => asset.thumbnailDataWithSize(
    ThumbnailSize(maxDimension, maxDimension),
    format: ThumbnailFormat.jpeg,
    quality: quality,
  );

  @override
  Future<(int, int)?> dimensions() async {
    if (asset.width > 0 && asset.height > 0) {
      return (asset.width, asset.height);
    }
    return null;
  }
}

const Set<String> kGalleryImageExtensions = {
  '.jpg',
  '.jpeg',
  '.png',
  '.gif',
  '.webp',
  '.bmp',
  '.heic',
  '.heif',
};

const Set<String> kGalleryVideoExtensions = {
  '.mp4',
  '.mov',
  '.m4v',
  '.mkv',
  '.webm',
  '.avi',
  '.3gp',
};

String _fileExtension(String path) {
  final dot = path.lastIndexOf('.');
  if (dot < 0) return '';
  return path.substring(dot).toLowerCase();
}

bool isVideoPath(String path) =>
    kGalleryVideoExtensions.contains(_fileExtension(path));

class _DesktopGallerySource implements GallerySource {

  @override
  Future<GalleryPermission> ensurePermission() async =>
      GalleryPermission.granted;

  @override
  Future<List<GalleryItem>> load({int limit = 120}) async {
    final entries = <({File file, DateTime modified})>[];
    for (final dir in _candidateDirs()) {
      if (!dir.existsSync()) continue;
      try {
        for (final entity in dir.listSync(followLinks: false)) {
          if (entity is! File || !_isMedia(entity.path)) continue;
          entries.add((file: entity, modified: entity.statSync().modified));
        }
      } catch (_) {}
    }
    entries.sort((a, b) => b.modified.compareTo(a.modified));
    final items = entries
        .take(limit)
        .map((e) => _FileGalleryItem(e.file))
        .toList();
    const batch = 8;
    const eager = 24;

    Future<void> probeRange(int from, int to) async {
      for (var i = from; i < to; i += batch) {
        final end = i + batch > to ? to : i + batch;
        await Future.wait(items.sublist(i, end).map((it) => it.probe()));
      }
    }

    final head = items.length < eager ? items.length : eager;
    await probeRange(0, head);
    if (head < items.length) unawaited(probeRange(head, items.length));
    return items;
  }

  @override
  Future<void> openSettings() async {}

  @override
  Future<void> manageAccess() async {}

  List<Directory> _candidateDirs() {
    final home =
        Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
    if (home == null || home.isEmpty) return const [];
    return [
      Directory('$home/Pictures'),
      Directory('$home/Изображения'),
      Directory('$home/Images'),
      Directory('$home/Videos'),
      Directory('$home/Видео'),
      Directory('$home/Movies'),
    ];
  }

  bool _isMedia(String path) {
    final ext = _fileExtension(path);
    return kGalleryImageExtensions.contains(ext) ||
        kGalleryVideoExtensions.contains(ext);
  }
}

class _FileGalleryItem implements GalleryItem {
  final File file;
  Duration? _duration;

  _FileGalleryItem(this.file, {Duration? duration}) : _duration = duration;

  Future<void> probe() async {
    if (!isVideo || _duration != null) return;
    _duration = await DesktopVideoProbe.duration(file.path);
  }

  @override
  String get id => file.path;

  @override
  bool get isVideo => isVideoPath(file.path);

  @override
  Duration? get duration => _duration;

  @override
  File? get localFile => file;

  @override
  Future<Uint8List?> thumbnail(int size) async =>
      isVideo ? DesktopVideoProbe.thumbnail(file.path, size) : null;

  @override
  Future<File?> originFile() async => file;

  @override
  Future<Uint8List?> encodeForUpload({
    required int maxDimension,
    required int quality,
  }) async => null;

  @override
  Future<(int, int)?> dimensions() => isVideo
      ? DesktopVideoProbe.dimensions(file.path)
      : imageFileDimensions(file);
}
