// lib/model/source/album.dart
import 'package:aves/model/db/folder_stats.dart';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/filters/container/album_group.dart';
import 'package:aves/model/filters/container/dynamic_album.dart';
import 'package:aves/model/filters/covered/stored_album.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/vaults/vaults.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/collection_utils.dart';
import 'package:aves/view/view.dart';
import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart';
import 'package:flutter/widgets.dart';

bool debugCreateFoldersEnabled = false;

// ============================================================
// FASE 2: FolderRow legacy
// Conservata per compatibilità, ma la logica moderna usa FolderStats.
// ============================================================

class FolderRow {
  final String path;
  final int albumType;
  final int priority;
  final String displayName;

  final int? isRemote;
  final int? hybridType;
  final String? deviceId;

  FolderRow({
    required this.path,
    required this.albumType,
    required this.priority,
    required this.displayName,
    this.isRemote,
    this.hybridType,
    this.deviceId,
  });
}

// ============================================================

mixin AlbumMixin on SourceBase {
  final Set<String> _directories = {};
  final Set<String> _newAlbums = {};

  // Cache cartelle moderna, basata esclusivamente su folderMap.
  late Map<String, FolderStats> foldersCache = {};

  bool _albumsReadyNotified = false;

  // ============================================================
  // GROUPING ALBUM REMOTI / LOCALI
  // ============================================================

  String groupForAlbum(String dir) {
    final folder = foldersCache[dir];

    if (folder == null) return 'Local';

    // Gli album ibridi sono trattati come Local.
    if (folder.hybridType != null) return 'Local';

    if (folder.isRemote == 1) return 'Remote';

    return 'Local';
  }

  static const groupOrder = {
    'Local': 0,
    'User': 1,
    'Common': 2,
    // Device:<id> va sempre in fondo.
  };

  int orderForGroup(String g) {
    if (groupOrder.containsKey(g)) return groupOrder[g]!;

    if (g.startsWith('User:')) return 1;
    if (g.startsWith('Device:')) return 3;

    return 4;
  }

  // ============================================================
  // Rebuild da folderMap only
  // ============================================================

  void _rebuildDirectoriesFromFolderMap({String reason = 'unknown'}) {
    foldersCache = Map<String, FolderStats>.from(folderMap);

    final grouped = <String, List<String>>{};

    for (final dir in foldersCache.keys) {
      final folder = foldersCache[dir];

      if (folder?.hidden == 1) continue;

      if (!remoteVisible && folder?.isRemote == 1) continue;

      final group = groupForAlbum(dir);
      grouped.putIfAbsent(group, () => <String>[]).add(dir);
    }

    final sortedGroups = grouped.keys.toList()
      ..sort((a, b) => orderForGroup(a).compareTo(orderForGroup(b)));

    final sortedDirs = <String>[];

    for (final group in sortedGroups) {
      final dirs = grouped[group]!;

      dirs.sort((a, b) {
        final fa = foldersCache[a];
        final fb = foldersCache[b];

        if (fa != null && fb != null) {
          final p = fa.priority.compareTo(fb.priority);
          if (p != 0) return p;
          return compareAsciiUpperCaseNatural(fa.displayName, fb.displayName);
        }

        return compareAsciiUpperCaseNatural(a, b);
      });

      sortedDirs.addAll(dirs);
    }

    _directories
      ..clear()
      ..addAll(sortedDirs);

    debugPrint(
      '[albums] rebuildFromFolderMap '
      'reason=$reason '
      'folderMap=${folderMap.length} '
      'foldersCache=${foldersCache.length} '
      'directories=${_directories.length}',
    );
  }

  // ============================================================
  // Caricamento cartelle
  // ============================================================

  @override
  Future<Map<String, FolderStats>> loadFoldersFromDb() async {
    _rebuildDirectoriesFromFolderMap(reason: 'loadFoldersFromDb');

    return Map<String, FolderStats>.from(foldersCache);
  }

  @override
  Future<void> initFoldersCache() async {
    _rebuildDirectoriesFromFolderMap(reason: 'initFoldersCache');
  }

  List<String> get rawAlbums {
    if (_directories.isEmpty && folderMap.isNotEmpty) {
      _rebuildDirectoriesFromFolderMap(reason: 'rawAlbums-empty');
    }

    if (!_albumsReadyNotified && _directories.isNotEmpty) {
      _albumsReadyNotified = true;

      Future.microtask(() {
        invalidateStoredAlbumDisplayNames();
        notifyAlbumsChanged();

        debugPrint(
          '[albums] rawAlbums notified UI refresh '
          'count=${_directories.length}',
        );
      });
    }

    debugPrint(
      '[albums] rawAlbums '
      'count=${_directories.length} '
      'folderMap=${folderMap.length}',
    );

    return List.unmodifiable(_directories);
  }

  Set<StoredAlbumFilter> getNewAlbumFilters(BuildContext context) =>
      Set.unmodifiable(
        _newAlbums.map(
          (v) => StoredAlbumFilter(
            v,
            getStoredAlbumDisplayName(context, v),
          ),
        ),
      );

  // ============================================================
  // Ordinamento album
  // ============================================================

  int compareAlbumsByPath(String? a, String? b) =>
      compareAsciiUpperCase(a ??= '', b ??= '');

  @override
  int compareAlbumsByName(String? a, String? b) {
    final fa = foldersCache[a];
    final fb = foldersCache[b];

    if (fa != null && fb != null) {
      final p = fa.priority.compareTo(fb.priority);
      if (p != 0) return p;
      return compareAsciiUpperCaseNatural(fa.displayName, fb.displayName);
    }

    return compareAsciiUpperCaseNatural(a ?? '', b ?? '');
  }

  void notifyAlbumsChanged() {
    eventBus.fire(AlbumsChangedEvent());
  }

  void _onAlbumChanged({bool notify = true}) {
    invalidateStoredAlbumDisplayNames();

    if (notify) {
      notifyAlbumsChanged();
    }
  }

  // ============================================================
  // getAlbumEntries
  // ============================================================

  Map<String, AvesEntry?> getAlbumEntries() {
    if (_directories.isEmpty && folderMap.isNotEmpty) {
      _rebuildDirectoriesFromFolderMap(reason: 'getAlbumEntries-empty');
    }

    final entries = sortedEntriesByDate;
    final result = <String, AvesEntry?>{};

    for (final album in rawAlbums) {
      result[album] = entries.firstWhereOrNull(
        (entry) => entry.directory == album,
      );
    }

    return result;
  }

  // ============================================================
  // updateDirectories
  // ============================================================

  @override
  Future<void> updateDirectories() async {
    _rebuildDirectoriesFromFolderMap(reason: 'updateDirectories');

    _albumsReadyNotified = true;
    invalidateStoredAlbumDisplayNames();

    notifyAlbumsChanged();
  }

  // ============================================================
  // addDirectories / cleanEmptyAlbums
  //
  // FolderMap-only:
  // - non scriviamo folders da qui;
  // - computeFolderMap() in CollectionSource è la fonte unica.
  // ============================================================

  @override
  void addDirectories({
    required Set<String?> albums,
    bool notify = true,
  }) async {
    final dirs = albums.nonNulls.where((a) => a.isNotEmpty).toSet();
    if (dirs.isEmpty) return;

    if (!debugCreateFoldersEnabled) {
      debugPrint(
        '[folders] CREATION SKIPPED (${dirs.length}) '
        '- folderMap-only mode',
      );
    }

    // Non modifichiamo foldersCache manualmente.
    // Le nuove cartelle appariranno quando computeFolderMap()
    // aggiornerà source.folderMap.
    if (notify) {
      notifyAlbumsChanged();
    }
  }

  @override
  void cleanEmptyAlbums([Set<String>? albums]) async {
    final dirs = (albums ?? _directories).nonNulls.toSet();
    if (dirs.isEmpty) return;

    bool changed = false;

    for (final dir in dirs) {
      final stillHasEntries = visibleEntries.any((e) => e.directory == dir);

      if (!stillHasEntries && _isRemovable(dir)) {
        foldersCache.remove(dir);
        _directories.remove(dir);
        changed = true;
      }
    }

    if (changed) {
      notifyAlbumsChanged();
    }
  }

  // ============================================================

  bool _isRemovable(String album) {
    if (visibleEntries.any((entry) => entry.directory == album)) return false;
    if (_newAlbums.contains(album)) return false;
    if (vaults.isVault(album)) return false;

    if (settings.pinnedFilters
        .whereType<StoredAlbumFilter>()
        .map((v) => v.album)
        .contains(album)) {
      return false;
    }

    return true;
  }

  // ============================================================
  // Filter summary
  // ============================================================

  final Map<String, int> _filterEntryCountMap = {}, _filterSizeMap = {};
  final Map<String, AvesEntry?> _filterRecentEntryMap = {};

  void invalidateAlbumFilterSummary({
    Set<AvesEntry>? entries,
    Set<String?>? directories,
    bool notify = true,
  }) {
    if (_filterEntryCountMap.isEmpty &&
        _filterSizeMap.isEmpty &&
        _filterRecentEntryMap.isEmpty) {
      return;
    }

    if (entries == null && directories == null) {
      _filterEntryCountMap.clear();
      _filterSizeMap.clear();
      _filterRecentEntryMap.clear();
    } else {
      directories ??= {};

      if (entries != null) {
        directories.addAll(entries.map((entry) => entry.directory).nonNulls);
      }

      directories.nonNulls
          .map((v) => StoredAlbumFilter(v, null).key)
          .forEach((key) {
        _filterEntryCountMap.remove(key);
        _filterSizeMap.remove(key);
        _filterRecentEntryMap.remove(key);
      });

      invalidateDynamicAlbumFilterSummary(notify: false);
      invalidateAlbumGroupFilterSummary(notify: false);
    }

    if (notify) {
      eventBus.fire(StoredAlbumSummaryInvalidatedEvent(directories));
      eventBus.fire(const DynamicAlbumSummaryInvalidatedEvent());
      eventBus.fire(const AlbumGroupSummaryInvalidatedEvent());
    }
  }

  void invalidateDynamicAlbumFilterSummary({bool notify = true}) {
    _filterEntryCountMap.removeWhere(_isDynamicAlbumKey);
    _filterSizeMap.removeWhere(_isDynamicAlbumKey);
    _filterRecentEntryMap.removeWhere(_isDynamicAlbumKey);

    if (notify) {
      eventBus.fire(const DynamicAlbumSummaryInvalidatedEvent());
    }
  }

  void invalidateAlbumGroupFilterSummary({bool notify = true}) {
    _filterEntryCountMap.removeWhere(_isAlbumGroupKey);
    _filterSizeMap.removeWhere(_isAlbumGroupKey);
    _filterRecentEntryMap.removeWhere(_isAlbumGroupKey);

    if (notify) {
      eventBus.fire(const AlbumGroupSummaryInvalidatedEvent());
    }
  }

  bool _isDynamicAlbumKey(String key, _) =>
      key.startsWith('${DynamicAlbumFilter.type}-');

  bool _isAlbumGroupKey(String key, _) =>
      key.startsWith('${AlbumGroupFilter.type}-');

  // ============================================================
  // Album summary
  // Compatibile con Aves: UI usa StoredAlbumFilter,
  // ma la lista album viene da folderMap.
  // ============================================================

  int albumEntryCount(AlbumBaseFilter filter) {
    return _filterEntryCountMap.putIfAbsent(
      filter.key,
      () {
        if (filter is StoredAlbumFilter) {
          final album = filter.album;

          return visibleEntries
              .where((entry) => entry.directory == album)
              .length;
        }

        return visibleEntries.where(filter.test).length;
      },
    );
  }

  int albumSize(AlbumBaseFilter filter) {
    return _filterSizeMap.putIfAbsent(
      filter.key,
      () {
        if (filter is StoredAlbumFilter) {
          final album = filter.album;

          return visibleEntries
              .where((entry) => entry.directory == album)
              .map((v) => v.sizeBytes)
              .sum;
        }

        return visibleEntries.where(filter.test).map((v) => v.sizeBytes).sum;
      },
    );
  }

  AvesEntry? albumRecentEntry(AlbumBaseFilter filter) {
    return _filterRecentEntryMap.putIfAbsent(
      filter.key,
      () {
        if (filter is StoredAlbumFilter) {
          final album = filter.album;

          return sortedEntriesByDate.firstWhereOrNull(
            (entry) => entry.directory == album,
          );
        }

        return sortedEntriesByDate.firstWhereOrNull(filter.test);
      },
    );
  }

  // ============================================================
  // New albums
  // ============================================================

  void createStoredAlbum(String directory) {
    if (!_directories.contains(directory)) {
      _newAlbums.add(directory);
      addDirectories(albums: {directory});
    }
  }

  void renameNewAlbum(String source, String destination) {
    if (_newAlbums.remove(source)) {
      cleanEmptyAlbums({source});
      createStoredAlbum(destination);
    }
  }

  void forgetNewAlbums(Set<String> directories) {
    _newAlbums.removeAll(directories);
  }

  // ============================================================
  // Display names
  // ============================================================

  final Map<String, String> _albumDisplayNamesWithContext = {},
      _albumDisplayNamesWithoutContext = {};

  void invalidateStoredAlbumDisplayNames() {
    _albumDisplayNamesWithContext.clear();
    _albumDisplayNamesWithoutContext.clear();
  }

  @override
  String getStoredAlbumDisplayName(BuildContext? context, String dirPath) {
    final f = foldersCache[dirPath] ?? folderMap[dirPath];

    if (f != null) return f.displayName;

    return pContext.basename(dirPath);
  }
}

class AlbumsChangedEvent {}

class DynamicAlbumSummaryInvalidatedEvent {
  const DynamicAlbumSummaryInvalidatedEvent();
}

class AlbumGroupSummaryInvalidatedEvent {
  const AlbumGroupSummaryInvalidatedEvent();
}

class StoredAlbumSummaryInvalidatedEvent {
  final Set<String?>? directories;

  const StoredAlbumSummaryInvalidatedEvent(this.directories);
}
