import 'package:flutter/foundation.dart';

import '../domain/product_draft.dart';
import '../domain/publish_status.dart';
import 'get_product_drafts.dart';
import 'publish_product.dart';
import 'update_product_name.dart';
import 'update_product_price.dart';
import 'update_product_status.dart';

/// The field used to sort the visible product list.
enum ProductSortField {
  /// Sort alphabetically by product name.
  name,

  /// Sort by last-modified date.
  lastModified,

  /// Sort by publishing status (draft → pendingReview → published → unpublished).
  status,
}

/// The outcome of a single status-change action (publish / move-to-draft).
///
/// Consumed once by the UI to show a SnackBar, then cleared.
class StatusActionResult {
  final bool success;
  final String productName;
  final PublishStatus targetStatus;
  final String? errorMessage;

  const StatusActionResult({
    required this.success,
    required this.productName,
    required this.targetStatus,
    this.errorMessage,
  });
}

/// The outcome of a price-update action.
///
/// Consumed once by the UI to show a SnackBar, then cleared.
class PriceActionResult {
  final bool success;
  final String productName;
  final double newPrice;
  final String? errorMessage;

  const PriceActionResult({
    required this.success,
    required this.productName,
    required this.newPrice,
    this.errorMessage,
  });
}

/// The outcome of a name-update action.
///
/// Consumed once by the UI to show a SnackBar, then cleared.
class NameActionResult {
  final bool success;
  final String productName;
  final String newName;
  final String? errorMessage;

  const NameActionResult({
    required this.success,
    required this.productName,
    required this.newName,
    this.errorMessage,
  });
}

/// Controller that manages the product publishing workspace state, including
/// filtering by publish status, free-text search, and draft selection.
class ProductPublishingController extends ChangeNotifier {
  final GetProductDrafts _getProductDrafts;
  final PublishProduct _publishProduct;
  final UpdateProductStatus _updateProductStatus;
  final UpdateProductPrice _updateProductPrice;
  final UpdateProductName _updateProductName;

  ProductPublishingController(
    this._getProductDrafts,
    this._publishProduct,
    this._updateProductStatus,
    this._updateProductPrice,
    this._updateProductName,
  );

  bool _disposed = false;

  bool isLoading = false;
  Object? error;

  /// All drafts returned by the repository (unfiltered).
  List<ProductDraft> _allDrafts = [];

  /// The currently visible drafts after applying [activeFilter] and [searchQuery].
  List<ProductDraft> drafts = [];

  /// The currently selected draft for preview.
  ProductDraft? selectedDraft;

  /// The active status filter label, or `null` for "all".
  ///
  /// Recognised values: `'draft'`, `'pendingReview'`, `'published'`, `'unpublished'`.
  String? activeFilter;

  /// The current free-text search query applied to name, SKU, and category.
  ///
  /// Multi-word queries use AND semantics: every whitespace-separated token
  /// must appear in at least one of the searchable fields.
  String searchQuery = '';

  /// The field used to sort the visible product list.
  ProductSortField activeSortField = ProductSortField.name;

  /// Whether the current sort is ascending (`true`) or descending (`false`).
  bool sortAscending = true;

  /// Product IDs that currently have an in-flight status update.
  ///
  /// Used to prevent duplicate clicks and to let the UI show per-row
  /// progress indicators without locking the entire page.
  final Set<String> updatingIds = {};

  /// Whether the product with [id] is currently being updated.
  bool isUpdating(String id) => updatingIds.contains(id);

  /// The result of the last status-change action.
  ///
  /// Set after [publish] or [updateStatus] completes. The UI should read
  /// this once to show feedback (e.g. a SnackBar) and then call
  /// [consumeActionResult] to clear it.
  StatusActionResult? lastActionResult;

  /// The result of the last price-update action.
  ///
  /// Set after [updatePrice] completes. The UI should read this once to
  /// show feedback (e.g. a SnackBar) and then call [consumePriceResult]
  /// to clear it.
  PriceActionResult? lastPriceResult;

  /// The result of the last name-update action.
  ///
  /// Set after [updateName] completes. The UI should read this once to
  /// show feedback (e.g. a SnackBar) and then call [consumeNameResult]
  /// to clear it.
  NameActionResult? lastNameResult;

