import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/api/api_paths.dart';
import '../../../core/api/api_providers.dart';
import '../../../core/utils/pdf_opener.dart';
import '../../../core/utils/global_error_handler.dart';
import '../../admin/data/admin_repository.dart';
import '../../admin/domain/admin_category_node.dart';
import '../../auth/data/auth_providers.dart';
import '../../inventory/data/inventory_providers.dart';
import '../../inventory/domain/inventory_item.dart';
import '../../pantry/data/pantry_providers.dart';
import '../../pantry/domain/pantry_item.dart';
import '../data/import_providers.dart';
import '../data/receipt_import_session.dart';
import '../domain/help_text_content.dart';
import '../domain/parsed_receipt_item.dart';
import '../../../core/ui/product_picker_field.dart' show ProductOption;
import '../utils/receipt_import_utils.dart';
import 'edit_dialog.dart';

typedef _Destination = ImportDestination;

// ── Typ-alias ─────────────────────────────────────────────────────────────────

typedef _ItemEdit = ItemEdit;

// ── Huvudwidget ───────────────────────────────────────────────────────────────

class ReceiptImportTab extends ConsumerStatefulWidget {
  const ReceiptImportTab({super.key});

  @override
  ConsumerState<ReceiptImportTab> createState() => _ReceiptImportTabState();
}

