import 'package:json_annotation/json_annotation.dart';

import '../webdav/queries/list_files/cacheable_file.dart';

part 'search_files_response.g.dart';

/// Subset of the OCS Search Provider API response we actually consume.
/// The provider (`files`) returns one object per match plus pagination state.
@JsonSerializable(explicitToJson: true)
class SearchFilesResponse {
  final String name;
  final bool isPaginated;
  final int? cursor;
  final List<SearchFilesEntry> entries;

  SearchFilesResponse({
    required this.name,
    required this.isPaginated,
    required this.cursor,
    required this.entries,
  });

  factory SearchFilesResponse.fromJson(Map<String, dynamic> json) =>
      _$SearchFilesResponseFromJson(json);
  Map<String, dynamic> toJson() => _$SearchFilesResponseToJson(this);
}

@JsonSerializable()
class SearchFilesEntry {
  final String title;
  final String? subline;
  final String? icon;
  final String? resourceUrl;
  final Map<String, dynamic>? attributes;

  SearchFilesEntry({
    required this.title,
    this.subline,
    this.icon,
    this.resourceUrl,
    this.attributes,
  });

  factory SearchFilesEntry.fromJson(Map<String, dynamic> json) =>
      _$SearchFilesEntryFromJson(json);
  Map<String, dynamic> toJson() => _$SearchFilesEntryToJson(this);

  /// Heuristic — the files provider sets icon classes containing "folder" for
  /// directories. Falls back to false when missing or unrecognised.
  bool get isDirectory => (icon ?? '').toLowerCase().contains('folder');

  String? _stringAttribute(String key) {
    final raw = attributes?[key];
    return raw is String && raw.isNotEmpty ? raw : null;
  }

  String? _dirFromResourceUrl() {
    final url = resourceUrl;
    if (url == null) return null;
    return Uri.tryParse(url)?.queryParameters['dir'];
  }

  /// Reconstructs the WebDAV-relative path used elsewhere (matching
  /// [CacheableFile.path] — no leading slash, trailing slash for
  /// directories). Prefers the explicit `path` attribute set by Nextcloud's
  /// files search provider (28+); falls back to the `dir` query parameter
  /// in [resourceUrl]. Returns `null` when neither is available — `subline`
  /// is intentionally **not** parsed because it is localized UI text
  /// ("in {folder}"), not a path, and using it produced bogus duplicate
  /// folder headers like "/in Alte-Notebooks".
  String? get webdavPath {
    final attrPath = _stringAttribute('path');
    if (attrPath != null) {
      final stripped = attrPath.replaceAll(RegExp(r'^/+|/+$'), '');
      return isDirectory ? '$stripped/' : stripped;
    }
    final dir = _dirFromResourceUrl();
    if (dir != null) {
      final stripped = dir.replaceAll(RegExp(r'^/+|/+$'), '');
      final base = stripped.isEmpty ? title : '$stripped/$title';
      return isDirectory ? '$base/' : base;
    }
    return null;
  }

  /// Derives a filename with its extension from the WebDAV-relative path.
  /// The provider's `title` is often the display name *without* the
  /// extension, which would defeat extension-based icon mapping.
  String _nameFromPath(String path) {
    final stripped = path.replaceAll(RegExp(r'^/+|/+$'), '');
    if (stripped.isEmpty) return title;
    final last = stripped.split('/').last;
    return last.isEmpty ? title : last;
  }

  /// The files search provider exposes the numeric Nextcloud file id either
  /// as an attribute, or implicitly in the `?openfile=<id>` query parameter
  /// of [resourceUrl]. Parses both into an [int] when available — used to
  /// hit the preview endpoint directly.
  int? _extractFileId() {
    final attr = attributes?['fileId'];
    if (attr is int) return attr;
    if (attr is String) {
      final parsed = int.tryParse(attr);
      if (parsed != null) return parsed;
    }
    final url = resourceUrl;
    if (url != null) {
      final raw = Uri.tryParse(url)?.queryParameters['openfile'];
      if (raw != null) return int.tryParse(raw);
    }
    return null;
  }

  CacheableFile? toCacheable() {
    final path = webdavPath;
    if (path == null) return null;
    final fileId = _extractFileId();
    return CacheableFile(
      path: path,
      isDirectory: isDirectory,
      name: _nameFromPath(path),
      fileId: fileId,
      // Search provider responses don't carry `nc:has-preview`. Probe
      // optimistically when a fileId is available — the preview endpoint
      // simply returns 404 for unsupported formats, which the file-leading
      // widget already falls back from to the typed icon.
      hasPreview: (!isDirectory && fileId != null) ? true : null,
    );
  }
}