  /// Clears [lastActionResult] so the same result is not shown twice.
  void consumeActionResult() {
    lastActionResult = null;
  }

  /// Clears [lastPriceResult] so the same result is not shown twice.
  void consumePriceResult() {
    lastPriceResult = null;
  }

  /// Clears [lastNameResult] so the same result is not shown twice.
  void consumeNameResult() {
    lastNameResult = null;
  }

  /// Loads all product drafts and applies any current filter / search.
  Future<void> load() async {
    isLoading = true;
    error = null;
    _safeNotify();

    try {
      _allDrafts = await _getProductDrafts();
      if (_disposed) return;
      _applyFilters();
      // Auto-select the first draft if nothing is selected.
      selectedDraft ??= drafts.isNotEmpty ? drafts.first : null;
    } catch (e) {
      if (_disposed) return;
      error = e;
    } finally {
      isLoading = false;
      _safeNotify();
    }
  }

  /// Sets the status filter and recomputes the visible list.
  void setFilter(String? filter) {
    activeFilter = filter;
    _applyFilters();
    _safeNotify();
  }

  /// Sets the search query and recomputes the visible list.
  void setSearchQuery(String query) {
    searchQuery = query;
    _applyFilters();
    _safeNotify();
  }

  /// Sets the sort field and direction, then recomputes the visible list.
  void setSort(ProductSortField field, {bool? ascending}) {
    activeSortField = field;
    if (ascending != null) {
      sortAscending = ascending;
    }
    _applyFilters();
    _safeNotify();
  }

  /// Selects a draft for preview.
  void selectDraft(ProductDraft draft) {
    selectedDraft = draft;
    _safeNotify();
  }

  /// Attempts to select a draft by SKU. Returns `true` if found.
  bool selectBySku(String sku) {
    final match = _allDrafts.where((d) => d.sku == sku).firstOrNull;
    if (match != null) {
      selectedDraft = match;
      _safeNotify();
      return true;
    }
    return false;
  }

  /// Publishes the draft with the given [id] and reloads the list.
  Future<void> publish(String id) async {
    // Prevent duplicate clicks while this row is already updating.
    if (updatingIds.contains(id)) return;

    final productName = _productNameById(id);

    updatingIds.add(id);
    _safeNotify();

    try {
      await _publishProduct(id);
      if (_disposed) return;
      updatingIds.remove(id);
      lastActionResult = StatusActionResult(
        success: true,
        productName: productName,
        targetStatus: PublishStatus.published,
      );
      await load();
    } catch (e) {
      if (_disposed) return;
      updatingIds.remove(id);
      lastActionResult = StatusActionResult(
        success: false,
        productName: productName,
        targetStatus: PublishStatus.published,
        errorMessage: e.toString(),
      );
      _safeNotify();
    }
  }

  /// Updates the publishing status of the product with [id].
  ///
  /// Tracks per-row updating state via [updatingIds] so the UI can show
  /// a spinner on the affected row without locking the entire page.
  /// Silently ignores duplicate calls while [id] is already in flight.
  Future<void> updateStatus(String id, PublishStatus status) async {
    // Prevent duplicate clicks while this row is already updating.
    if (updatingIds.contains(id)) return;

    final productName = _productNameById(id);

    updatingIds.add(id);
    _safeNotify();

    try {
      await _updateProductStatus(id, status);
      if (_disposed) return;
      updatingIds.remove(id);
      lastActionResult = StatusActionResult(
        success: true,
        productName: productName,
        targetStatus: status,
      );
      await load();
    } catch (e) {
      if (_disposed) return;
      updatingIds.remove(id);
      lastActionResult = StatusActionResult(
        success: false,
        productName: productName,
        targetStatus: status,
        errorMessage: e.toString(),
      );
      _safeNotify();
    }
  }

  /// Updates only the price of the product with [id].
  ///
  /// Follows the same per-row updating pattern as [updateStatus].
  Future<void> updatePrice(String id, double price) async {
    if (updatingIds.contains(id)) return;

    final productName = _productNameById(id);

    updatingIds.add(id);
    _safeNotify();

    try {
      await _updateProductPrice(id, price);
      if (_disposed) return;
      updatingIds.remove(id);
      lastPriceResult = PriceActionResult(success: true, productName: productName, newPrice: price);
      await load();
    } catch (e) {
      if (_disposed) return;
      updatingIds.remove(id);
      lastPriceResult = PriceActionResult(
        success: false,
        productName: productName,
        newPrice: price,
        errorMessage: e.toString(),
      );
      _safeNotify();
    }
  }