class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
  bool _isLoading = false;
  bool _isSaving = false;
  bool _isHelpLoading = false;
  PlatformFile? _pickedFile;
  bool _categoryLoadFailed = false;
  bool _globalProductsLoadFailed = false;
  bool _privateProductsLoadFailed = false;
  bool _authLoadFailed = false;

  // Session-state lyfts till provider — överlever tabbyte
  ReceiptImportSession? get _session => ref.read(receiptImportSessionProvider);
  List<ParsedReceiptItem>? get _items => _session?.items;
  Map<int, _ItemEdit> get _edits => _session?.edits ?? {};
  Map<int, bool> get _selected => _session?.selected ?? {};

  // Produktlistan för pickern
  List<ProductOption> _products = [];

  // Kategoriträdet för tvåstegs-picker
  List<AdminCategoryNode> _categoryTree = [];
  CategoryLookup _lookup = CategoryLookup.fromTree([]);

  // Befintligt inventarie: productId → InventoryItem (för sammanslagning)
  Map<int, InventoryItem> _inventoryByProduct = {};

  // Befintligt baslager: productId → PantryItem (för deduplicering)
  Set<int> _pantryProductIds = {};

  @override
  void initState() {
    super.initState();
    _loadProducts();
    _restoreSession();
  }

  Future<void> _restoreSession() async {
    final notifier = ref.read(receiptImportSessionProvider.notifier);
    await notifier.restore();
    final session = ref.read(receiptImportSessionProvider);
    if (!mounted || session?.fileBytes == null) return;

    final fileName =
        session?.fileName ?? 'kvitto.${session?.fileExtension ?? 'pdf'}';
    final bytes = session!.fileBytes!;
    setState(() {
      _pickedFile = PlatformFile(
        name: fileName,
        size: bytes.length,
        bytes: bytes,
      );
    });

    if (session.items != null) {
      await _loadInventory();
    }
  }

  int? _categoryIdForProduct(int? productId) {
    if (productId == null) return null;
    return _products
        .cast<ProductOption?>()
        .firstWhere((p) => p?.id == productId, orElse: () => null)
        ?.categoryId;
  }

  List<dynamic> _extractItems(dynamic data) {
    if (data is List<dynamic>) return data;
    if (data is Map<String, dynamic>) {
      return (data['items'] as List<dynamic>?) ?? const [];
    }
    return const [];
  }

  Future<void> _loadProducts() async {
    final api = ref.read(apiClientProvider);
    final adminRepo = ref.read(adminRepositoryProvider);

    List<AdminCategoryNode> categoryTree = _categoryTree;
    List<dynamic> globalList = const [];
    List<dynamic> mineList = const [];
    var categoryFailed = false;
    var globalFailed = false;
    var privateFailed = false;
    var authFailed = false;

    try {
      categoryTree = await adminRepo.listCategoryTree();
    } catch (e, st) {
      categoryFailed = true;
      debugPrint('ReceiptImportTab._loadProducts categoryTree failed: $e');
      debugPrintStack(stackTrace: st);
    }

    try {
      final token = await ref.read(authStateProvider.future);

      try {
        final globalData =
            await api.getJson(ProductApiPaths.list, token: token);
        globalList = _extractItems(globalData);
      } catch (e, st) {
        globalFailed = true;
        debugPrint('ReceiptImportTab._loadProducts global products failed: $e');
        debugPrintStack(stackTrace: st);
      }

      try {
        final mineData = await api.getJson(ProductApiPaths.mine, token: token);
        mineList = _extractItems(mineData);
      } catch (e, st) {
        privateFailed = true;
        debugPrint(
            'ReceiptImportTab._loadProducts private products failed: $e');
        debugPrintStack(stackTrace: st);
      }
    } catch (e, st) {
      authFailed = true;
      debugPrint('ReceiptImportTab._loadProducts auth/token failed: $e');
      debugPrintStack(stackTrace: st);
    }

    if (!mounted) return;

    final mergedProducts = [
      ...globalList.cast<Map<String, dynamic>>().map((e) => (
            id: e['id'] as int,
            name: (e['canonicalName'] ?? e['name']) as String,
            categoryId: (e['categoryId'] as num?)?.toInt()
          )),
      ...mineList.cast<Map<String, dynamic>>().map((e) => (
            id: e['id'] as int,
            name: (e['canonicalName'] ?? e['name']) as String,
            categoryId: (e['categoryId'] as num?)?.toInt()
          )),
    ];
    final dedupedById = <int, ProductOption>{
      for (final product in mergedProducts) product.id: product,
    };

    setState(() {
      _products = dedupedById.values.toList();
      _categoryTree = categoryTree;
      _lookup = CategoryLookup.fromTree(categoryTree);
      _categoryLoadFailed = categoryFailed;
      _globalProductsLoadFailed = globalFailed;
      _privateProductsLoadFailed = privateFailed;
      _authLoadFailed = authFailed;
    });

    if (_products.isEmpty && _categoryTree.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
            content: Text(
                'Kunde inte ladda produkter eller kategorier. Försök igen.')),
      );
    }
  }

  bool get _hasLoadDiagnostics =>
      _categoryLoadFailed ||
      _globalProductsLoadFailed ||
      _privateProductsLoadFailed ||
      _authLoadFailed;

  Widget _buildLoadDiagnosticsBanner(ThemeData theme) {
    final issues = <String>[
      if (_authLoadFailed) 'auth/token',
      if (_categoryLoadFailed) 'categories',
      if (_globalProductsLoadFailed) 'products:list',
      if (_privateProductsLoadFailed) 'products:mine',
    ];

    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: Colors.amber.shade50,
        border: Border.all(color: Colors.amber.shade300),
        borderRadius: BorderRadius.circular(10),
      ),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Icon(Icons.warning_amber_rounded,
              color: Colors.amber.shade800, size: 18),
          const SizedBox(width: 8),
          Expanded(
            child: Text(
              'Diagnostik (tillfällig): Problem vid laddning av ${issues.join(', ')}. '
              'Vissa funktioner kan vara begränsade.',
              style: theme.textTheme.bodySmall?.copyWith(
                color: Colors.amber.shade900,
              ),
            ),
          ),
          const SizedBox(width: 8),
          TextButton(
            onPressed: _loadProducts,
            child: const Text('Försök igen'),
          ),
        ],
      ),
    );
  }

  Future<void> _loadInventory() async {
    try {
      final token = await ref.read(authStateProvider.future);
      final invRepo = ref.read(inventoryRepositoryProvider);
      final pantryRepo = ref.read(pantryRepositoryProvider);
      final results = await Future.wait([
        invRepo.fetchInventory(token: token),
        pantryRepo.fetchPantry(token: token),
      ]);
      if (mounted) {
        setState(() {
          _inventoryByProduct = {
            for (final item in results[0] as List<InventoryItem>)
              item.productId: item,
          };
          _pantryProductIds = {
            for (final item in results[1] as List<PantryItem>) item.productId,
          };
        });
      }
    } catch (_) {
      // Tyst fel — sammanslagningsfunktionen är valfri
    }
  }

  Future<void> _pickFile() async {
    final result = await FilePicker.pickFiles(
      type: FileType.custom,
      allowedExtensions: ['pdf', 'png', 'jpg', 'jpeg', 'webp', 'bmp'],
      withData: true,
    );
    if (result == null || result.files.isEmpty) return;
    final file = result.files.first;
    setState(() => _pickedFile = file);
    // Spara bildbytes i session så att förhandsvisningen överlever tabbyte
    ref.read(receiptImportSessionProvider.notifier).setFile(
          file.bytes!,
          file.extension?.toLowerCase() ?? '',
          fileName: file.name,
        );
  }

  Future<void> _submit() async {
    final session = ref.read(receiptImportSessionProvider);
    final submitBytes = _pickedFile?.bytes ?? session?.fileBytes;
    if (submitBytes == null) return;

    final submitFileName = _pickedFile?.name ??
        session?.fileName ??
        'kvitto.${session?.fileExtension ?? 'pdf'}';

    setState(() {
      _isLoading = true;
    });
    // Obs: setFile() i _pickFile har redan placerat bytes i session; clear() behövs ej här

    try {
      final token = await ref.read(authStateProvider.future);
      final repo = ref.read(importRepositoryProvider);
      final items = await repo.importReceiptFile(
        bytes: submitBytes,
        filename: submitFileName,
        token: token,
      );
      if (!mounted) return;
      final notifier = ref.read(receiptImportSessionProvider.notifier);
      final nextEdits = <int, _ItemEdit>{};
      final nextSelected = <int, bool>{};
      // Förmarkera rader som har en träff
      for (var i = 0; i < items.length; i++) {
        final it = items[i];
        final pid = it.matchedProductId ?? it.suggestedProductId;
        nextSelected[i] = pid != null;
        final inferred = inferPackageFields(
          rawName: it.rawName,
          quantity: it.quantity,
          unit: it.unit,
        );
        final name = it.matchedProductName ?? it.suggestedProductName;
        final resolvedCategoryId = it.categorySuggestionId ??
            (pid != null ? _categoryIdForProduct(pid) : null);
        final resolvedCategoryPath =
            it.categorySuggestionPath ?? _lookup.pathFor(resolvedCategoryId);

        if (pid != null || resolvedCategoryPath != null) {
          nextEdits[i] = _ItemEdit(
            productId: pid,
            productName: name,
            categoryId: resolvedCategoryId,
            categoryPath: resolvedCategoryPath,
            categorySource: it.categorySuggestionId != null
                ? CategorySelectionSource.ai
                : null,
            quantity: inferred.totalQuantity,
            unit: inferred.totalUnit,
            packQuantity: inferred.packQuantity,
            packUnit: inferred.packUnit,
            packageCount: inferred.packageCount,
          );
        }
      }
      notifier.setImportedResult(
        items: items,
        edits: nextEdits,
        selected: nextSelected,
      );
      // Ladda inventariet för att visa befintliga poster och möjliggöra sammanslagning
      await _loadInventory();
    } catch (e) {
      if (mounted)
        showGlobalErrorDialog(context, 'Ett fel uppstod vid import: $e');
    } finally {
      if (mounted) setState(() => _isLoading = false);
    }
  }

  Future<void> _openEditDialog(
    int index, {
    ImportProductEntryMode? initialEntryMode,
  }) async {
    final needsCategoryTree = initialEntryMode != ImportProductEntryMode.create;
    if (_categoryTree.isEmpty) {
      await _loadProducts();
      if (!mounted) return;
      if (_categoryTree.isEmpty && needsCategoryTree) {
        showGlobalErrorDialog(
          context,
          'Inga kategorier kunde laddas. Prova att uppdatera kategorier i Admin > Databas och försök igen.',
        );
        return;
      }
      if (_categoryTree.isEmpty && !needsCategoryTree) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text(
              'Kategorier kunde inte laddas just nu. Du kan fortfarande skapa produkt utan kategori.',
            ),
          ),
        );
      }
    }

    final item = _items![index];
    final inferred = inferPackageFields(
      rawName: item.rawName,
      quantity: item.quantity,
      unit: item.unit,
    );
    final current = _edits[index] ??
        _ItemEdit(
          productId: item.matchedProductId ?? item.suggestedProductId,
          productName: item.matchedProductName ?? item.suggestedProductName,
          categoryId: item.categorySuggestionId ??
              _categoryIdForProduct(
                  item.matchedProductId ?? item.suggestedProductId),
          categoryPath: item.categorySuggestionPath ??
              _lookup.pathFor(
                item.categorySuggestionId ??
                    _categoryIdForProduct(
                        item.matchedProductId ?? item.suggestedProductId),
              ),
          categorySource: item.categorySuggestionId != null
              ? CategorySelectionSource.ai
              : null,
          quantity: inferred.totalQuantity,
          unit: inferred.totalUnit,
          packQuantity: inferred.packQuantity,
          packUnit: inferred.packUnit,
          packageCount: inferred.packageCount,
        );

    final result = await showDialog<_ItemEdit>(
      context: context,
      builder: (_) => EditDialog(
        item: item,
        current: current,
        products: _products,
        categoryTree: _categoryTree,
        initialEntryMode: initialEntryMode,
        canLearnGlobalAlias: ref.read(isAdminProvider),
        onCreate: (name, categoryId) async {
          final token = await ref.read(authStateProvider.future);
          final api = ref.read(apiClientProvider);
          final raw = await api.postJson(
            ProductApiPaths.createPrivate,
            body: {
              'name': name.trim(),
              if (categoryId != null) 'categoryId': categoryId,
            },
            token: token,
          );

          if (raw is! Map<String, dynamic>) {
            throw Exception('Ogiltigt API-svar vid produktskapande.');
          }

          final idRaw = raw['id'];
          if (idRaw is! num) {
            throw Exception('API-svar saknar giltigt produkt-id.');
          }

          final displayNameRaw = raw['canonicalName'] ?? raw['name'];
          final displayName = displayNameRaw?.toString().trim();
          if (displayName == null || displayName.isEmpty) {
            throw Exception('API-svar saknar produktnamn.');
          }

          final int? returnedCategoryId = raw['categoryId'] is num
              ? (raw['categoryId'] as num).toInt()
              : categoryId;

          final newProduct = (
            id: idRaw.toInt(),
            name: displayName,
            categoryId: returnedCategoryId,
          );

          if (mounted) setState(() => _products = [..._products, newProduct]);
          return newProduct;
        },
      ),
    );
    if (result != null && mounted) {
      ref.read(receiptImportSessionProvider.notifier).setEdit(index, result);
      ref.read(receiptImportSessionProvider.notifier).setSelected(index, true);
      setState(() {});
    }
  }

  Future<void> _showReceiptPreview(
      BuildContext context, List<ParsedReceiptItem> items) async {
    if (!context.mounted) return;
    await showDialog(
      context: context,
      builder: (ctx) => _ReceiptPreviewDialog(items: items),
    );
  }

  Future<void> _showHelpForReceiptImport() async {
    if (_isHelpLoading) return;
    setState(() => _isHelpLoading = true);
    try {
      final token = await ref.read(authStateProvider.future);
      final repo = ref.read(importRepositoryProvider);
      final help =
          await repo.fetchHelpTextByKey('receipt_import', token: token);
      if (!mounted) return;
      await _showHelpDialog(help);
    } catch (e) {
      if (!mounted) return;
      showGlobalErrorDialog(context, 'Kunde inte läsa hjälptexten just nu: $e');
    } finally {
      if (mounted) setState(() => _isHelpLoading = false);
    }
  }

  Future<void> _showHelpDialog(HelpTextContent help) {
    final updatedAt = help.updatedAt;
    final updatedAtText = updatedAt == null
        ? null
        : '${updatedAt.year.toString().padLeft(4, '0')}-${updatedAt.month.toString().padLeft(2, '0')}-${updatedAt.day.toString().padLeft(2, '0')}';

    return showDialog<void>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: Text(help.title.isEmpty ? 'Hjälp: Kvittoimport' : help.title),
        content: SizedBox(
          width: 560,
          child: SingleChildScrollView(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                SelectableText(help.content),
                const SizedBox(height: 12),
                Wrap(
                  spacing: 8,
                  runSpacing: 8,
                  children: [
                    Chip(label: Text('Scope: ${help.scope}')),
                    if (updatedAtText != null)
                      Chip(label: Text('Uppdaterad: $updatedAtText')),
                  ],
                ),
              ],
            ),
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(ctx).pop(),
            child: const Text('Stäng'),
          ),
        ],
      ),
    );
  }

  void _deleteItem(int index) {
    final items = _items;
    if (items == null || index < 0 || index >= items.length) return;

    // Ta bort raden från items
    final remainingItems = <ParsedReceiptItem>[...items];
    remainingItems.removeAt(index);

    // Re-index edits och selected i en enda pass
    final remainingEdits = <int, ItemEdit>{};
    final remainingSelected = <int, bool>{};
    var newIndex = 0;

    for (var oldIndex = 0; oldIndex < items.length; oldIndex++) {
      if (oldIndex != index) {
        if (_edits.containsKey(oldIndex)) {
          remainingEdits[newIndex] = _edits[oldIndex]!;
        }
        if (_selected[oldIndex] == true) {
          remainingSelected[newIndex] = true;
        }
        newIndex++;
      }
    }

    final notifier = ref.read(receiptImportSessionProvider.notifier);
    if (remainingItems.isEmpty) {
      notifier.clear();
    } else {
      notifier.setImportedResult(
        items: remainingItems,
        edits: remainingEdits,
        selected: remainingSelected,
      );
    }
    setState(() {});
  }

  Future<void> _editAliasForItem(int index) async {
    final items = _items;
    if (items == null || index < 0 || index >= items.length) return;

    final item = items[index];
    final edit = _edits[index];
    final productId = edit?.productId;
    final initialAlias = item.rawName.trim();

    if (productId == null) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
            content: Text('Välj produkt för raden innan du redigerar alias.')),
      );
      return;
    }

    final controller = TextEditingController(text: initialAlias);
    final aliasName = await showDialog<String>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Spara alias för raden'),
        content: TextField(
          controller: controller,
          autofocus: true,
          decoration: const InputDecoration(
            labelText: 'Aliasnamn',
            border: OutlineInputBorder(),
          ),
          onSubmitted: (value) => Navigator.pop(ctx, value.trim()),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(ctx),
            child: const Text('Avbryt'),
          ),
          FilledButton(
            onPressed: () => Navigator.pop(ctx, controller.text.trim()),
            child: const Text('Spara'),
          ),
        ],
      ),
    );
    controller.dispose();

    if (!mounted || aliasName == null) return;
    if (aliasName.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Aliasnamn kan inte vara tomt.')),
      );
      return;
    }

    try {
      await ref.read(adminRepositoryProvider).upsertReceiptAlias(
            receiptName: aliasName,
            productId: productId,
            isGlobal: false,
          );
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Alias sparat.')),
      );
    } catch (e) {
      if (!mounted) return;
      showGlobalErrorDialog(context, 'Kunde inte uppdatera alias: $e');
    }
  }

  Future<void> _addSelected() async {
    final items = _items;
    if (items == null) return;

    final toAdd = <int>[];
    for (var i = 0; i < items.length; i++) {
      if (_selected[i] == true && _edits[i]?.productId != null) toAdd.add(i);
    }
    if (toAdd.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
            content: Text('Välj produkter för alla markerade rader först.')),
      );
      return;
    }

    setState(() => _isSaving = true);
    try {
      final token = await ref.read(authStateProvider.future);
      final repo = ref.read(importRepositoryProvider);

      // Bygg upp items för saveReceipt endpoint
      final saveItems = <Map<String, dynamic>>[];
      for (final i in toAdd) {
        final edit = _edits[i]!;
        final item = items[i];
        final pid = edit.productId!;

        final saveItem = <String, dynamic>{
          'rawName': item.rawName,
          'quantity': edit.quantity ?? item.quantity ?? 0,
          'unit': (edit.unit ?? item.unit ?? 'st').trim(),
          'destination':
              edit.destination == _Destination.pantry ? 'pantry' : 'inventory',
          'productId': pid,
        };

        // Lägg till optional fält
        if (item.price != null) saveItem['price'] = item.price;
        if (item.brand != null) saveItem['brand'] = item.brand;
        if (item.origin != null) saveItem['origin'] = item.origin;

        // Päckfält för inventory
        if (edit.destination == _Destination.inventory) {
          if (edit.packQuantity != null)
            saveItem['packQuantity'] = edit.packQuantity;
          if (edit.packUnit != null) saveItem['packUnit'] = edit.packUnit;
          if (edit.packageCount != null)
            saveItem['packageCount'] = edit.packageCount;
        }

        // Lär in alias bara om användaren uttryckligen valt det
        final alreadyAliasMatch =
            item.matchedVia == 'alias' && item.matchedProductId == pid;
        if (edit.learnAlias &&
            item.rawName.trim().isNotEmpty &&
            !alreadyAliasMatch) {
          saveItem['learnAlias'] = true;
          if (edit.learnAliasGlobally) {
            saveItem['learnAliasGlobally'] = true;
          }
        }

        // Lär in enhetsmappning för inventory
        if (edit.destination == _Destination.inventory) {
          final originalUnit = (item.unit ?? '').trim().toLowerCase();
          final preferredUnit =
              (edit.unit ?? item.unit ?? 'st').trim().toLowerCase();
          if (originalUnit.isNotEmpty &&
              preferredUnit.isNotEmpty &&
              originalUnit != preferredUnit) {
            saveItem['learnUnitMapping'] = true;
          }
        }

        saveItems.add(saveItem);
      }

      // Gör ett enda anrop till saveReceipt
      final response = await repo.saveReceipt(
        items: saveItems,
        token: token,
      );

      if (!mounted) return;

      // Visa feedback från response
      final created = response['created'] as int? ?? 0;
      final merged = response['merged'] as int? ?? 0;
      final pantryAdded = response['pantryAdded'] as int? ?? 0;
      final pantrySkipped = response['pantrySkipped'] as int? ?? 0;
      final aliasesLearned = response['aliasesLearned'] as int? ?? 0;
      final unitMappingsLearned = response['unitMappingsLearned'] as int? ?? 0;
      final flyerAutoSync = response['flyerAutoSync'] as Map<String, dynamic>?;
      final errors = response['errors'] as List? ?? [];

      final parts = <String>[
        if (created > 0) '$created ny${created == 1 ? '' : 'a'} i inventarie',
        if (merged > 0)
          '$merged ${merged == 1 ? 'sammanslagen' : 'sammanslagna'} i inventarie',
        if (pantryAdded > 0)
          '$pantryAdded tillagd${pantryAdded == 1 ? '' : 'a'} i baslager',
        if (pantrySkipped > 0) '$pantrySkipped fanns redan i baslager',
        if (aliasesLearned > 0) '$aliasesLearned alias inlärda',
        if (unitMappingsLearned > 0)
          '$unitMappingsLearned enhetsmappningar inlärda',
        if ((flyerAutoSync?['bought'] as int? ?? 0) > 0)
          '${flyerAutoSync?['bought']} planerade flyer-varor markerade som köpta',
        if ((flyerAutoSync?['ambiguous'] as int? ?? 0) > 0)
          '${flyerAutoSync?['ambiguous']} flyer-matchningar kräver kontroll',
      ];

      if (errors.isNotEmpty) {
        final errorParts = <String>[];
        for (final err in errors) {
          final index = err['index'] as int?;
          final error = err['error'] as String?;
          if (index != null && error != null) {
            errorParts.add('Rad $index: $error');
          }
        }
        if (errorParts.isNotEmpty) {
          parts.add('⚠️ ${errorParts.join(', ')}');
        }
      }

      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(parts.join(', ') + '.')),
      );

      // Ta bort de tillagda raderna från listan
      final addedIndexSet = toAdd.toSet();
      final remainingItems = <ParsedReceiptItem>[];
      final remainingEdits = <int, ItemEdit>{};
      final remainingSelected = <int, bool>{};

      var newIndex = 0;
      for (var oldIndex = 0; oldIndex < items.length; oldIndex++) {
        if (!addedIndexSet.contains(oldIndex)) {
          remainingItems.add(items[oldIndex]);
          if (_edits.containsKey(oldIndex)) {
            remainingEdits[newIndex] = _edits[oldIndex]!;
          }
          if (_selected[oldIndex] == true) {
            remainingSelected[newIndex] = true;
          }
          newIndex++;
        }
      }

      final notifier = ref.read(receiptImportSessionProvider.notifier);
      if (remainingItems.isEmpty) {
        notifier.clear();
      } else {
        notifier.setImportedResult(
          items: remainingItems,
          edits: remainingEdits,
          selected: remainingSelected,
        );
      }
      setState(() {});
      await _loadInventory();
    } catch (e) {
      if (mounted) showGlobalErrorDialog(context, 'Fel vid sparande: $e');
    } finally {
      if (mounted) setState(() => _isSaving = false);
    }
  }

  bool get _canSubmit {
    if (_isLoading) return false;
    if (_pickedFile?.bytes != null) return true;
    final session = ref.read(receiptImportSessionProvider);
    return session?.fileBytes != null;
  }

  int get _selectedCount => _selected.values.where((v) => v).length;

  // ── Kvittobild / PDF-förhandsvisning ───────────────────────────────────────

  Widget _buildReceiptPreview(ThemeData theme, ReceiptImportSession session) {
    final bytes = session.fileBytes!;
    final ext = session.fileExtension ?? '';
    final isImage = ['png', 'jpg', 'jpeg', 'webp', 'bmp'].contains(ext);

    return Card(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          ListTile(
            dense: true,
            leading: Icon(
              isImage ? Icons.image_outlined : Icons.picture_as_pdf_outlined,
              color: theme.colorScheme.primary,
            ),
            title: const Text('Kvittoförhandsvisning'),
            trailing: isImage
                ? null
                : OutlinedButton.icon(
                    icon: const Icon(Icons.open_in_new, size: 16),
                    label: const Text('Visa kvitto'),
                    style: OutlinedButton.styleFrom(
                        visualDensity: VisualDensity.compact),
                    onPressed: () async {
                      final opened = await openPdfBytes(bytes);
                      if (!context.mounted || opened) return;
                      ScaffoldMessenger.of(context).showSnackBar(
                        const SnackBar(
                          content: Text(
                              'PDF kan bara öppnas direkt i webbversionen just nu.'),
                        ),
                      );
                    },
                  ),
          ),
          if (isImage)
            Padding(
              padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
              child: ClipRRect(
                borderRadius: BorderRadius.circular(8),
                child: ConstrainedBox(
                  constraints: const BoxConstraints(maxHeight: 600),
                  child: Image.memory(bytes, fit: BoxFit.contain),
                ),
              ),
            )
          else
            const SizedBox(height: 8),
        ],
      ),
    );
  }

  Widget _buildMatchedViaBadge(ParsedReceiptItem item, ThemeData theme) {
    final via = item.matchedVia;
    if (via == null || via == 'none') return const SizedBox.shrink();

    final (label, bg, fg) = switch (via) {
      'alias' => ('Alias', Colors.teal.shade50, Colors.teal.shade800),
      'wordmatch' => ('Ordmatch', Colors.blue.shade50, Colors.blue.shade800),
      'ai' => ('AI-kategori', Colors.purple.shade50, Colors.purple.shade800),
      _ => (
          'Matchad',
          theme.colorScheme.surfaceContainerHighest,
          theme.colorScheme.onSurfaceVariant
        ),
    };

    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
      decoration: BoxDecoration(
        color: bg,
        borderRadius: BorderRadius.circular(999),
        border: Border.all(color: fg.withValues(alpha: 0.3)),
      ),
      child: Text(
        label,
        style: theme.textTheme.labelSmall
            ?.copyWith(color: fg, fontWeight: FontWeight.w600),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final session = ref.watch(receiptImportSessionProvider);
    final theme = Theme.of(context);
    final items = session?.items;
    final selectedFileName = _pickedFile?.name ?? session?.fileName;
    final selectedFileSizeBytes =
        _pickedFile?.size ?? session?.fileBytes?.length;

    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Expanded(
                child: Text(
                  'Ladda upp ett kvitto (PDF eller bild) — raderna tolkas och kan läggas till i ditt inventarie.',
                  style: theme.textTheme.bodyMedium
                      ?.copyWith(color: theme.colorScheme.onSurfaceVariant),
                ),
              ),
              const SizedBox(width: 8),
              TextButton.icon(
                onPressed: _isHelpLoading ? null : _showHelpForReceiptImport,
                icon: _isHelpLoading
                    ? const SizedBox(
                        width: 14,
                        height: 14,
                        child: CircularProgressIndicator(strokeWidth: 2),
                      )
                    : const Icon(Icons.help_outline),
                label: const Text('Läs hjälp'),
              ),
            ],
          ),
          const SizedBox(height: 20),
          OutlinedButton.icon(
            onPressed: _isLoading ? null : _pickFile,
            icon: const Icon(Icons.attach_file),
            label: Text(
                selectedFileName == null ? 'Välj kvittofil' : selectedFileName),
          ),
          if (selectedFileSizeBytes != null) ...[
            const SizedBox(height: 8),
            Text(
              '${(selectedFileSizeBytes / 1024).round()} KB',
              style: theme.textTheme.bodySmall
                  ?.copyWith(color: theme.colorScheme.outline),
            ),
          ],
          // ── Förhandsvisning av kvitto ────────────────────────────────────────
          if (session?.fileBytes != null) ...[
            const SizedBox(height: 12),
            _buildReceiptPreview(theme, session!),
          ],
          const SizedBox(height: 24),
          if (_isLoading) ...[
            const LinearProgressIndicator(),
            const SizedBox(height: 8),
            Text(
              'Tolkar kvittot — detta kan ta upp till en minut...',
              style: theme.textTheme.bodySmall
                  ?.copyWith(color: theme.colorScheme.onSurfaceVariant),
            ),
            const SizedBox(height: 16),
          ],
          FilledButton.icon(
            onPressed: _canSubmit ? _submit : null,
            icon: const Icon(Icons.receipt_long_outlined),
            label: const Text('Importera kvitto'),
          ),
          if (_hasLoadDiagnostics) ...[
            const SizedBox(height: 12),
            _buildLoadDiagnosticsBanner(theme),
          ],
          // ── Resultatlista ──────────────────────────────────────────────
          if (items != null) ...[
            const SizedBox(height: 24),
            const Divider(),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text('${items.length} rader — tryck för att redigera',
                    style: theme.textTheme.titleSmall),
                Row(
                  children: [
                    TextButton.icon(
                      onPressed: items.isEmpty
                          ? null
                          : () => _showReceiptPreview(context, items),
                      icon: const Icon(Icons.description_outlined),
                      label: const Text('Se kvitto'),
                    ),
                    const SizedBox(width: 8),
                    TextButton(
                      onPressed: () => setState(() {
                        final notifier =
                            ref.read(receiptImportSessionProvider.notifier);
                        notifier.setSelectedForAll(
                            items.length, _selectedCount < items.length);
                      }),
                      child: Text(_selectedCount < items.length
                          ? 'Välj alla'
                          : 'Avmarkera alla'),
                    ),
                  ],
                ),
              ],
            ),
            const SizedBox(height: 4),
            ListView.builder(
              key: const PageStorageKey<String>('receipt-import-result-list'),
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              itemCount: items.length,
              itemBuilder: (context, i) {
                return _ReceiptImportResultRow(
                  index: i,
                  item: items[i],
                  edit: _edits[i],
                  existingInventoryByProduct: _inventoryByProduct,
                  pantryProductIds: _pantryProductIds,
                  onCheckedChanged: (v) {
                    ref
                        .read(receiptImportSessionProvider.notifier)
                        .setSelected(i, v);
                  },
                  onEditRequested: () => _openEditDialog(i),
                  onSelectExistingRequested: () => _openEditDialog(
                    i,
                    initialEntryMode: ImportProductEntryMode.existing,
                  ),
                  onCreateRequested: () => _openEditDialog(
                    i,
                    initialEntryMode: ImportProductEntryMode.create,
                  ),
                  onAliasEditRequested: () => _editAliasForItem(i),
                  onDeleteRequested: () => _deleteItem(i),
                  matchedViaBadgeBuilder: _buildMatchedViaBadge,
                );
              },
            ),
            const SizedBox(height: 16),
            SizedBox(
              width: double.infinity,
              child: FilledButton.icon(
                onPressed:
                    (_selectedCount > 0 && !_isSaving) ? _addSelected : null,
                icon: _isSaving
                    ? const SizedBox(
                        width: 18,
                        height: 18,
                        child: CircularProgressIndicator(
                            strokeWidth: 2, color: Colors.white))
                    : const Icon(Icons.add_shopping_cart),
                label: Text(_selectedCount > 0
                    ? 'Lägg till $_selectedCount markerade'
                    : 'Markera rader att lägga till'),
              ),
            ),
          ],
        ],
      ),
    );
  }
}