  /// Updates only the name of the product with [id].
  ///
  /// Follows the same per-row updating pattern as [updateStatus].
  Future<void> updateName(String id, String name) async {
    if (updatingIds.contains(id)) return;

    final productName = _productNameById(id);

    updatingIds.add(id);
    _safeNotify();

    try {
      await _updateProductName(id, name);
      if (_disposed) return;
      updatingIds.remove(id);
      lastNameResult = NameActionResult(success: true, productName: productName, newName: name);
      await load();
    } catch (e) {
      if (_disposed) return;
      updatingIds.remove(id);
      lastNameResult = NameActionResult(
        success: false,
        productName: productName,
        newName: name,
        errorMessage: e.toString(),
      );
      _safeNotify();
    }
  }

  // ── Lifecycle ───────────────────────────────────────────────────────────

  @override
  void dispose() {
    if (_disposed) return;
    _disposed = true;
    super.dispose();
  }

  // ── Private helpers ────────────────────────────────────────────────────

  /// Calls [notifyListeners] only if the controller has not been disposed.
  void _safeNotify() {
    if (!_disposed) notifyListeners();
  }

  /// Returns the product name for [id], or a fallback if not found.
  String _productNameById(String id) {
    return _allDrafts.where((d) => d.id == id).map((d) => d.name).firstOrNull ?? 'Product $id';
  }

  void _applyFilters() {
    var result = _allDrafts;

    // Status filter
    final status = _parseStatus(activeFilter);
    if (status != null) {
      result = result.where((d) => d.status == status).toList();
    }

    // Free-text search on name, SKU, and category.
    // Multi-word queries use AND semantics: every whitespace-separated
    // token must appear in at least one of the searchable fields.
    if (searchQuery.isNotEmpty) {
      final tokens = searchQuery
          .toLowerCase()
          .split(RegExp(r'\s+'))
          .where((t) => t.isNotEmpty)
          .toList();
      if (tokens.isNotEmpty) {
        result = result.where((d) {
          final haystack =
              '${d.name.toLowerCase()} ${d.sku.toLowerCase()} ${d.category.toLowerCase()}';
          return tokens.every((token) => haystack.contains(token));
        }).toList();
      }
    }

    // Sort
    result = _sortDrafts(result);

    drafts = result;

    // Keep selection valid; clear if the selected draft is no longer visible.
    if (selectedDraft != null && !drafts.any((d) => d.id == selectedDraft!.id)) {
      selectedDraft = null;
    }
  }

  /// Sorts [items] according to [activeSortField] and [sortAscending].
  ///
  /// Uses name as a stable secondary sort when the primary field has ties.
  List<ProductDraft> _sortDrafts(List<ProductDraft> items) {
    if (items.length <= 1) return items;

    final sorted = List<ProductDraft>.of(items);
    final dir = sortAscending ? 1 : -1;

    sorted.sort((a, b) {
      int cmp;
      switch (activeSortField) {
        case ProductSortField.name:
          cmp = a.name.toLowerCase().compareTo(b.name.toLowerCase());
        case ProductSortField.lastModified:
          cmp = a.lastModified.compareTo(b.lastModified);
        case ProductSortField.status:
          cmp = a.status.index.compareTo(b.status.index);
      }
      // Stable secondary sort by name when primary field ties.
      if (cmp == 0 && activeSortField != ProductSortField.name) {
        cmp = a.name.toLowerCase().compareTo(b.name.toLowerCase());
      }
      return cmp * dir;
    });

    return sorted;
  }

  static PublishStatus? _parseStatus(String? filter) {
    if (filter == null) return null;
    switch (filter) {
      case 'draft':
        return PublishStatus.draft;
      case 'pendingReview':
        return PublishStatus.pendingReview;
      case 'published':
        return PublishStatus.published;
      case 'unpublished':
        return PublishStatus.unpublished;
      default:
        return null;
    }
  }
}