class _ReceiptImportResultRow extends ConsumerWidget {
  final int index;
  final ParsedReceiptItem item;
  final _ItemEdit? edit;
  final Map<int, InventoryItem> existingInventoryByProduct;
  final Set<int> pantryProductIds;
  final ValueChanged<bool> onCheckedChanged;
  final VoidCallback onEditRequested;
  final VoidCallback onSelectExistingRequested;
  final VoidCallback onCreateRequested;
  final VoidCallback onAliasEditRequested;
  final VoidCallback onDeleteRequested;
  final Widget Function(ParsedReceiptItem item, ThemeData theme)
      matchedViaBadgeBuilder;

  const _ReceiptImportResultRow({
    required this.index,
    required this.item,
    required this.edit,
    required this.existingInventoryByProduct,
    required this.pantryProductIds,
    required this.onCheckedChanged,
    required this.onEditRequested,
    required this.onSelectExistingRequested,
    required this.onCreateRequested,
    required this.onAliasEditRequested,
    required this.onDeleteRequested,
    required this.matchedViaBadgeBuilder,
  });

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final isChecked = ref.watch(
      receiptImportSessionProvider.select((s) => s?.selected[index] ?? false),
    );
    final theme = Theme.of(context);
    final hasProduct = edit?.productId != null;
    final categoryPath = edit?.categoryPath ?? item.categorySuggestionPath;
    final isMatched = item.matchedProductId != null;
    final isSuggested =
        item.suggestedProductId != null && item.matchedProductId == null;
    final existingInv =
        edit?.productId != null && edit?.destination != _Destination.pantry
            ? existingInventoryByProduct[edit!.productId]
            : null;
    final inferredForPreview = inferPackageFields(
      rawName: item.rawName,
      quantity: edit?.quantity ?? item.quantity,
      unit: edit?.unit ?? item.unit,
    );
    final previewPackageCount =
        edit?.packageCount ?? inferredForPreview.packageCount;
    final previewPackQuantity =
        edit?.packQuantity ?? inferredForPreview.packQuantity;
    final previewIncomingQty = previewPackQuantity != null
        ? (previewPackQuantity * previewPackageCount)
        : (edit?.quantity ??
            inferredForPreview.totalQuantity ??
            item.quantity ??
            0);
    final previewIncomingUnit = edit?.packUnit ??
        inferredForPreview.packUnit ??
        edit?.unit ??
        item.unit ??
        'st';
    final convertedPreviewQty = existingInv == null
        ? null
        : convertQuantity(
            previewIncomingQty,
            previewIncomingUnit,
            existingInv.unit,
          );
    final canMergePreview = existingInv != null && convertedPreviewQty != null;
    final alreadyInPantry =
        edit?.productId != null && edit?.destination == _Destination.pantry
            ? pantryProductIds.contains(edit!.productId)
            : false;

    return Card(
      margin: const EdgeInsets.symmetric(vertical: 3),
      child: ListTile(
        leading: Checkbox(
          value: isChecked,
          onChanged: (v) => onCheckedChanged(v ?? false),
        ),
        title: Text(
          normalizeProductName(item.rawName),
          style: theme.textTheme.bodyMedium,
        ),
        subtitle: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              [
                if ((edit?.quantity ?? item.quantity) != null)
                  '${edit?.quantity ?? item.quantity}',
                if ((edit?.unit ?? item.unit) != null) edit?.unit ?? item.unit!,
                if (item.price != null) '· ${item.price} kr',
              ].join(' '),
              style: theme.textTheme.bodySmall,
            ),
            const SizedBox(height: 2),
            if (hasProduct)
              Wrap(
                spacing: 6,
                runSpacing: 4,
                crossAxisAlignment: WrapCrossAlignment.center,
                children: [
                  Text(
                    'Produktnamn: ${normalizeProductName(edit!.productName ?? '')}',
                    style: theme.textTheme.bodySmall?.copyWith(
                      color: isMatched
                          ? Colors.green.shade700
                          : theme.colorScheme.primary,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  matchedViaBadgeBuilder(item, theme),
                  if (edit!.categorySource != null)
                    Container(
                      padding: const EdgeInsets.symmetric(
                          horizontal: 8, vertical: 2),
                      decoration: BoxDecoration(
                        color:
                            edit!.categorySource == CategorySelectionSource.ai
                                ? Colors.green.shade50
                                : theme.colorScheme.surfaceContainerHighest,
                        borderRadius: BorderRadius.circular(999),
                        border: Border.all(
                          color:
                              edit!.categorySource == CategorySelectionSource.ai
                                  ? Colors.green.shade300
                                  : theme.colorScheme.outlineVariant,
                        ),
                      ),
                      child: Text(
                        edit!.categorySource == CategorySelectionSource.ai
                            ? 'AI'
                            : 'Manuell',
                        style: theme.textTheme.labelSmall?.copyWith(
                          color:
                              edit!.categorySource == CategorySelectionSource.ai
                                  ? Colors.green.shade800
                                  : theme.colorScheme.onSurfaceVariant,
                        ),
                      ),
                    ),
                ],
              )
            else if (isSuggested)
              Text(
                'Namnförslag: ${normalizeProductName(item.suggestedProductName ?? '')}',
                style: theme.textTheme.bodySmall
                    ?.copyWith(color: Colors.orange.shade700),
              )
            else
              Text(
                'Ingen matchning ännu — tryck för att välja eller skapa produkt',
                style: theme.textTheme.bodySmall
                    ?.copyWith(color: theme.colorScheme.tertiary),
              ),
            if (categoryPath != null) ...[
              const SizedBox(height: 2),
              Text(
                '${hasProduct ? 'Kategori' : 'Kategoriförslag'}: $categoryPath',
                style: theme.textTheme.bodySmall?.copyWith(
                  color: theme.colorScheme.onSurfaceVariant,
                ),
              ),
            ],
            if (!hasProduct && !isSuggested) ...[
              const SizedBox(height: 8),
              Wrap(
                spacing: 8,
                runSpacing: 8,
                children: [
                  OutlinedButton.icon(
                    onPressed: onSelectExistingRequested,
                    icon: const Icon(Icons.search, size: 16),
                    label: const Text('Välj befintlig'),
                    style: OutlinedButton.styleFrom(
                      visualDensity: VisualDensity.compact,
                      tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                    ),
                  ),
                  OutlinedButton.icon(
                    onPressed: onCreateRequested,
                    icon: const Icon(Icons.add_box_outlined, size: 16),
                    label: const Text('Ny produkt'),
                    style: OutlinedButton.styleFrom(
                      visualDensity: VisualDensity.compact,
                      tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                    ),
                  ),
                ],
              ),
            ],
            if (existingInv != null && canMergePreview) ...[
              const SizedBox(height: 2),
              Row(children: [
                Icon(Icons.kitchen_outlined,
                    size: 12, color: Colors.blue.shade700),
                const SizedBox(width: 3),
                Text(
                  'I lager: ${existingInv.quantity} ${existingInv.unit} → blir ${(existingInv.quantity + convertedPreviewQty).toStringAsFixed(existingInv.quantity % 1 == 0 ? 0 : 2)} ${existingInv.unit}',
                  style: theme.textTheme.bodySmall
                      ?.copyWith(color: Colors.blue.shade700),
                ),
              ]),
            ],
            if (existingInv != null && !canMergePreview) ...[
              const SizedBox(height: 2),
              Row(children: [
                Icon(Icons.info_outline,
                    size: 12, color: Colors.orange.shade700),
                const SizedBox(width: 3),
                Text(
                  'Finns i lager med annan enhet (${existingInv.unit}) - sparas som ny rad',
                  style: theme.textTheme.bodySmall
                      ?.copyWith(color: Colors.orange.shade700),
                ),
              ]),
            ],
            if (alreadyInPantry) ...[
              const SizedBox(height: 2),
              Row(children: [
                Icon(Icons.inventory_2_outlined,
                    size: 12, color: Colors.orange.shade700),
                const SizedBox(width: 3),
                Text(
                  'Finns redan i baslager',
                  style: theme.textTheme.bodySmall
                      ?.copyWith(color: Colors.orange.shade700),
                ),
              ]),
            ],
          ],
        ),
        trailing: SizedBox(
          width: 120,
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Icon(
                hasProduct
                    ? Icons.check_circle
                    : (isSuggested ? Icons.help_outline : Icons.error_outline),
                color: hasProduct
                    ? Colors.green
                    : (isSuggested
                        ? Colors.orange
                        : theme.colorScheme.tertiary),
                size: 20,
              ),
              if (hasProduct)
                IconButton(
                  icon: const Icon(
                    Icons.drive_file_rename_outline,
                    size: 18,
                    semanticLabel: 'Spara alias',
                  ),
                  onPressed: onAliasEditRequested,
                  tooltip: 'Spara alias',
                  constraints:
                      const BoxConstraints(minWidth: 40, minHeight: 40),
                  padding: const EdgeInsets.all(4),
                ),
              IconButton(
                icon: Icon(
                  Icons.delete_outline,
                  size: 18,
                  color: theme.colorScheme.error,
                  semanticLabel: 'Ta bort rad',
                ),
                onPressed: onDeleteRequested,
                tooltip: 'Ta bort rad',
                constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
                padding: const EdgeInsets.all(4),
              ),
            ],
          ),
        ),
        onTap: onEditRequested,
      ),
    );
  }
}

class _ReceiptPreviewDialog extends StatelessWidget {
  final List<ParsedReceiptItem> items;

  const _ReceiptPreviewDialog({required this.items});

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return AlertDialog(
      title: const Text('Kvittotexten i sin helhet'),
      content: SizedBox(
        width: 600,
        child: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                'Här visas all OCR-parsad text från kvittot. En rad per artikel:',
                style: theme.textTheme.bodySmall?.copyWith(
                  color: theme.colorScheme.onSurfaceVariant,
                ),
              ),
              const SizedBox(height: 12),
              Container(
                decoration: BoxDecoration(
                  color: theme.colorScheme.surfaceContainerLowest,
                  border: Border.all(color: theme.colorScheme.outlineVariant),
                  borderRadius: BorderRadius.circular(8),
                ),
                padding: const EdgeInsets.all(12),
                child: SelectableText.rich(
                  TextSpan(
                    children: items.isEmpty
                        ? [
                            TextSpan(
                                text: '(Inga rader)',
                                style: theme.textTheme.bodySmall)
                          ]
                        : items.asMap().entries.map((entry) {
                            final item = entry.value;
                            final lineNumber = entry.key + 1;
                            final lineText = _formatReceiptLine(item);
                            return TextSpan(
                              children: [
                                TextSpan(
                                  text: '$lineNumber. ',
                                  style: theme.textTheme.labelSmall?.copyWith(
                                    color: theme.colorScheme.outlineVariant,
                                  ),
                                ),
                                TextSpan(
                                  text: lineText,
                                  style: theme.textTheme.bodySmall?.copyWith(
                                    fontFamily: 'monospace',
                                  ),
                                ),
                                const TextSpan(text: '\n'),
                              ],
                            );
                          }).toList(),
                  ),
                  style: theme.textTheme.bodySmall,
                ),
              ),
            ],
          ),
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('Stäng'),
        ),
      ],
    );
  }

  String _formatReceiptLine(ParsedReceiptItem item) {
    final parts = <String>[];

    if (item.quantity != null) {
      parts.add('${item.quantity}');
    }

    if (item.unit != null) {
      parts.add(item.unit!);
    }

    parts.add(item.rawName);

    if (item.price != null) {
      parts.add('— ${item.price} kr');
    }

    return parts.join(' ');
  }
}
